Proxy mode and comparison of three common proxy types

Proxy mode

The proxy mode is a design mode that provides additional access to the target object, that is, accessing the target object through the proxy object, so that it can provide additional functional operations and expand the function of the target object without modifying the original target object

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

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

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

insert image description here

Proxy Pattern Classification

  • Static proxy : It has been implemented at compile time, and the proxy class is an actual class file after compilation

  • Dynamic proxy : dynamically generated at runtime, that is, there is no actual class file after compilation, but the class bytecode is dynamically generated at runtime and loaded into the JVM

  • JDK-based dynamic proxy (reflection)

  • Dynamic Proxy Based on CGLIB (Byte Code Technology)

static proxy

How to use:

1. Create an interface, and then create a proxied class to implement the interface and implement the abstract methods in the interface.

2. Then create a proxy class and make it also implement this interface.

3. Hold a reference to the proxy object in the proxy class , and then call the method of the object in the proxy class method.

public interface UserDao {
    
    
  void save();
}
public class UserDaoImpl implements UserDao {
    
    
    @Override
    public void save() {
    
    
        System.out.println("正在保存用户...");
    }
}
public class TransactionHandler implements UserDao {
    
    
    //目标代理对象
    private UserDao target;
    //构造代理对象时传入目标对象
    public TransactionHandler(UserDao target) {
    
    
        this.target = target;
    }
    @Override
    public void save() {
    
    
        //调用目标方法前的处理
        System.out.println("开启事务控制...");
        //调用目标对象的方法
        target.save();
        //调用目标方法后的处理
        System.out.println("关闭事务控制...");
    }
}
public class Main {
    
    
    public static void main(String[] args) {
    
    
        //新建目标对象
        UserDaoImpl target = new UserDaoImpl();
        //创建代理对象, 并使用接口对其进行引用
        UserDao userDao = new TransactionHandler(target);
        //针对接口进行调用
        userDao.save();
    }
}

It is easy to complete the proxy operation of a class by using the JDK static proxy. But JDKthe shortcomings of static proxy are also exposed: since the proxy can only serve one class, if there are many classes that need to be proxied, then a large number of proxy classes need to be written, which is cumbersome

JDK dynamic proxy

Five steps to use JDK dynamic proxy:

InvocationHandler1. Customize your own by implementing the interface InvocationHandler;
2. Proxy.getProxyClassObtain the dynamic proxy class;
3. Obtain the construction method of the proxy class through the reflection mechanism, and the method signature is getConstructor(InvocationHandler.class);
4. Obtain the proxy object through the constructor and pass the customized InvocationHandlerinstance object
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 {
    
    

    /** 目标对象 */
    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("------插入前置通知代码-------------");
        // 执行相应的目标方法
        Object rs = method.invoke(target,args);
        System.out.println("------插入后置处理代码-------------");
        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 {
    
    
        // =========================第一种==========================
        // 1、生成$Proxy0的class文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        // 2、获取动态代理类
        Class proxyClazz = Proxy.getProxyClass(IHello.class.getClassLoader(),IHello.class);
        // 3、获得代理类的构造函数,并传入参数类型InvocationHandler.class
        Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
        // 4、通过构造函数来创建动态代理对象,将自定义的InvocationHandler实例传入
        IHello iHello1 = (IHello) constructor.newInstance(new MyInvocationHandler(new HelloImpl()));
        // 5、通过代理对象调用目标方法
        iHello1.sayHello();

        // ==========================第二种=============================
        /**
         * Proxy类中还有个将2~4步骤封装好的简便方法来创建动态代理对象,
         *其方法签名为:newProxyInstance(ClassLoader loader,Class<?>[] instance, InvocationHandler h)
         */
        IHello  iHello2 = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), // 加载接口的类加载器
                new Class[]{
    
    IHello.class}, // 一组接口
                new MyInvocationHandler(new HelloImpl())); // 自定义的InvocationHandler
        iHello2.sayHello();
    }
}

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

The difference: In static proxy, we need to create a proxy class for which interface and which proxy class, so we need the proxy class to implement the same interface as the proxy class before compiling, and call the proxy directly in the implemented method The corresponding method of the class; but the dynamic proxy is different, we don't know which interface and which proxy class to create the proxy class for, because it is created at runtime.

To sum up the difference between JDK static proxy and JDK dynamic proxy in one sentence:

JDK static proxies are created by direct coding, while JDKdynamic proxies use the reflection mechanism to create proxy classes at runtime.

In fact, in dynamic proxy, the core is InvocationHandler. Each proxy instance will have an associated invocation handler (InvocationHandler). When an invocation is made to the proxy instance, the invocation of the method will be encoded and assigned to its invocation handler (InvocationHandler) invokemethod

The call to the proxy object instance method is done through the invoke method in InvocationHandler, and the invoke method will determine which method to call the proxy according to the incoming proxy object, method name and parameters.

CGLIB dynamic proxy

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

The CGLIB proxy is implemented as follows:

  1. First implement a MethodInterceptor, method calls will be forwarded to the intercept () method of the class.
  2. Then, when it needs to be used, obtain the proxy object through the CGLIB dynamic proxy.

Use Cases

 public class HelloService {
    
    

    public HelloService() {
    
    
        System.out.println("HelloService构造");
    }

    /**
     * 该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的
     */
    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;

/**
 * 自定义MethodInterceptor
 */
public class MyMethodInterceptor implements MethodInterceptor{
    
    

    /**
     * sub:cglib生成的代理对象
     * method:被代理对象方法
     * objects:方法入参
     * methodProxy: 代理方法
     */
    @Override
    public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    
    
        System.out.println("======插入前置通知======");
        Object object = methodProxy.invokeSuper(sub, objects);
        System.out.println("======插入后者通知======");
        return object;
    }
}
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;

public class Client {
    
    
    public static void main(String[] args) {
    
    
        // 代理类class文件存入本地磁盘方便我们反编译查看源码
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
        // 通过CGLIB动态代理获取代理对象的过程
        Enhancer enhancer = new Enhancer();
        // 设置enhancer对象的父类
        enhancer.setSuperclass(HelloService.class);
        // 设置enhancer的回调对象
        enhancer.setCallback(new MyMethodInterceptor());
        // 创建代理对象
        HelloService proxy= (HelloService)enhancer.create();
        // 通过代理对象调用目标方法
        proxy.sayHello();
    }
}

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

The CGLIB dynamic proxy has no such mandatory requirements. Simply put, CGLIBthe generated proxy class will inherit the proxy class, and the proxy method will be strengthened in the proxy class (pre-processing, post-processing, etc.).

Summarize what work CGLIB has done when acting as a proxy

  • The generated proxy class inherits the proxy class. Here we need to pay attention to one point: if the delegate class is final modified, it cannot be inherited, that is, it cannot be proxied; similarly, if there is a final modified method in the delegate class, then the method cannot be proxied
  • The proxy class will generate two methods for the delegate method, one is a method with the same signature as the delegate method, which will call the delegate method in the method super; the other is a unique method of the proxy class
  • When executing the method of the proxy object, it will first judge whether there is an implementation of MethodInterceptorthe interface CGLIB$CALLBACK_0; if it exists, the method MethodInterceptorin the call will be calledintercept

In interceptthe method, in addition to calling the delegate method, we will also perform some enhanced operations. In Spring AOP, a typical application scenario is to log operations before and after the execution of certain sensitive methods

In CGLIB, the method call is not done through reflection, but the method is called directly: the Class object is specially processed through the FastClass mechanism , for example, the reference of the method will be saved in an array, and each time the method is called Both maintain a reference to the method through an index subscript

Fast class mechanism

CGLIB uses the mechanism of FastClass to implement the call to the intercepted method.

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

public class test10 {
    
    
  //这里,tt可以看作目标对象,fc可以看作是代理对象;首先根据代理对象的getIndex方法获取目标方法的索引,
  //然后再调用代理对象的invoke方法就可以直接调用目标类的方法,避免了反射
    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;
    }
    //这个方法对Test类中的方法建立索引
    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, and there are two methods getIndex and invoke in Test2.

Create an index for each method of Test in the getIndex method, and return the corresponding index according to the input parameters (method name + method descriptor).

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

Comparison among the three proxy methods

Here is a simple comparison of the three proxy methods:

Proxy accomplish advantage shortcoming features
JDK static proxy The proxy class implements the same interface as the delegate class, and the interface needs to be hardcoded in the proxy class Simple to implement and easy to understand Proxy classes require hard-coded interfaces, which may lead to repeated coding, waste of storage space and low efficiency in practical applications seems to have no features
JDK dynamic proxy The proxy class and the delegate class implement the same interface, mainly through the proxy class to implement InvocationHandler and rewrite invokethe method to perform dynamic proxy, and the method will be enhanced in the invoke method No hard-coded interface is required, and the code reuse rate is high Only delegate classes that implement the interface can be proxied The bottom layer uses the reflection mechanism to call the method
CGLIB dynamic proxy superThe proxy class takes the delegate class as its own parent class and creates two methods for the non-final delegate method in it, one is a method with the same signature as the delegate method, which will call the delegate method in the method; the other is unique to the proxy class Methods. In the proxy method, it will judge whether there is MethodInterceptoran object that implements the interface, and if it exists, it will call the intercept method to proxy the delegate method The class or interface can be enhanced at runtime, and the delegate class does not need to implement the interface Cannot finalproxy classes and final methods The bottom layer stores all the methods in an array, and directly calls the method through the array index

common problem

CGlib faster than JDK?

  • Use CGLib to implement dynamic proxy. The bottom layer of CGLib adopts ASM bytecode generation framework, and bytecode technology is used to generate proxy classes. Before jdk6, it is more efficient than using Java reflection. The only thing to note is that CGLib cannot proxy methods declared as final, because the principle of CGLib is to dynamically generate subclasses of the proxy class.
  • After jdk6, jdk7, and jdk8 gradually optimize the JDK dynamic proxy, the JDK proxy efficiency is higher than the CGLIB proxy efficiency when the number of calls is small. Only when a large number of calls are made, jdk6 and jdk7 are less efficient than CGLIB agents, but when it comes to jdk8, the efficiency of jdk agents is higher than that of CGLIB agents. In short, every time the jdk version is upgraded, the efficiency of jdk agents is improved. The news is a little out of step.

How does Spring choose to use JDK or CGLIB?

  • When the bean implements the interface, Spring will use the JDK's dynamic proxy.
  • When a bean does not implement an interface, Spring uses the CGlib implementation.
  • Can be forced to use CGlib

Guess you like

Origin blog.csdn.net/xiaozhengN/article/details/132179626