Personal understanding of Java dynamic agent

Static proxy mode

The above is a simple static proxy implementation class diagram.

Use the proxy mode to control the program's access to the RealSubject object . If abnormal access is found, you can directly limit the flow or return, or you can perform related pre-processing and post-processing before and after the business processing is performed to help the upper caller shield the bottom layer The details. For example, in the RPC framework, the proxy can complete a series of operations such as serialization, network I/O operations, load balancing, fault recovery, and service discovery, while the upper caller only perceives a local call.

The proxy mode can also be used to implement the function of lazy loading . We know that querying the database is a time-consuming operation, and sometimes the data queried is not actually used by the program. The lazy loading function can effectively avoid this waste. When the system accesses the database, it can first obtain a proxy object. At this time, no database query operation is performed. Naturally, there is no real data in the proxy object; when the system really needs to use the data When, call the proxy object to complete the database query and return the data. The principle of lazy loading in common ORM frameworks (for example, MyBatis, Hibernate) is roughly the same.

In addition, the proxy object can coordinate the relationship between the real RealSubject object and the caller, and achieve the effect of decoupling to a certain extent.

JDK dynamic proxy

The implementation of the proxy mode described above is also called "static proxy mode", because it is necessary to create a Proxy class for each RealSubject class during the compilation phase. When there are many classes that need to be proxied, a large number of them will appear. Proxy class.

In this scenario, we can use the JDK dynamic proxy to solve this problem. The core of JDK dynamic proxy is the InvocationHandler interface . Here is a Demo implementation of InvocationHandler, the code is as follows:

public class DemoInvokerHandler implements InvocationHandler {
    private Object target; // 真正的业务对象,也就是RealSubject对象
    public DemoInvokerHandler(Object target) { // 构造方法
        this.target = target;
    }
    public Object invoke(Object proxy, Method method, Object[] args)
             throws Throwable {
        // ...在执行业务方法之前的预处理...
        Object result = method.invoke(target, args);
        // ...在执行业务方法之后的后置处理...
        return result;
    }
    public Object getProxy() {
        // 创建代理对象
        return Proxy.newProxyInstance(Thread.currentThread()
            .getContextClassLoader(),
                target.getClass().getInterfaces(), this);
    }
}

Next, we can create a main() method to simulate the upper caller, create and use a dynamic proxy:

public class Main {
    public static void main(String[] args) {
        Subject subject = new RealSubject();
        DemoInvokerHandler invokerHandler = new DemoInvokerHandler(subject);
        // 获取代理对象
        Subject proxy = (Subject) invokerHandler.getProxy();
        // 调用代理对象的方法,它会调用DemoInvokerHandler.invoke()方法
        proxy.operation();
    }
}

For business classes that require the same proxy logic, you only need to provide an InvocationHandler interface implementation class. During the running of Java, the JDK will dynamically generate the corresponding proxy class for each RealSubject class and load it into the JVM, then create the corresponding proxy instance object and return it to the upper caller.

The entry point of the JDK dynamic proxy implementation is the static method Proxy.newProxyInstance(). Its three parameters are the class loader for loading the dynamically generated proxy class, the interface implemented by the business class, and the InvocationHandler object introduced above. The specific implementation of the Proxy.newProxyInstance() method is as follows:

public static Object newProxyInstance(ClassLoader loader,
     Class[] interfaces, InvocationHandler h) 
         throws IllegalArgumentException {
    final Class<?>[] intfs = interfaces.clone();
    // ...省略权限检查等代码
    Class<?> cl = getProxyClass0(loader, intfs);  // 获取代理类
    // ...省略try/catch代码块和相关异常处理
    // 获取代理类的构造方法
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    final InvocationHandler ih = h;
    return cons.newInstance(new Object[]{h});  // 创建代理对象
}

Through the implementation of the newProxyInstance() method, it can be seen that the JDK dynamic proxy is to complete the generation and loading of the proxy class in the getProxyClass0() method. The specific implementation of the getProxyClass0() method is as follows:

private static Class getProxyClass0 (ClassLoader loader, 
        Class... interfaces) {
    // 边界检查,限制接口数量(略)
    // 如果指定的类加载器中已经创建了实现指定接口的代理类,则查找缓存;
    // 否则通过ProxyClassFactory创建实现指定接口的代理类
    return proxyClassCache.get(loader, interfaces);
}

proxyClassCache is a static field defined in the Proxy class. It is mainly used to cache the proxy classes that have been created. The definition is as follows:

private static final WeakCache[], Class> proxyClassCache
     = new WeakCache<>(new KeyFactory(), 
           new ProxyClassFactory());

The WeakCache.get() method will first try to find the proxy class from the cache. If it cannot find it, it will create a Factory object and call its get() method to obtain the proxy class. Factory is an internal class in WeakCache. The Factory.get() method will call the ProxyClassFactory.apply() method to create and load the proxy class.

The ProxyClassFactory.apply() method first detects the set of interfaces that the proxy class needs to implement, then determines the name of the proxy class, then creates the proxy class and writes it into the file, finally loads the proxy class, and returns the corresponding Class object for subsequent use Instantiate the proxy class object. The specific implementation of this method is as follows:

public Class apply(ClassLoader loader, Class[] interfaces) {
    // ... 对interfaces集合进行一系列检测(略)
    // ... 选择定义代理类的包名(略)
    // 代理类的名称是通过包名、代理类名称前缀以及编号这三项组成的
    long num = nextUniqueNumber.getAndIncrement();
    String proxyName = proxyPkg + proxyClassNamePrefix + num;
    // 生成代理类,并写入文件
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
    
    // 加载代理类,并返回Class对象
    return defineClass0(loader, proxyName, proxyClassFile, 0, 
      proxyClassFile.length);
}

The ProxyGenerator.generateProxyClass() method generates the bytecode of the proxy class according to the specified name and interface set, and decides whether to save it to the disk according to the conditions. The specific code of this method is as follows:

public static byte[] generateProxyClass(final String name,
       Class[] interfaces) {
    ProxyGenerator gen = new ProxyGenerator(name, interfaces);
    // 动态生成代理类的字节码,具体生成过程不再详细介绍,感兴趣的读者可以继续分析
    final byte[] classFile = gen.generateClassFile();
    // 如果saveGeneratedFiles值为true,会将生成的代理类的字节码保存到文件中
    if (saveGeneratedFiles) { 
        java.security.AccessController.doPrivileged(
            new java.security.PrivilegedAction() {
                public Void run() {
                    // 省略try/catch代码块
                    FileOutputStream file = new FileOutputStream(
                        dotToSlash(name) + ".class");
                    file.write(classFile);
                    file.close();
                    return null;
                }
            }
        );
    }
    return classFile; // 返回上面生成的代理类的字节码
}

Finally, in order to clearly see the real definition of the proxy class dynamically generated by the JDK, we need to decompile the bytecode of the proxy class generated above. The above example is the proxy class generated by RealSubject. The code obtained after decompilation is as follows:

public final class $Proxy37 
      extends Proxy implements Subject {  // 实现了Subject接口
    // 这里省略了从Object类继承下来的相关方法和属性
    private static Method m3;
    static {
        // 省略了try/catch代码块
        // 记录了operation()方法对应的Method对象
        m3 = Class.forName("com.xxx.Subject")
          .getMethod("operation", new Class[0]);
    }
    // 构造方法的参数就是我们在示例中使用的DemoInvokerHandler对象
    public $Proxy11(InvocationHandler var1) throws {
        super(var1); 
    }
    public final void operation() throws {
        // 省略了try/catch代码块
        // 调用DemoInvokerHandler对象的invoke()方法
        // 最终调用RealSubject对象的对应方法
        super.h.invoke(this, m3, (Object[]) null);
    }
}

So far, the basic use and core principles of the JDK dynamic proxy are introduced. To summarize briefly, the implementation principle of JDK dynamic proxy is to dynamically create proxy classes and load them through the specified class loader, and pass the InvocationHandler object as the construction parameter when creating the proxy object. When the proxy object is called, the InvocationHandler.invoke() method is called to execute the proxy logic and finally call the corresponding method of the real business object.

CGLib

JDK dynamic proxy is natively supported by Java and does not require any external dependencies. However, as analyzed above, it can only proxy based on interfaces. For classes that do not inherit any interfaces, JDK dynamic proxy is useless .

If you want to proxy a class that does not implement any interface, you can consider using CGLib.

CGLib (Code Generation Library) is an ASM-based bytecode generation library , which allows us to modify and dynamically generate bytecode at runtime. CGLib uses bytecode technology to implement dynamic proxy functions. The underlying principle is to generate a subclass for the target class through bytecode technology, and use method interception in the subclass to intercept all parent class method calls to achieve proxy Function.

Because CGLib implements dynamic proxying by generating subclasses, it cannot proxy methods modified by the final keyword (because final methods cannot be overridden). In this way, CGLib and JDK dynamic proxy can complement each other: when the target class implements the interface, use the JDK dynamic proxy to create the proxy object; when the target class does not implement the interface, use CGLib to implement the function of the dynamic proxy . In a variety of open source frameworks such as Spring and MyBatis, you can see the combined use of JDK dynamic proxy and CGLib.

The implementation of CGLib consists of two important members.

  • Enhancer : Specify the target object to be proxied and the object that actually handles the proxy logic. Finally, the proxy object is obtained by calling the create() method. All non-final method calls of this object will be forwarded to MethodInterceptor for processing.

  • MethodInterceptor : The method calls of the dynamic proxy object will be forwarded to the intercept method for enhancement.

The use of these two components is similar to the Proxy and InvocationHandler in the JDK dynamic proxy.

eg:

public class CglibProxy implements MethodInterceptor {
    // 初始化Enhancer对象
    private Enhancer enhancer = new Enhancer(); 
    
    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz); // 指定生成的代理类的父类
        enhancer.setCallback(this); // 设置Callback对象
        return enhancer.create(); // 通过ASM字节码技术动态创建子类实例
    }
    
    // 实现MethodInterceptor接口的intercept()方法
    public Object intercept(Object obj, Method method, Object[] args,
                   MethodProxy proxy) throws Throwable {
        System.out.println("前置处理");
        Object result = proxy.invokeSuper(obj, args); // 调用父类中的方法
        System.out.println("后置处理");
        return result;
    }
}  

// 这里是测试的类
public class CGLibTest { // 目标类
    public String method(String str) {   // 目标方法
        System.out.println(str);
        return "CGLibTest.method():" + str;
    }
    
    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        // 生成CBLibTest的代理对象
        CGLibTest proxyImp = (CGLibTest) 
            proxy.getProxy(CGLibTest.class);
        // 调用代理对象的method()方法
        String result = proxyImp.method("test");
        System.out.println(result);
    }
}

Javassist

Javassist is an open source class library that generates Java bytecode . Its main advantage is that it is simple and fast. You can directly use the Java API provided by Javassist to dynamically modify the structure of the class or dynamically generate the class.

The use of Javassist is relatively simple, let's first look at how to use the Java API provided by Javassist to dynamically create classes. The sample code is as follows:

public class JavassistMain {
    public static void main(String[] args) throws Exception {
        ClassPool cp = ClassPool.getDefault(); // 创建ClassPool
        // 要生成的类名称为com.test.JavassistDemo
        CtClass clazz = cp.makeClass("com.test.JavassistDemo");
    
        StringBuffer body = null;
        // 创建字段,指定了字段类型、字段名称、字段所属的类
        CtField field = new CtField(cp.get("java.lang.String"), 
            "prop", clazz);
        // 指定该字段使用private修饰
        field.setModifiers(Modifier.PRIVATE);
    
        // 设置prop字段的getter/setter方法
        clazz.addMethod(CtNewMethod.setter("getProp", field));
        clazz.addMethod(CtNewMethod.getter("setProp", field));
        // 设置prop字段的初始化值,并将prop字段添加到clazz中
        clazz.addField(field, CtField.Initializer.constant("MyName"));
    
        // 创建构造方法,指定了构造方法的参数类型和构造方法所属的类
        CtConstructor ctConstructor = new CtConstructor(
            new CtClass[]{}, clazz);
        // 设置方法体
        body = new StringBuffer();
        body.append("{\n prop=\"MyName\";\n}");
        ctConstructor.setBody(body.toString());
        clazz.addConstructor(ctConstructor); // 将构造方法添加到clazz中
    
        // 创建execute()方法,指定了方法返回值、方法名称、方法参数列表以及
        // 方法所属的类
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "execute",
                new CtClass[]{}, clazz);
        // 指定该方法使用public修饰
        ctMethod.setModifiers(Modifier.PUBLIC);
        // 设置方法体
        body = new StringBuffer();
        body.append("{\n System.out.println(\"execute():\" " +
                "+ this.prop);");
        body.append("\n}");
        ctMethod.setBody(body.toString());
        clazz.addMethod(ctMethod); // 将execute()方法添加到clazz中
        // 将上面定义的JavassistDemo类保存到指定的目录
        clazz.writeFile("/Users/xxx/"); 
        // 加载clazz类,并创建对象
        Class<?> c = clazz.toClass();
        Object o = c.newInstance();
        // 调用execute()方法
        Method method = o.getClass().getMethod("execute", 
          new Class[]{});
        method.invoke(o, new Object[]{});
    }
}

After executing the above code, you can find the generated JavassistDemo.class file in the specified directory, decompile it, and get the JavassistDemo code as follows:

public class JavassistDemo {
    private String prop = "MyName";
    public JavassistDemo() {
        prop = "MyName";
    }
    public void setProp(String paramString) {
        this.prop = paramString;
    }
    public String getProp() {
        return this.prop;
    }
    public void execute() {
        System.out.println("execute():" + this.prop);
    }
}

Javassist can also implement dynamic proxy functions, and the underlying principles are also implemented by creating subclasses of the target class. Here we use Javassist to create a proxy object for the JavassitDemo generated above. The specific implementation is as follows:

public class JavassitMain2 {
    public static void main(String[] args) throws Exception {
        ProxyFactory factory = new ProxyFactory();
        // 指定父类,ProxyFactory会动态生成继承该父类的子类
        factory.setSuperclass(JavassistDemo.class);
        // 设置过滤器,判断哪些方法调用需要被拦截
        factory.setFilter(new MethodFilter() {
            public boolean isHandled(Method m) {
                if (m.getName().equals("execute")) {
                    return true;
                }
                return false;
            }
        });
        // 设置拦截处理
        factory.setHandler(new MethodHandler() {
            @Override
            public Object invoke(Object self, Method thisMethod, 
                Method proceed, Object[] args) throws Throwable {
                System.out.println("前置处理");
                Object result = proceed.invoke(self, args);
                System.out.println("执行结果:" + result);
                System.out.println("后置处理");
                return result;
            }
        });
        // 创建JavassistDemo的代理类,并创建代理对象
        Class<?> c = factory.createClass();
        JavassistDemo JavassistDemo = (JavassistDemo) c.newInstance();
        JavassistDemo.execute(); // 执行execute()方法,会被拦截
        System.out.println(JavassistDemo.getProp());
    }
}

The basic knowledge of Javassist is introduced here. Javassist can directly use Java language strings to generate classes, which is relatively easy to use. Javassist also has better performance, which is Dubbo's default proxy generation method .

 

Guess you like

Origin blog.csdn.net/qq_32323239/article/details/109428092