Deep understanding of java Dynamic Proxy

Posted by Opticon on Fri, 08 Oct 2021 18:40:16 +0200

AOP,Aspectj,Spring AOP Past and Present

  • AOP is to implement certain packaging based on the code we originally wrote, such as interception or enhancement processing before method execution, after method return, after method run out of the exception, etc.
  • The implementation of AOP is not because of what magical dog java provides. It tells us a few lifecycles of the method. Instead, we want to implement a proxy. The actual running instance is the instance of the generated proxy class.
  • Dynamic proxy. By default, use JDK dynamic proxy if using an interface, or CGLIB if no interface exists.
  • After Spring 3.2, spring-core directly included source code for CGLIB and ASM, so we don't need to introduce two dependencies
  • Spring's AOP is managed by IOC containers.
  • Spring provides Aspectj support, but only uses AspectJ's tangent resolution and matching.

Static Proxy

Let's learn about static proxies through examples, then understand the drawbacks of static proxies Write an interface UserService and an implementation class of the interface UserServiceImpl

public interface UserService {
    public void select();   
    public void update();
}

public class UserServiceImpl implements UserService {  
    public void select() {  
        System.out.println("query selectById");
    }
    public void update() {
        System.out.println("To update update");
    }
}

We will enhance UserServiceImpl with a static proxy, logging some logs before calling select and update. Write a proxy class UserServiceProxy, which needs to implement UserService

public class UserServiceProxy implements UserService {
    private UserService target; // Object to be proxied

    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    public void select() {
        before();
        target.select();    // This is where the real theme role's method is actually invoked
        after();
    }
    public void update() {
        before();
        target.update();    // This is where the real theme role's method is actually invoked
        after();
    }

    private void before() {     // Execute before executing method
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    private void after() {      // Execute after executing method
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}

Client Test

ublic class Client1 {
    public static void main(String[] args) {
        UserService userServiceImpl = new UserServiceImpl();
        UserService proxy = new UserServiceProxy(userServiceImpl);

        proxy.select();
        proxy.update();
    }
}

output

log start time [Thu Dec 20 14:13:25 CST 2021] 
query selectById
log end time [Thu Dec 20 14:13:25 CST 2021] 
log start time [Thu Dec 20 14:13:25 CST 2021] 
To update update
log end time [Thu Dec 20 14:13:25 CST 2021] 

With a static proxy, we achieve the purpose of enhancing functionality without breaking into the original code, which is an advantage of a static proxy.

Static proxy, disadvantage If the scenario is complex and static proxy needs to proxy multiple classes, there are two ways for proxy objects to implement interfaces consistent with the target object:

  • Maintain only one proxy class, which is too large;
  • Create new proxy classes, one for each target object, but this results in too many proxy classes
  • When an interface needs to be added or deleted, both the target object and the proxy class need to be modified at the same time, which is not easy to maintain.

If you see the disadvantage of a static proxy, how to avoid that is to make the proxy generate dynamically, that is, to make the proxy dynamic.
Why classes can be generated dynamically
This involves the class loading mechanism, and the process of loading a class on a java virtual machine is divided into five main stages: loading, validation, preparation, parsing, initialization.
1. Get the binary byte stream that defines a class by its fully qualified name
2. Convert the static storage structure represented by this byte stream into the runtime data structure of the method area
3. Generate a java.lang.Class object in memory that represents this class as various data access entries for this class in the method area
Since the virtual machine specification is not specific for three points, the actual implementation is very flexible, dare the first point, there are several ways to obtain binary streams (class byte codes):

  • Get from zip packages, such as jar,war, and so on
  • Get it from the network, such as the applet
  • This scenario is generated at runtime using dynamic proxy technology most often, in java.lang.reflect.proxy, using ProxyGenenrtor.generateProxyClass to generate the form for a particular interface as "
  • Read from database
  • Generate from other files, typically jsp, which generates the corresponding class class class from a JSP file
    Therefore, a dynamic proxy is a way to calculate the byte code of the proxy based on the interface or target object and load it into the jvm for use.

Direction of main implementation agent
By Implementing Interfaces - > JDK Dynamic Proxy
By inheriting classes - > CGLIB dynamic proxy

JDK Dynamic Proxy

The JDK dynamic proxy mainly involves two classes: java.lang.reflect.Proxy and java.lang.reflect.InvocationHandler. We still learn to write a calling logical processor LogHandler class through a case, provide log enhancements, and implement the InvocationHandler interface; maintain a target object in the LogHandler, which is the object being proxied(Real Theme Role); Write logical processing of method calls in invoke methods.

package com.jzc.proxy.jdk;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class LogHandle implements InvocationHandler {
	Object target;//Agent object, actual method executor

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("Before executing method");
		Object res = method.invoke(target,args);
		System.out.println("After method execution");
		return res;
	}
	//important
	public Object getProxy() {
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
	}
	LogHandle(Object target) {
		this.target = target;
	}

}

Query User Implementation Class

public class UserServiceImpl implements UserService{

	@Override
	public void select() {
		System.out.println("Query Users");
	}

}

Client

package com.jzc.proxy.jdk;

public class JDKClient {
	public static void main(String[] args) {
		UserServiceImpl userService = new UserServiceImpl();
		LogHandle logHandle = new LogHandle(userService);
		UserService proxy = (UserService) logHandle.getProxy();
		proxy.select();
	}
}

Result

Before executing method
 Query Users
 After method execution

among
Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);Construct a new instance of the proxy class that implements the specified interface, and all methods call the invoke method of the given processor object

Public Object invoke (Object proxy, Method, Object[] args) defines the action that a proxy object wants to perform when calling a method, which is used to centralize method calls on dynamic proxy class objects.

Decompile dynamic proxy code

public final class UserServiceProxy extends Proxy implements UserService {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public UserServiceProxy(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void select() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.jzc.proxy.jdk.UserService").getMethod("select");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

You can see

  • UserServiceProxy inherits the Proxy class and implements all the interfaces that are proxied, as well as equals, hashCode, toString, and so on.
  • Since UserServiceProxy inherits the Proxy class, each proxy class is associated with an InvocationHandler method call processor
  • Classes and all methods are decorated with public final, so proxy classes can only be used, not inherited
  • Each method has a Method object to describe it, which is created in a static block of code and named in the form of an m +number
  • The method is called through super.h.invoke(this, m1, (Object[])null);Invocation, where super.h.invoke is actually a LogHandler object passed to Proxy.newProxyInstance when the proxy is created, inherits the InvocationHandler class and is responsible for the actual call handling logic

LogHandler's invoke method receives parameters such as method, args, etc., performs some processing, and then lets the proxied object target execute the method through reflection

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);       // Call the method method method of the target
        after();
        return result;  // Returns the execution result of the method
    }

The jdk dynamic proxy is invoked as follows:

CGLIB Dynamic Proxy

CGLIB(Code Generation Library) is an open source, high performance, high quality Code Generation Class Library (Code Generation Package).
The bottom level of CGLIB is to convert byte codes and generate new classes by using a small and fast byte code processing framework ASM. However, direct use of the ASM framework is not encouraged because of the high technical requirements of the bottom level.

Use case

Introducing CGLIB dependencies
Here we take UserDao, which operates on user data, as an example, to enhance its functionality through a dynamic proxy (adding logs before and after execution). UserDao is defined as follows:

public class UserDao {
	public void select() {
		System.out.println("Dynamic Proxy Query");
	}
}

Create interceptors, implement methodInterceptor, for interception callbacks of methods.

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class LogInterceptor implements MethodInterceptor {
	/**
     * @param object Represents the object to be enhanced
     * @param method Method representing interception
     * @param objects Arrays represent parameter lists, and basic data types need to be passed in their wrapper types, such as int-->Integer,long-Long,double-->Double
     * @param methodProxy Represents a proxy to a method. invokeSuper Method represents a call to a proxy object method
     * @return results of enforcement
     * @throws Throwable
     * /
	@Override
	public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
		System.out.println("Before call");
		Object result = methodProxy.invokeSuper(o,objects);
		System.out.println("After call");
		return result;
	}
}
public class CgLibClient {
	public static void main(String[] args) {
		LogInterceptor logInterceptor = new LogInterceptor();
		 // Create Enhancer object, Proxy class similar to JDK dynamic Proxy
		Enhancer enhancer = new Enhancer();
		// Set Byte Code File for Target Class
		enhancer.setSuperclass(UserDao.class);// Setting up superclasses, cglib is implemented through inheritance
		// Set Callback Function
		enhancer.setCallback(logInterceptor);
		UserDao proxy = (UserDao) enhancer.create();// Create Proxy Class
		// Invoke specific business methods of proxy classes
		proxy.select();
	}
}

Run Results
You can see that the corresponding enhancement has been added before and after the method

Before call
 Dynamic Proxy Query
 After call

Decompile class
In the first line of the main method, we can also add the following settings to store the class file for the proxy class

UserDao$$EnhancerByCGLIB$$18912a09$$FastClassByCGLIB$$d67b432b.class
UserDao$$EnhancerByCGLIB$$18912a09.class
UserDao$$FastClassByCGLIB$$41e08c12.class
hodProxy(Signature var0) {
        String var10000 = var0.toString();
        switch(var10000.hashCode()) {
        case -1716030215:
            if (var10000.equals("select()V")) {
                return CGLIB$select$0$Proxy;
            }
            break;
        case -508378822:
            if (var10000.equals("clone()Ljava/lang/Object;")) {
                return CGLIB$clone$4$Proxy;
            }
            break;
        case 1826985398:
            if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
                return CGLIB$equals$1$Proxy;
            }
            break;
        case 1913648695:
            if (var10000.equals("toString()Ljava/lang/String;")) {
                return CGLIB$toString$2$Proxy;
            }
            break;
        case 1984935277:
            if (var10000.equals("hashCode()I")) {
                return CGLIB$hashCode$3$Proxy;
            }
        }

        return null;
    }

    public UserDao$$EnhancerByCGLIB$$18912a09() {
        CGLIB$BIND_CALLBACKS(this);
    }

    public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
        CGLIB$THREAD_CALLBACKS.set(var0);
    }

    public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
        CGLIB$STATIC_CALLBACKS = var0;
    }

    private static final void CGLIB$BIND_CALLBACKS(Object var0) {
        UserDao$$EnhancerByCGLIB$$18912a09 var1 = (UserDao$$EnhancerByCGLIB$$18912a09)var0;
        if (!var1.CGLIB$BOUND) {
            var1.CGLIB$BOUND = true;
            Object var10000 = CGLIB$THREAD_CALLBACKS.get();
            if (var10000 == null) {
                var10000 = CGLIB$STATIC_CALLBACKS;
                if (var10000 == null) {
                    return;
                }
            }

            var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
        }

    }

    public Object newInstance(Callback[] var1) {
        CGLIB$SET_THREAD_CALLBACKS(var1);
        UserDao$$EnhancerByCGLIB$$18912a09 var10000 = new UserDao$$EnhancerByCGLIB$$18912a09();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Callback var1) {
        CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
        UserDao$$EnhancerByCGLIB$$18912a09 var10000 = new UserDao$$EnhancerByCGLIB$$18912a09();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
        CGLIB$SET_THREAD_CALLBACKS(var3);
        UserDao$$EnhancerByCGLIB$$18912a09 var10000 = new UserDao$$EnhancerByCGLIB$$18912a09;
        switch(var1.length) {
        case 0:
            var10000.<init>();
            CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
            return var10000;
        default:
            throw new IllegalArgumentException("Constructor not found");
        }
    }

    public Callback getCallback(int var1) {
        CGLIB$BIND_CALLBACKS(this);
        MethodInterceptor var10000;
        switch(var1) {
        case 0:
            var10000 = this.CGLIB$CALLBACK_0;
            break;
        default:
            var10000 = null;
        }

        return var10000;
    }

    public void setCallback(int var1, Callback var2) {
        switch(var1) {
        case 0:
            this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
        default:
        }
    }

    public Callback[] getCallbacks() {
        CGLIB$BIND_CALLBACKS(this);
        return new Callback[]{this.CGLIB$CALLBACK_0};
    }

    public void setCallbacks(Callback[] var1) {
        this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
    }

    static {
        CGLIB$STATICHOOK1();
    }
}

From the decompiled source code, it can be seen that the proxy object inherits UserDao, the intercept() method is called by the intercept() intercept () method, and the intercept() method is implemented by the custom LogInterceptor. Therefore, the intercept() method in LogInterceptor is finally called to complete the dynamic proxy implementation from the proxy object to the target object.

CGLIB Create Dynamic Proxy Class Procedure

(1) Find method definitions for all non-final public types on the target class;

(2) Convert qualified method definitions into byte codes;

(3) converting the byte codes into the corresponding proxy class objects;

(4) Implement the MethodInterceptor interface to handle requests for all methods on the proxy class.

JDK Dynamic Proxy versus CGLIB

  • JDK dynamic proxy: based on Java reflection mechanism implementation, business classes that implement interfaces must be implemented to generate proxy objects.
  • CGLIB dynamic proxy: Based on ASM mechanism, it is implemented by generating a subclass of business class as proxy class.

Advantages of JDK Proxy:

Minimize dependencies, code implementation is simple, development and maintenance is simplified, JDK native support is more reliable than CGLIB, and upgrades smoothly with the JDK version. Byte code class libraries often need to be updated to be usable on the new version of Java.

Advantages based on CGLIB:
There is no need to implement interfaces, no intrusion into proxy classes, and only the classes you care about, without increasing the workload for other related classes. High performance.

How to choose in a specific business scenario can be compared based on their strengths and weaknesses, but as a benchmark project in Java, Spring uses CGLIB for many functions.

Interview Questions
Describe several implementations of dynamic proxy? Indicate the advantages and disadvantages.

Proxy can be divided into "static proxy" and "dynamic proxy", and dynamic proxy can be divided into "JDK dynamic proxy" and "CGLIB dynamic proxy" implementations.
Static Proxy: The proxy object and the actual object both inherit the same interface and point to an instance of the actual object in the proxy object so that the proxy object is exposed and the Real Object is actually called

Advantages: Business logic of real objects can be well protected from exposure, thereby improving security.
Disadvantages: Different interfaces require different proxy class implementations, which can be redundant
JDK dynamic proxy:

To solve the redundancy caused by generating a large number of proxy classes in static proxy, jdk dynamic proxy only needs to implement InvocationHandler interface, override invoke method to complete proxy implementation, jdk proxy uses reflection to generate Proxyxx.class proxy class byte code and generate objectJDK dynamic proxy can only proxy interfaces because the proxy class itself has extends Proxy, while java does not allow multiple inheritance, but allows multiple interfaces to be implemented.

Advantages: Solves the redundant proxy implementation class problem in static proxies.

Disadvantages: JDK dynamic proxy is implemented based on interface design and throws exceptions if there is no interface.

CGLIB Agent:

Because the JDK dynamic proxy restricts the design to be based on interfaces only, the JDK method cannot solve the problem without interfaces; CGLibA very low-level byte code technology is used, which is based on the byte code technology to create a subclass for a class, intercept all the parent method calls in the subclass by the method intercept technology, and weave the crosscut logic into the homeopathy to complete the implementation of dynamic proxy.The callback method of the class is implemented. However, CGLib takes much more time to create proxy objects than JDK, so CGLib is more appropriate for singleton objects because it does not need to create objects frequently, and JDK is more appropriate instead. Also, because CGLib is a method of creating subclasses dynamically, it cannot proxy for final methods.

Advantages: Dynamic proxy can be achieved without an interface, and the performance is good using byte code enhancement technology.

Disadvantages: Technical implementation is relatively difficult to understand.

Topics: Java Dynamic Proxy