Jacko teaches you about dynamic agents

Keywords: Java Dynamic Proxy

As we know, dynamic proxy uses reflection. AOP in Spring uses dynamic proxy, so it is equivalent to using reflection mechanism. So, what is an agent? What is dynamic agent? How is reflection used in dynamic proxies? Today, I'll show you the true face of dynamic agents.

Agent mode overview

In short, the proxy mode is to use the proxy object to replace the access to the real object, so that it can provide additional function operations and expand the function of the target object without modifying the original target object.

The agent model has three roles:

  • Real Subject: real class, i.e. proxy class and delegate class. Used to truly complete business service functions;
  • Proxy: proxy class, which implements its own request with the corresponding function of Real Subject. The proxy class object does not really realize its business function;
  • Subject: defines the interface that both RealSubject and Proxy roles should implement.

As shown in the figure:

Generally speaking, the main function of proxy mode is to expand the function of the target object. For example, you can add some additional operations before and after the execution of a method of the target object without modifying the original code of the method. If you have learned AOP of Spring, you will be able to understand this sentence well.

For example, if you want to rent a house (you are the entrusted Real Subject), you first find an intermediary (the intermediary is equivalent to your Proxy), and then the intermediary agent you go to the landlord to rent a house. In the view of the landlord, it only cares about renting a house. Because you both rent a house, it is equivalent to that you are the same as the intermediary (funny).

Then why do proxy classes and delegate classes implement the same interface?

In order to maintain the consistency of behavior, there is no difference between the two in the eyes of visitors. In this way, through the middle layer of proxy class, the delegate class object is well hidden and protected, which can effectively shield the direct access of the outside world to the delegate class object. At the same time, you can also add additional operations to the agent class. For example, the intermediary will talk about the price with the landlord. In the view of the landlord, you also talk about the price when renting a house. Therefore, this realizes the enhancement of the function of the entrustment class.

The agent mode has two implementation modes: static agent and dynamic agent.

Static proxy

What is a static proxy

Let's first look at the implementation steps of the static agent:

1) Define an interface (Subject)

2) Create a Real Subject to implement this interface

3) Creating a Proxy class also implements this interface

4) entrust the class Real Subject into the agent class Proxy and invoke the corresponding method in Real Subject in the proxy class method. In this way, we can block the access to the target object through the proxy class, and do something we want to do before and after the execution of the target method.

From the perspective of implementation and application, the enhancement of each method of the target object in the static proxy is done manually, which is very inflexible (for example, once the method is added to the interface, the target object and proxy object must be modified) and troublesome (a proxy class needs to be written separately for each target class). There are very few actual application scenarios, and there is almost no scenario of using static agents in daily development.

From the JVM level, the static proxy will turn the interface, delegate class and proxy class into actual. Class files at compile time.

Here is an example:

1) Define the interface for sending SMS

public interface SmsService {
    String send(String message);
}

2) Create a Real Subject to implement this interface

public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

3) Creating a Proxy class also implements this interface

4) entrust the class Real Subject into the agent class Proxy and invoke the corresponding method in Real Subject in the proxy class method. In this way, we can block the access to the target object through the proxy class, and do something we want to do before and after the execution of the target method.

public class SmsProxy implements SmsService {
 
    // Inject the delegate class into the proxy class
    private final SmsService smsService;

    public SmsProxy(SmsService smsService) {
        this.smsService = smsService;
    }

    @Override
    public String send(String message) {
        // Before calling the delegate class method, we can add our own operations
        System.out.println("before method send()");
        // Calling delegate class methods
        smsService.send(message); 
        // After calling the delegate class method, we can also add our own operations
        System.out.println("after method send()");
        return null;
    }
}

So, how to use this enhanced send method?

public class Main {
    public static void main(String[] args) {
        SmsService smsService = new SmsServiceImpl();
        SmsProxy smsProxy = new SmsProxy(smsService);
        smsProxy.send("Java");
    }
}

After running the above code, the console prints out:

before method send()
send message:java
after method send()

From the output, we can see that we have enhanced the send() method of the delegate class SmsServiceImpl.

The above process is abstracted as follows:

Of course, we can also see from the above code that static agents have some disadvantages. If we now add a delegate class to implement the SmsService interface, if we want to enhance this delegate class, we need to rewrite a proxy class and inject this new delegate class, which is very inflexible. In other words, a static proxy is a delegate corresponding to a proxy class. Can you make the proxy class a general one? For this reason, dynamic agent application is born.

Dynamic agent

Before formally introducing the play of dynamic proxy, let's take a look at the. class bytecode file. The dynamic proxy mechanism is closely related to the Java bytecode generation framework.

We know that a class class corresponds to a. Class bytecode file, that is, all the information of a class is stored in the bytecode file. Bytecode is actually a binary file, which contains machine code that can only be recognized by the JVM.

The parsing process is as follows: the JVM reads the. Class bytecode file, takes out the binary data, loads it into memory, parses the information in the bytecode file, and generates the corresponding class object:

Obviously, the above process occurs at compile time.

Well, since the JVM loads classes through. Class bytecode files (that is, binary information), if we follow the Java compilation system to organize the format and structure of. Class bytecode files, generate corresponding binary data, and then load and convert the binary data into corresponding classes. In this way, we have completed the dynamic creation of a class at run time. This idea is actually the idea of dynamic agent.

In the run time, the corresponding binary data is generated according to the organization rules of. class bytecode files in the JVM specification. At present, there are many open source frameworks that can complete this function, such as:

  • ASM
  • CGLIB
  • Javassist
  • ...

It should be noted that CGLIB is based on ASM. Here is a brief comparison between ASM and Javassist:

  • Javassist source level API s are easier to use than the actual bytecode operations in ASM
  • Javassist provides a higher level of abstraction on complex bytecode level operations. Javassist source code level API requires little or no actual bytecode knowledge, so it is easier and faster to implement.
  • Javassist uses a reflection mechanism, which makes it slower than ASM.

In general, ASM is much faster than Javassist and provides better performance, but Javassist is relatively easier to use, and both have their own advantages.

Taking Javassist as an example, let's take a look at the power of these frameworks to generate. class bytecode files at run time.

Normally, the code for creating a class is as follows:

package com.ktf;

public class Programmer {
	public void code(){
  		System.out.println("I'm a Programmer,Just Coding.....");
    }
}

Next, create as like as two peas of Programmer class by Javassist,

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;

public class MyGenerator {
    public static void main(String[] args) throws Exception {
    	ClassPool pool = ClassPool.getDefault();
        // Create Programmer class  
        CtClass cc= pool.makeClass("com.ktf.Programmer");
        // Definition method
        CtMethod method = CtNewMethod.make("public void code(){}", cc);
        // Insert method code
        method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
        cc.addMethod(method);
        // Save generated bytecode
        cc.writeFile("d://temp");
 	}
}

Open Programmer.class through the decompile tool to see the following code:

package com.ktf;

import java.io.PrintStream;

public class Programmer {
	public void code(){
  		System.out.println("I'm a Programmer,Just Coding.....");
    }
}

We found it as like as two peas we created above.

Now that you know the Java bytecode generation framework, you can start learning about dynamic proxies.

We know from the static proxy that the proxy class adds some operations before and after calling the delegate class method. Different delegate classes lead to different proxy classes.

In order to create a general proxy class, we extract the action of calling the delegate class method and encapsulate it into a general processing class, so there is the InvocationHandler role (processing class) in the dynamic proxy.

Therefore, there is a processing class role between the proxy class and the delegate class. This role is mainly to make a unified call to the action of the proxy class calling the delegate class method, that is, the InvocationHandler handles the action of the proxy class calling the delegate class method. See the figure below:

From the perspective of JVM, dynamic agent dynamically generates. class bytecode files at runtime and loads them into the JVM. We have already mentioned this in the Java bytecode generation framework.

There are many ways to implement dynamic agents, such as:

  • JDK dynamic agent
  • CGLIB dynamic proxy
  • Javassit dynamic proxy
  • ...

Let's introduce them one by one.

JDK dynamic proxy mechanism

Use steps

Let's first look at the steps of using JDK dynamic proxy mechanism:

1) Define an interface (Subject)

2) Create a Real Subject to implement this interface

3) Create a processing class and implement the InvocationHandler interface, rewrite its invoke method (in the invoke method, use the reflection mechanism to call the methods of the delegate class, and customize some processing logic), and inject the delegate class into the processing class

public interface InvocationHandler {    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;}

The method has the following three parameters:

  • Proxy: proxy class object (see next step)
  • Method: remember the Method.invoke mentioned in reflection? That's it. We can call the method (reflection) of the delegate class through it
  • args: parameter list passed to delegate class method

4) Create Proxy object: create the Proxy object of the delegate class object through Proxy.newProxyInstance().

@CallerSensitivepublic static Object newProxyInstance(ClassLoader loader,                                      Class<?>[] interfaces,                                      InvocationHandler h)    throws IllegalArgumentException

This method requires three parameters:

  • ClassLoader
  • The interface array implemented by the delegate class needs to pass in at least one interface
  • The InvocationHandler instance called handles the interface method (that is, the instance of the class we created in step 3)

That is to say, when the Proxy object created through newProxyInstance() of Proxy class calls the method, it will actually call the invoke() method of the processing class that implements the InvocationHandler interface. You can customize the processing logic in the invoke() method, such as what to do before and after the method is executed. This invoke() Method will be reflected to call the method of the delegate class.

Code example

1) Define an interface (Subject)

public interface SmsService {    String send(String message);}

2) Create a Real Subject to implement this interface

public class SmsServiceImpl implements SmsService {    public String send(String message) {        System.out.println("send message:" + message);        return message;    }}

3) Create a processing class and implement the InvocationHandler interface, rewrite its invoke method (in the invoke method, use the reflection mechanism to call the methods of the delegate class, and customize some processing logic), and inject the delegate class into the processing class

import java.lang.reflect.InvocationHandler;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class DebugInvocationHandler implements InvocationHandler {        // Inject the delegate class into the processing class (here we use Object instead to facilitate extension) private final Object target; Public debuginvocationhandler (Object target) {this. Target = target;} / / override the invoke method @ override public Object invoke (Object proxy, method, Object [] args) throws invocationtargetexception, illegalaccessexception {/ / we can add our own operation system.out.println before calling the method( "Before method" + method. Getname()); Object result = method. Invoke (target, args); / / after calling the method, we can also add our own operation system.out.println ("after method" + method. Getname()); return result;}}

4) Define a factory class for creating Proxy objects: create Proxy objects of delegate class objects through Proxy.newProxyInstance()

public class JdkProxyFactory {    public static Object getProxy(Object target) {        return Proxy.newProxyInstance(                target.getClass().getClassLoader(),                target.getClass().getInterfaces(),                new DebugInvocationHandler(target)        );    }}

5) Actual use

SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());smsService.send("Java");

After running the above code, the console prints out:

before method sendsend message:Javaafter method send

CGLIB dynamic proxy mechanism

Use steps

One of the most fatal problems of JDK dynamic proxy is that it can only proxy the implementation class of an interface, and the proxy class can only proxy the methods implemented in the interface. If the implementation class has its own private method but not in the interface, the method cannot be used as a proxy.

To solve this problem, we can use CGLIB dynamic proxy mechanism.

As mentioned above, CGLIB (Code Generation Library) It is a Java bytecode generation framework based on ASM, which allows us to modify and dynamically generate bytecode at runtime. The principle is to generate a subclass through bytecode technology, intercept the calls of parent class methods in the subclass, and weave additional business logic. Key words: have you noticed, intercept! CGLIB introduces a new role, method interceptor meth Odinterceptor. Similar to the InvocationHandler in JDK, it is also used to realize the unified calling of methods. See the following figure:

Note: in addition, because CGLIB inherits, the proxied class cannot be modified by final.

Many well-known open source frameworks use CGLIB, such as the AOP module in Spring: if the target object implements the interface, JDK dynamic proxy is adopted by default, otherwise CGLIB dynamic proxy is adopted.

Let's look at the steps of using CGLIB dynamic agent:

1) First, create a Real Subject

2) Create a method interceptor to implement the interface MethodInterceptor, and override the intercept method. Intercept is used to intercept and enhance the methods of the delegate class (similar to the invoke method in JDK dynamic proxy InvocationHandler)

public interface MethodInterceptor extends Callback {    Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;}

The method has four parameters:

  • Object var1: delegate class object
  • Method var2: intercepted method (method to be enhanced in delegate class)
  • Object[] var3: method input parameter
  • MethodProxy var4: used to call the original method of the delegate class (the bottom layer is also through the reflection mechanism, but instead of Method.invoke, the MethodProxy.invokeSuper method is used)

3) Create Proxy object: create the Proxy object of the delegate class object through Enhancer.create()

public Object create() {    classOnly = false;    argumentTypes = null;    return createHelper();}    public Object create(Class[] argumentTypes, Object[] arguments) {    classOnly = false;    if (argumentTypes == null || arguments == null || argumentTypes.length != arguments.length) {     	throw new IllegalArgumentException("Arguments must be non-null and of equal length");    }    this.argumentTypes = argumentTypes;    this.arguments = arguments;    return createHelper();}

That is to say, when the proxy object created through create() of Enhancer class calls the method, it will actually call the intercept() method of the processing class that implements the MethodInterceptor interface. You can customize the processing logic in the intercept() method, such as what to do before and after the method is executed.

It can be found that the steps of CGLIB dynamic Proxy mechanism are similar to those of JDK dynamic Proxy mechanism. The core of CGLIB dynamic Proxy is method interceptor and Enhancer, while the core of JDK dynamic Proxy is processing classes InvocationHandler and Proxy.

Code example

1) First, create a Real Subject

public class AliSmsService {    public String send(String message) {        System.out.println("send message:" + message);        return message;    }}

2) Create a method interceptor to implement the interface MethodInterceptor, and override the intercept method

import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class DebugMethodInterceptor implements MethodInterceptor {    @Override    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {        // Before calling the method, we can add our own operation system. Out. Println ("before method" + method. Getname())// Call the delegate class's method Object object = methodProxy.invokeSuper(o, args) through reflection// After calling the method, we can also add our own operation system. Out. Println ("after method" + method. Getname()); return object;    }}

3) Create Proxy object: create the Proxy object of the delegate class object through Enhancer.create()

import net.sf.cglib.proxy.Enhancer;public class CglibProxyFactory {    public static Object getProxy(Class<?> clazz) {        // Create a dynamic agent enhancement class Enhancer enhancer = new Enhancer()// Set the class loader enhancer.setClassLoader(clazz.getClassLoader())// Set delegate class (set parent class) enhancer.setSuperclass(clazz)// Set the method interceptor enhancer.setCallback(new DebugMethodInterceptor())// Create proxy class return enhancer.create();}}

From setSuperclass, we can see why CGLIB is based on inheritance.

4) Actual use

AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);aliSmsService.send("Java");

After running the above code, the console prints out:

before method sendsend message:Javaafter method send

Comparison between JDK dynamic agent and CGLIB dynamic agent

1) JDK dynamic agent is based on the delegate class that implements the interface, and implements the agent through the interface; CGLIB dynamic proxy is based on the subclass that inherits the delegate class, and implements the proxy through the subclass.

2) JDK dynamic proxy can only proxy the classes that implement the interface, and can only enhance the existing methods in the interface; CGLIB can delegate classes that do not implement any interfaces.

3) In terms of their efficiency, JDK dynamic agent is more efficient in most cases. With the upgrade of JDK version, this advantage is more obvious.

In a word, Javassist dynamic proxy mechanism is also common. Like CGLIB, as a Java bytecode generation framework, Javassist naturally has the ability to dynamically create a class at runtime, so it is natural to implement dynamic proxy. Dubbo uses Javassit for dynamic proxy by default.

Application of dynamic agent

1) One design principle in the design pattern is the open close principle, that is, it is closed for modification and open for extension. We sometimes take over many previous codes in our work. The code logic is confusing, so it is difficult to modify the code. At this time, we can enhance the class through the agent.

2) When we use the RPC framework, the framework itself cannot know which interfaces and methods each business party wants to call in advance. At this time, you can use dynamic proxy to establish an intermediary for the client, which is also convenient for the framework to build logic. To some extent, it is also a manifestation of the loose coupling between the client code and the framework.

3) Spring's AOP mechanism also uses dynamic proxy, which will not be discussed in detail here.

summary

Comparison between static agent and dynamic agent

1) Flexibility: dynamic proxy is more flexible. It does not need to implement interfaces. It can implement classes directly, and it does not need to create a proxy class for each target class. In addition, in static proxy, once a new method is added to the interface, the target object and proxy object must be modified, which is very troublesome

2) JVM level: static agents turn interfaces, implementation classes, and agent classes into actual. Class bytecode files at compile time. The dynamic agent dynamically generates class bytecode at runtime and loads it into the JVM.

Well, that's all for our dynamic agent. Brother Jie has learned how to read it. Can't you?

quote:

https://mp.weixin.qq.com/s?__biz=MzI0NDc3ODE5OQ==&mid=2247485887&idx=1&sn=0d6cdfeda8f18612f301d5151e1292e2&chksm=e959dc07de2e5511b7eb06ca65f5028a0b19eaba1e27db9677df4544baf137739f46eb158f39&scene=178&cur_album_id=1683346627601743872#rd

Posted by erikhillis on Mon, 01 Nov 2021 17:20:06 -0700