Dynamic agent summary, all you need to know is here, no nonsense!

preface

Interview question: talk about jdk dynamic agent, cglib difference, implementation principle, advantages and disadvantages, and how to call methods

come from: One and a half years of face-to-face experience sharing of social recruitment (including JD Didi, the headline of Alibaba meituan)

This article summarizes the knowledge points you need to answer. There is less nonsense and dry goods in the whole process. The article is long. You can like it. If you like this article, I will share it all the time, and hard core articles will be shared regularly!

At the same time, previous personal websites: upheart.cn/ In the last two days, I decided to continue to maintain the official account. The public articles will be updated regularly (about 2 days).

As for the reason why we decided to continue to maintain, it is mainly for everyone to work and study at the same time. After all, when you work, you can't play the mobile phone to read the official account.

proxy pattern

Proxy mode is a design mode that provides additional access to the target object, that is, access to the target object through the proxy object. In this way, it can provide additional function operations and expand the function of the target object without modifying the original target object

For example: when renting a house, some people will rent directly through the landlord, and some people will rent a house through an intermediary.

Which of these two situations is more convenient? Of course, it is more convenient through intermediary.

The intermediary here is equivalent to an agent. The user completes a series of operations of renting a house through the intermediary (house viewing, deposit payment, house renting and cleaning). The agent mode can effectively decouple the specific implementation from the caller, and completely hide the specific implementation inside through interface oriented coding.

Classification:

Static proxy: it has been implemented at compile time. After compilation, the proxy class is an actual class file

Dynamic agent: it is generated dynamically at runtime, that is, there is no actual class file after compilation, but dynamically generates class bytecode at runtime and loads it into the JVM

Static proxy

Mode of use

Create an interface, and then create the proxy class to implement the interface and implement the abstract methods in the interface. Then create a proxy class and make it also implement this interface. In the proxy class, a reference to the proxy is held, then the method of the object is called in the proxy class method.

public interface UserDao {    
  void save();     
}
public class UserDaoImpl implements UserDao {
    @Override
    public void save() {
        System.out.println("Saving user...");
    }
}
public class TransactionHandler implements UserDao {
    //Target proxy object
    private UserDao target;
    //Pass in the target object when constructing the proxy object
    public TransactionHandler(UserDao target) {
        this.target = target;
    }
    @Override
    public void save() {
        //Processing before calling the target method
        System.out.println("Turn on transaction control...");
        //Call the method of the target object
        target.save();
        //Processing after calling the target method
        System.out.println("Turn off transaction control...");
    }
}
public class Main {
    public static void main(String[] args) {
        //New target object
        UserDaoImpl target = new UserDaoImpl();
        //Create a proxy object and reference it with an interface
        UserDao userDao = new TransactionHandler(target);
        //Call for interface
        userDao.save();
    }
}

Using JDK static proxy, it is easy to complete the proxy operation of a class. However, the shortcomings of JDK static agent are also exposed: because the agent can only serve one class, if there are many classes to be represented, it is cumbersome to write a large number of agent classes

JDK dynamic agent

Five steps of using JDK dynamic agent:

  1. Define your own InvocationHandler by implementing the InvocationHandler interface;

  2. Via proxy Getproxyclass obtains the dynamic proxy class;

  3. Obtain the construction method of proxy class through reflection mechanism, and the method signature is getConstructor(InvocationHandler.class);

  4. Obtain the proxy object through the constructor and pass the custom InvocationHandler instance object as a parameter;

  5. Call the target method through the proxy object;

public interface IHello {
    void sayHello();
}
 public class HelloImpl implements IHello {
    @Override
    public void sayHello() {
        System.out.println("Hello world!");
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
 
public class MyInvocationHandler implements InvocationHandler {
 
    /** Target object */
    private Object target;
 
    public MyInvocationHandler(Object target){
        this.target = target;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("------Insert pre notification code-------------");
        // Implement the corresponding target method
        Object rs = method.invoke(target,args);
        System.out.println("------Insert post-processing code-------------");
        return rs;
    }
}
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;

public class MyProxyTest {
    public static void main(String[] args)
            throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        // =========================First kind==========================
        // 1. Generate class file for $Proxy0
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        // 2. Get dynamic proxy class
        Class proxyClazz = Proxy.getProxyClass(IHello.class.getClassLoader(),IHello.class);
        // 3. Get the constructor of the proxy class and pass in the parameter type invocationhandler class
        Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
        // 4. Create a dynamic proxy object through the constructor and pass in the custom InvocationHandler instance
        IHello iHello1 = (IHello) constructor.newInstance(new MyInvocationHandler(new HelloImpl()));
        // 5. Call the target method through the proxy object
        iHello1.sayHello();
 
        // ==========================Second=============================
        /**
         * Proxy Class also has a simple method to create dynamic proxy objects by encapsulating 2 ~ 4 steps,
         *Its method signature is: newproxyinstance (classloader, loader, class <? > [] instance, invocationhandler h)
         */
        IHello  iHello2 = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), // Class loader for loading interface
                new Class[]{IHello.class}, // A set of interfaces
                new MyInvocationHandler(new HelloImpl())); // Custom InvocationHandler
        iHello2.sayHello();
    }
}

There are some similarities between JDK static proxy and JDK dynamic proxy. For example, proxy classes need to be created, and proxy classes need to implement interfaces.

The difference is: in static proxy, we need to create an agent class for which interface and which proxy class is created, so before we compile, we need the proxy class to implement the same interface as the proxy class, and directly call the corresponding method of the agent class in the implementation method. We don't know which interface the agent is created for, but we don't know which class it is created for.

In one sentence, summarize the difference between JDK static agent and JDK dynamic agent:

JDK static proxy is created through direct coding, while JDK dynamic proxy uses reflection mechanism to create proxy class at runtime.

In fact, the core of dynamic agent is InvocationHandler. Each instance of an agent has an associated invocation handler. When a proxy instance is to be called, the method call is encoded and assigned to the invoke method of its invocation handler

All calls to proxy object instance methods are completed through the invoke method in InvocationHandler, and the invoke method will determine which method to call the proxy according to the passed proxy object, method name and parameters.

CGLIB

The bottom layer of CGLIB package is to convert bytecode and generate new classes by using a small and fast bytecode processing framework ASM

The CGLIB agent is implemented as follows:

  1. First, implement a MethodInterceptor, and the method call will be forwarded to the intercept() method of the class.
  2. Then, when needed, obtain the proxy object through CGLIB dynamic proxy.

Use case

 public class HelloService {
 
    public HelloService() {
        System.out.println("HelloService structure");
    }
 
    /**
     * This method cannot be overridden by subclasses, and Cglib cannot proxy final modified methods
     */
    final public String sayOthers(String name) {
        System.out.println("HelloService:sayOthers>>"+name);
        return null;
    }
 
    public void sayHello() {
        System.out.println("HelloService:sayHello");
    }
}
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
 
import java.lang.reflect.Method;
 
/**
 * Custom MethodInterceptor
 */
public class MyMethodInterceptor implements MethodInterceptor{
 
    /**
     * sub: cglib Generated proxy object
     * method: Proxy object method
     * objects: Method input parameter
     * methodProxy: Agent method
     */
    @Override
    public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("======Insert pre notification======");
        Object object = methodProxy.invokeSuper(sub, objects);
        System.out.println("======Insert the latter notification======");
        return object;
    }
}
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
 
public class Client {
    public static void main(String[] args) {
        // The proxy class file is stored on the local disk, which is convenient for us to decompile and view the source code
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
        // The process of obtaining proxy objects through CGLIB dynamic proxy
        Enhancer enhancer = new Enhancer();
        // Sets the parent class of the enhancer object
        enhancer.setSuperclass(HelloService.class);
        // Set the callback object of enhancer
        enhancer.setCallback(new MyMethodInterceptor());
        // Create proxy object
        HelloService proxy= (HelloService)enhancer.create();
        // Call target method through proxy object
        proxy.sayHello();
    }
}

JDK proxy requires that the proxy class must implement the interface, which has strong limitations.

CGLIB dynamic agent has no such mandatory requirements. In short, CGLIB will let the generated proxy class inherit the proxy class, and strengthen the proxy method in the proxy class (pre-processing, post-processing, etc.).

Summarize what work CGLIB has done when acting as an agent

  • The generated proxy class inherits the proxied class. Here we need to pay attention to one thing: if the delegate class is modified by final, it cannot be inherited, that is, it cannot be proxied; Similarly, if there is a final modified method in the delegate class, the method cannot be proxied
  • The proxy class will generate two methods for the delegate method. One is the method with the same signature as the delegate method. In the method, it will call the delegate method through super; The other is the method unique to the proxy class
  • When executing the method of the proxy object, you will first judge whether there is a cglib $callback that implements the MethodInterceptor interface_ 0 If it exists, the intercept method in MethodInterceptor will be called

In the intercept method, we will not only call the delegate method, but also perform some enhanced operations. In Spring AOP, a typical application scenario is to log operations before and after the execution of some sensitive methods

In CGLIB, method calls are not done through reflection, but directly to the method: the Class object is specially processed through the FastClass mechanism. For example, the method reference will be saved in an array. Each time the method is called, the reference to the method is maintained through an index subscript

Fastclass mechanism

CGLIB adopts the FastClass mechanism to call the intercepted method.

FastClass mechanism is to index the methods of a class and directly call the corresponding methods through the index

public class test10 {
  //Here, tt can be regarded as the target object and fc can be regarded as the proxy object; First, get the index of the target method according to the getIndex method of the proxy object,
  //Then call the invoke method of the proxy object to directly call the method of the target class, avoiding reflection
    public static void main(String[] args){
        Test tt = new Test();
        Test2 fc = new Test2();
        int index = fc.getIndex("f()V");
        fc.invoke(index, tt, null);
    }
}

class Test{
    public void f(){
        System.out.println("f method");
    }
    
    public void g(){
        System.out.println("g method");
    }
}
class Test2{
    public Object invoke(int index, Object o, Object[] ol){
        Test t = (Test) o;
        switch(index){
        case 1:
            t.f();
            return null;
        case 2:
            t.g();
            return null;
        }
        return null;
    }
    //This method indexes the methods in the Test class
    public int getIndex(String signature){
        switch(signature.hashCode()){
        case 3078479:
            return 1;
        case 3108270:
            return 2;
        }
        return -1;
    }
}

In the above example, Test2 is the Fastclass of Test. There are two methods getIndex and invoke in Test2.

In the getIndex method, index each method of Test and return the corresponding index according to the input parameter (method name + method descriptor).

Invoke calls the method of object O with ol as the input parameter according to the specified index. This avoids reflection calls and improves efficiency

Comparison of three agency methods

Agency moderealizationadvantageshortcomingcharacteristic
JDK static proxyThe proxy class and the delegate class implement the same interface, and the hard coded interface is required in the proxy classSimple implementation and easy to understandProxy classes need hard coded interfaces, which may lead to repeated coding, waste of storage space and low efficiency in practical applicationsIt doesn't seem to have any characteristics
JDK dynamic agentThe proxy class and the delegate class implement the same interface. The proxy class implements InvocationHandler and rewrites the invoke method for dynamic proxy. The method will be enhanced in the invoke methodNo hard coding interface is required, and the code reuse rate is highOnly delegate classes that implement interfaces can be proxiedThe bottom layer uses reflection mechanism to call methods
CGLIB dynamic proxyThe proxy class takes the delegate class as its parent class and creates two methods for the non final delegate methods. One is the method with the same signature as the delegate method, which will call the delegate method through super in the method; The other is the method unique to the proxy class. In the proxy method, it will judge whether there is an object that implements the MethodInterceptor interface. If so, it will call the intercept method to proxy the delegate methodThe interface does not need to be implemented by the class and can be enhanced at run timefinal classes and methods cannot be proxiedThe bottom layer stores all the methods in an array and calls the methods directly through the array index

problem

CGlib is faster than JDK?

  • Cglib is used to realize dynamic proxy. The bottom layer of cglib adopts ASM bytecode generation framework and bytecode technology to generate proxy classes, which is more efficient than Java reflection before jdk6. The only thing to note is that cglib cannot delegate a method declared as final, because the principle of cglib is to dynamically generate subclasses of the proxied class.

  • After jdk6, jdk7 and jdk8 gradually optimize the JDK dynamic agent, the efficiency of JDK agent is higher than that of CGLIB agent under the condition of less calls. Only when a large number of calls are made, the efficiency of jdk6 and jdk7 is a little lower than that of CGLIB agent, but when it comes to jdk8, the efficiency of JDK agent is higher than that of CGLIB agent. In short, with each JDK version upgrade, the efficiency of JDK agent is improved, and the CGLIB agent message does not keep up with the pace.

How does Spring choose JDK or CGLIB?

  • When the Bean implements the interface, Spring will use the dynamic proxy of JDK.
  • When the Bean does not implement the interface, Spring uses CGlib to implement it.
  • You can force the use of CGlib

Keywords: Java Dynamic Proxy

Added by zleviticus on Wed, 09 Feb 2022 16:07:23 +0200