Proxy mode (CGLIB and JDK)

1. Introduction

1. Agency cases in life

  • Housing agency: The client does not have housing information, so we need to find an agent to obtain the housing.
  • Product Purchasing Agency: We don’t have low-cost products, so we need to find agents to obtain low-cost products.

For consumers, they only need to care about their own products through purchasing agents, and do not need to pay attention to how to obtain the products, which enhances the

2. Application of proxy mode in java

  • Unified exception handling
  • My shoe
  • Spring's AOP usage principle
  • Logging framework etc.

3. What is proxy mode?

The agent pattern is one of the 23 design patterns and is a structural pattern. It means that an object itself does not perform actual operations, but obtains the results it wants through other objects. Therefore, the target object only needs to care about the details of its own implementation, and the function enhancement through proxy objects can expand the functions of the target object. This reflects a very important programming idea. You cannot modify the source code casually. If you need to modify the source code, you only need to use a proxy to expand the function.
Insert image description here

2. How to implement proxy

1 Introduction

Under normal circumstances, we define an interface, that is, define a specification, and then use a class to implement the interface. Then the client can use the basic functions by calling the implementation class. In the current situation, we do not directly call the implementation class, but implement the interface through a proxy class, and add pre-notification and post-notification to enhance the interface method, and then the client uses the functions defined by the interface through the proxy class
Insert image description here

Elemental composition:

  • Interface: Define behavior and specifications
  • Proxied class: is the target object
  • Agent class: function enhancement

2. Static proxy

Static proxies are a very common design pattern in Java. In this pattern, one class (proxy class) provides the same interface as another class (target class) and then forwards calls to the target class. Both the proxy class and the target class implement the same interface. A common use of this pattern is to add cross-aspect behavior such as logging, performance metrics, or security checks without modifying the code of the target class.

public interface IService {
    
    
    void doSomething();
}

public class RealService implements IService {
    
    
    public void doSomething() {
    
    
        // 实际业务逻辑
    }
}

public class ProxyService implements IService {
    
    
    private IService realService;

    public ProxyService(IService realService) {
    
    
        this.realService = realService;
    }

    public void doSomething() {
    
    
        // 在调用实际方法前可以执行一些操作
        System.out.println("Before doSomething...");
        realService.doSomething();
        // 在调用实际方法后也可以执行一些操作
        System.out.println("After doSomething...");
    }
}

In this example, RealService is our target class and ProxyService is our proxy class. ProxyService performs some additional operations before and after calling RealService's doSomething method. Static proxies have the following characteristics:

  • The proxy class and target class are determined at compile time and are static;
  • Both the proxy class and the target class implement the same interface, and the proxy class calls the implementation of the target class through the methods defined in the interface;
  • The user calls the method of the target class through the proxy class. The proxy class can add some additional processing before or after calling the method of the target class.

The main problem with static proxy is that if the interface defines many methods, then the proxy class needs to write forwarding code for each method, which is obviously very cumbersome. In addition, if the interface changes, the proxy class also needs to be modified, which increases the complexity of maintenance. This is why in Java, dynamic proxies (such as using Java's java.lang.reflect.Proxy or third-party libraries such as CGLIB) are often used to replace static proxies.

3. Dynamic proxy

Dynamic proxy in Java refers to the technology of dynamically generating proxy classes and creating proxy objects at runtime. Java's core library provides support for dynamic proxies, mainly in the java.lang.reflect package. Dynamic proxy is a convenient and powerful tool. Its main applications include interceptors, mock objects, database connections, and transaction management. Using Java's dynamic proxy requires defining an invocation handler. The invocation handler is a class that implements the java.lang.reflect.InvocationHandler interface, which only defines one method:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

This invoke method is called when a method is called on the proxy instance. It receives three parameters: the proxy object itself, the method object to be called on the proxy object, and the parameters passed when calling the method. This method can add additional processing logic before and after calling the target method. Here is an example of a simple dynamic proxy:

public interface IService {
    
    
    void doSomething();
}

public class RealService implements IService {
    
    
    public void doSomething() {
    
    
        System.out.println("Do something in RealService");
    }
}

public class MyInvocationHandler implements InvocationHandler {
    
    
    private Object target;

    public MyInvocationHandler(Object target) {
    
    
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        System.out.println("Before invoke method...");
        Object result = method.invoke(target, args);
        System.out.println("After invoke method...");
        return result;
    }
}

You can then create the proxy object using the newProxyInstance method of the Proxy class:

//一个是在代理对象上调用的方法对象
IService realService = new RealService();
//调用处理器
InvocationHandler handler = new MyInvocationHandler(realService);
//创建代理对象
IService proxyService = (IService) Proxy.newProxyInstance(
    IService.class.getClassLoader(),
    new Class<?>[]{
    
    IService.class},
    handler
);
proxyService.doSomething();  // 调用这个方法时,会触发 handler 的 invoke 方法

In this example, we created a proxy for RealService. When we call the doSomething method through the proxy, the invoke method of MyInvocationHandler will be called first. It should be noted that Java's dynamic proxy can only create proxies for interfaces, not classes. If you need to create a proxy for a class, you can use a third-party library such as CGLIB.

static proxy dynamic proxy
compile time The proxy class already exists at compile time Proxy classes are dynamically generated at runtime
code complexity You need to write a forwarding code for each method. If the interface has many methods, the amount of code will be large. Only one Invocation Handler needs to be written, and the amount of code is relatively small.
Code flexibility If the interface changes, the proxy class needs to be modified accordingly, making maintenance complicated. The calling processor usually only relies on the interface rather than the specific implementation. If the interface changes, there is generally no need to modify the code of the calling processor.
Method to realize Both the proxy class and the target class implement the same interface, and the proxy class directly calls the method of the target class. Using Java's Proxy class and InvocationHandler interface, the proxy object is dynamically generated by the JVM at runtime.
Application scenarios When you need to create a proxy for one or several specific classes or interfaces, static proxies are simpler and more straightforward. Dynamic proxies are a better choice when you need to create proxies for multiple or undefined classes or interfaces, or when you need more flexible and powerful proxy functions (such as interceptors, AOP)

3. Dynamic proxy in JDK

1 Introduction

package java.lang.reflect;

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

The InvocationHandler interface (used for method interception) defines a method: invoke. This method is called when a method is called on the proxy instance. The invoke method accepts three parameters:

  1. proxy: proxy instance. This is the instance of the proxy class that calls the invoke method. (Create proxy instance through newProxyInstance)
  2. method: Method instance representing the interface method called on the proxy instance. This can be used to obtain information about the called method, such as method name, parameter types, etc. (executing the target method)
  3. args: An array of objects containing the arguments passed in method calls on the proxy instance. If the interface method takes no parameters, it is null. (parameters of target method)

The return value of the invoke method is the return value of the proxy instance method call. That is, this method actually determines the behavior of the proxy method. If an exception is thrown during a method call on the proxy instance, the exception will be thrown by the invoke method. In actual use, you usually create a class that implements the InvocationHandler interface and write your custom logic in the invoke method, such as adding additional processing before and after method invocation.
In Java, InvocationHandler is an interface used to handle method invocations on proxy instances. It is the core interface of Java dynamic proxy mechanism

Java's java.lang.reflect.Proxy class is the core class for implementing dynamic proxy. During the working process of dynamic proxy, the Proxy class generates a proxy class and its object for the specified interface, and implements the dynamic invocation of the method by calling the InvocationHandler interface.

public class Proxy implements java.io.Serializable {
    
    
    private static final long serialVersionUID = -2222568056686623797L;
    
    private InvocationHandler h;
    
    protected Proxy(InvocationHandler h) {
    
    
        Objects.requireNonNull(h);
        this.h = h;
    }

    public static Class<?> getProxyClass(ClassLoader loader,
                                         Class<?>... interfaces)
        throws IllegalArgumentException
    {
    
    
        // ...
    }

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
    
    
        // ...
    }

    public static InvocationHandler getInvocationHandler(Object proxy)
        throws IllegalArgumentException
    {
    
    
        // ...
    }
}

The Proxy class provides the following main methods:

  1. getProxyClass(ClassLoader loader, Class<?>… interfaces): This method is used to generate the Class object of the proxy class of the specified interface. The parameter loader is the class loader, used to define the proxy class; interfaces is a set of interfaces, and the proxy class will implement these interfaces.
  2. newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h): This method is used to generate a proxy object for the specified interface. This method not only generates the Class object of the proxy class, but also creates an instance of this class. Parameter h is the implementation of the InvocationHandler interface, which defines the behavior of method invocation on the proxy object.
  3. getInvocationHandler(Object proxy): This method returns the invocation handler associated with the specified proxy object. The parameter proxy is a proxy object.

When using the Proxy class to generate a proxy object, you first need to provide a class that implements the InvocationHandler interface. The invoke method of this class defines the behavior of method invocation on the proxy object. Then, you can call the Proxy.newProxyInstance method to generate a proxy object. When a method is called on a proxy object, the call is forwarded to the invoke handler's invoke method.

2. Test

Create student class

public class Student {
    
    
    private String name;

    public Student(String name) {
    
    
        this.name = name;
    }
    public Student() {
    
    
    }

    public String getName() {
    
    
        return name;
    }

    @Override
    public String toString() {
    
    
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}

Create service interface

public interface StudentService {
    
    
    public void save();
    public Student query();
}

Implement the service interface (a class that needs a proxy - a class that needs to be enhanced)

public class IStudentServiceImpl implements StudentService {
    
    
    public void save() {
    
    
        System.out.println("Save student info");
    }

    public Student query() {
    
    
        System.out.println("查询成功");
        Student student=new Student("赵云");
        return student;
        
    }
}

Implement enhanced classes

public class DaoTrascation {
    
    
    public void before(){
    
    
        System.out.println("开启事务操作");
    }
    public void after(){
    
    
        System.out.println("关闭事务操作");
    }
}

Implement the InvocationHandler interface

public class TransactionHandler implements InvocationHandler {
    
    
    //增强类对象
    private DaoTrascation daoTrascation;
    //需要代理的目标对象
    private Object object;
    public TransactionHandler(DaoTrascation daoTrascation,Object object){
    
    
        this.daoTrascation=daoTrascation;
        this.object=object;
    }
    
    //代理对象要执行的方法,实现目标方法执行,和功能增强
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        Object ret=null;
        //判断当前方法是否是save方法,是才做事务操作
        if("save".equals(method.getName())){
    
    
            daoTrascation.before();
            ret=method.invoke(object,args);
            daoTrascation.after();
        }else {
    
    
            method.invoke(object,args);
        }
        return ret;
    }
}

Test code

public class MybeanTest {
    
    
    @Test
    public void testMyBean(){
    
    
        //增强类对象
        DaoTrascation trascation=new DaoTrascation();
        //目标类
        StudentService studentService= new IStudentServiceImpl();
        //方法拦截处理器
        TransactionHandler transactionHandler = new TransactionHandler(trascation,studentService);
        //获取代理实例对象
        StudentService studentService1= (StudentService) Proxy.newProxyInstance(
                IStudentServiceImpl.class.getClassLoader(),  //类加载器
                IStudentServiceImpl.class.getInterfaces(),   //目标类所实现的所有接口
                transactionHandler //方法拦截处理器
        );
        studentService1.save();
        studentService1.query();
    }
}

Insert image description here

3. Principle analysis

Combined with the above code, view the underlying layer through decompilation

Modify test class code

 @Test
    public void testMyBean(){
    
    
        //增强类对象
        DaoTrascation trascation=new DaoTrascation();
        //目标类
        StudentService studentService= new IStudentServiceImpl();
        //方法拦截处理器
        TransactionHandler transactionHandler = new TransactionHandler(trascation,studentService);
        //获取代理实例对象
        StudentService studentService1= (StudentService) Proxy.newProxyInstance(
                IStudentServiceImpl.class.getClassLoader(),
                IStudentServiceImpl.class.getInterfaces(),
                transactionHandler
        );
        studentService1.save(new Student());
        Student query = studentService1.query();
        saveProxyClass("/Users/jackchai/Desktop/自学笔记/java项目/SpringTest/Springtest/src");
    }
    //使用生成字节码学习使用
    private void  saveProxyClass(String path) {
    
    
        //生成的代理类的字节码文件
        byte[] $proxy1 = ProxyGenerator.generateProxyClass("$Proxy1", IStudentServiceImpl.class.getInterfaces());
        try(FileOutputStream fileOutputStream = new FileOutputStream(new File(path + "$Proxy1.class"));) {
    
    
            fileOutputStream.write($proxy1);
        }  catch (IOException e) {
    
    
            throw new RuntimeException(e);
        }

    }

Allow code to generate byte files

//代理对象首先实现了StudentService接口
ublic final class $Proxy1 extends Proxy implements StudentService {
    
    
//生成5个代理方法对象(因为Object对象中有一些方法)
    private static Method m1;
    private static Method m4;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    //构造函数
    public $Proxy1(InvocationHandler var1) throws  {
    
    
        super(var1);
    }
    //equals方法(Object方法)
    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);
        }
    }
    //StudentService的query方法
    public final Student query() throws  {
    
    
        try {
    
    
            return (Student)super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
    
    
            throw var2;
        } catch (Throwable var3) {
    
    
            throw new UndeclaredThrowableException(var3);
        }
    }
    //object的tostring方法
    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);
        }
    }
    StudentService的save方法
    public final void save(Student var1) throws  {
    
    
        try {
    
    
            super.h.invoke(this, m3, new Object[]{
    
    var1});
        } catch (RuntimeException | Error var3) {
    
    
            throw var3;
        } catch (Throwable var4) {
    
    
            throw new UndeclaredThrowableException(var4);
        }
    }
    //object的hashcode方法
    public final int hashCode() throws  {
    
    
        try {
    
    
        //invoke方法,代理对象为当前对象,代理方法为m0,参数为null
        //h是Proxy类的InvocationHandeler类的实例
            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"));
            m4 = Class.forName("Service.StudentService").getMethod("query");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("Service.StudentService").getMethod("save", Class.forName("pojo.Student"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
    
    
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
    
    
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

4. Schematic diagram

Insert image description here

4. Dynamic proxy in CGLIB

1 Introduction

CGLIB is a powerful, high-performance and high-quality third-party code generation library. This library is widely used by third-party frameworks such as Spring, Mybatis, and Hibernate to provide method interception operations. CGLIB is an open source project, its CGLIB source code address . The CGLIB agent mainly introduces a level of indirection to the object through the operation of bytecode to control access to the object. CGLIB is more powerful than the JDK dynamic proxy based on Java reflection. A flaw of JDK dynamic proxy is that it can only proxy interfaces and cannot proxy individual ordinary classes. CGLIB can solve this problem. (In fact, it is a supplement to JDK dynamic proxy)

2. Case

  • Import related packages
 <dependency>
          <groupId>cglib</groupId>
          <artifactId>cglib</artifactId>
          <version>2.2.2</version>
      </dependency>
  • Create target class
public class IStudentServiceImpl implements StudentService {
    
    
    public void save(Student student) {
    
    
        System.out.println("Save student info");
    }

    public Student query() {
    
    
        System.out.println("查询成功");
        Student student=new Student("赵云");
        return student;

    }
}
  • Create transaction operation
public class DaoTrascation {
    
    
    public void before(){
    
    
        System.out.println("开启事务操作");
    }
    public void after(){
    
    
        System.out.println("关闭事务操作");
    }
}
  • Perform transaction interception
public class CglibInterceptor implements MethodInterceptor {
    
    
    DaoTrascation daoTrascation;
    StudentService studentService;

    public CglibInterceptor(DaoTrascation daoTrascation, StudentService studentService) {
    
    
        this.daoTrascation = daoTrascation;
        this.studentService = studentService;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    
    
        daoTrascation.before();
        Object ret=methodProxy.invokeSuper(o,objects);
        daoTrascation.after();
        return ret;
    }
}
  • test
  @Test
    public void testMyBean(){
    
    
        DaoTrascation trascation = new DaoTrascation();

        //得到方法拦截器
        CglibInterceptor interceptor=new CglibInterceptor(trascation);
        //使用CGLIB框架生成目标类的子类(代理类)实现增强
        Enhancer enhancer = new Enhancer();
        //设置父类字节码
        enhancer.setSuperclass(IStudentServiceImpl.class);
        //设置拦截处理
        enhancer.setCallback(interceptor);
        StudentService service= (StudentService) enhancer.create();
        service.save(new Student());

    }

Insert image description here

3. Analysis of underlying principles

  • Modify test class
public void testMyBean(){
    
    
        //当Spring使用CGLIB创建代理对象时,CGLIB会生成字节码来创建新的类。这个 DebuggingClassWriter.DEBUG_LOCATION_PROPERTY
        // 属性实际上是一个开关,当你设置了这个属性,CGLIB在生成新的类时,会在指定的目录下生成相应的.class文件,这样就可以查看或者进一步
        // 分析这些自动生成的类了。
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/jackchai/Desktop/自学笔记/java项目/SpringTest/Springtest");
        DaoTrascation trascation = new DaoTrascation();
        //得到方法拦截器
        CglibInterceptor interceptor=new CglibInterceptor(trascation);
        //使用CGLIB框架生成目标类的子类(代理类)实现增强
        Enhancer enhancer = new Enhancer();
        //设置父类字节码
        enhancer.setSuperclass(IStudentServiceImpl.class);
        //设置拦截处理
        enhancer.setCallback(interceptor);
        StudentService service= (StudentService) enhancer.create();
        service.save(new Student());

    }

Insert image description here
The following is the proxy class file:

public class IStudentServiceImpl$$EnhancerByCGLIB$$8b9b66bd extends IStudentServiceImpl implements Factory {
    
    
 public final void save(Student var1) {
    
    
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
    
    
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
    
    
            var10000.intercept(this, CGLIB$save$0$Method, new Object[]{
    
    var1}, CGLIB$save$0$Proxy);
        } else {
    
    
            super.save(var1);
        }
    }
    public final Student query() {
    
    
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
    
    
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? (Student)var10000.intercept(this, CGLIB$query$1$Method, CGLIB$emptyArgs, CGLIB$query$1$Proxy) : super.query();
    }
   }
  • The proxy class inherits the proxy object IStudentServiceImpl and implements its methods, so this pattern is implemented using inheritance.
  • Then the interceptor method in the interceptor is called in both save and query to achieve enhancement.

Guess you like

Origin blog.csdn.net/qq_43456605/article/details/131076909
Recommended