Master these three proxy modes and march towards Spring AOP!

Agent mode definition

First, let's take a look at the proxy mode:

The so-called proxy mode means that the client does not directly call the actual object (RealSubject in the lower right corner of the figure below), but indirectly calls the actual object by calling the proxy (ProxySubject).

The proxy mode is generally used because the client does not want to directly access the actual object, or there are technical obstacles to access the actual object, so the proxy object is used as a bridge to complete indirect access.

Business scene

First, there is a UserService interface, and there is a method to add users in the interface

public interface UserService {
    void addUser();
}

This is its implementation class

public class UserServiceImpl implements UserService {
    @Override
    public void addUser() {
        System.out.println("添加一个用户");
    }
}

Now you need to record a log when adding users. Of course, you can write the code to add logs directly in addUser,

    public void addUser() {
        System.out.println("添加一个用户");
	System.out.println("拿个小本本记一下");
    }

However, Java promotes the single responsibility principle. If it is written in this way, this principle is violated. We need to decouple the code for adding logs and let the addUser() method focus on writing our own business logic.

Static proxy

According to the class diagram, create a static proxy class

public class UserStaticProxy implements UserService{
    private UserService userService;
    public UserStaticProxy(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void addUser() {
        userService.addUser();
        System.out.println("拿个小本本记录一下");
    }
}

We create a test class to test the static proxy:

public class Test {

    public static void main(String[] args) {
        UserStaticProxy userStaticProxy = new UserStaticProxy(new UserServiceImpl());
        userStaticProxy.addUser();
    }
}

operation result:

In this way, a static proxy class is created. We can focus on writing business logic in Service, adding logs and other non-business logic to this static proxy class to complete.

Disadvantages of static proxy

Disadvantage 1: Add methods to the interface, and the proxy class needs to be maintained synchronously

As the business expands, there are no addUser methods in the UserService class, as well as updateUser, deleteUser, batchUpdateUser, batchDeleteUser and other methods, all of which need to be logged.

The UserServiceImpl class is as follows:

public class UserServiceImpl implements UserService {
    @Override
    public void addUser() {
        System.out.println("添加一个用户");
    }

    @Override
    public void updateUser() {
        System.out.println("更新一个用户");
    }

    @Override
    public void deleteUser() {
        System.out.println("删除一个用户");
    }

    @Override
    public void batchUpdateUser() {
        System.out.println("批量更新用户");
    }

    @Override
    public void batchDeleteUser() {
        System.out.println("批量删除用户");
    }
}

Then the corresponding static proxy class is as follows:

public class UserStaticProxy implements UserService{
    private UserService userService;
    public UserStaticProxy(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void addUser() {
        userService.addUser();
        System.out.println("拿个小本本记录一下");
    }

    @Override
    public void updateUser() {
        userService.updateUser();
        System.out.println("拿个小本本记录一下");
    }

    @Override
    public void deleteUser() {
        userService.deleteUser();
        System.out.println("拿个小本本记录一下");
    }

    @Override
    public void batchUpdateUser() {
        userService.batchUpdateUser();
        System.out.println("拿个小本本记录一下");
    }

    @Override
    public void batchDeleteUser() {
        userService.batchDeleteUser();
        System.out.println("拿个小本本记录一下");
    }
}

From the above we can see that there are a lot of duplicate log codes in the proxy class. Because the proxy class and the target object implement the same interface, once methods are added to the interface, the proxy class also has to add methods synchronously and has to add repeated additional function codes synchronously, increasing the amount of code

Disadvantage 2: The more interfaces, the more agents

If you need to add business classes, such as StudentService, TeacherService, etc., the methods in these classes also need to implement the method of increasing the log, then you need to create the corresponding proxy class synchronously. In addition, static proxy classes are not automatically generated and need to be written before compilation. If the business becomes larger and larger, more and more proxy classes are created, which increases the amount of code.

How to solve these shortcomings? At this time, the dynamic proxy method is needed

JDK dynamic proxy

In fact, the essence of dynamic proxy and static proxy is the same. In the end, a proxy object instance needs to be generated when the program is run, and related enhancements and business logic are completed through it, but the static proxy needs to be specified in a hard-coded manner, and the dynamic proxy supports operation This implementation is generated dynamically at the time.

The JDK itself helps us implement dynamic proxy, just use the newProxyInstance method:

 public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

Note that this method is a static method in the Proxy class, and the three parameters received are in order:

  • ClassLoader loader,: Specify the current target object to use the class loader
  • Class<?>[] interfaces,: The list of interfaces that the proxy class needs to implement
  • InvocationHandler h: call the handler, dispatch the method of the target object to the call handler

Code example:

public class DynamicProxy implements InvocationHandler {

    private Object target; // 目标对象

    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(target, args);
        System.out.println("拿个小本本记录一下");
        return result;
    }

}

The above invoke method is responsible for enhancing the method of the target object, and all methods of the interface class will use this invoke method. In addition, the bind method simply encapsulates the proxy method newProxyInstance of the JDK and is responsible for returning the interface class.

Test category:

 public static void main(String[] args) {
        DynamicProxy dynamicProxy = new DynamicProxy();
        UserService userService = (UserService)dynamicProxy.bind(new UserServiceImpl());
        userService.addUser();
        userService.updateUser();
    }

The results are as follows:

As shown in the figure, all methods in the UserService interface have added logging logic. In addition, let's take a look at the target attribute in the UserDynamicProxy class is of type Object. Therefore, this dynamic proxy method can also be reused for other services. It can be called like this:

DynamicProxy dynamicProxy = new DynamicProxy();
TeacherService teacherService = (TeacherService)dynamicProxy.bind(new TeacherServiceImpl());

In summary, dynamic proxy solves the shortcomings of static proxy

Use arthas to view the classes generated by the JDK dynamic proxy

The dynamic agent generates the agent class dynamically at runtime. This class is stored in memory. How can we see this class?

artias is a brilliant Java diagnostic tool developed by Ali open source. If you don’t understand, you can read this article http://www.dblearn.cn/article/5. You can use it to decompile the code online.

Here we add a breakpoint:

public static void main(String[] args) throws IOException {
        DynamicProxy dynamicProxy = new DynamicProxy();
        UserService userService = (UserService)dynamicProxy.bind(new UserServiceImpl());
        userService.addUser();
        userService.updateUser();
        System.in.read();
    }

Run arthas

Use the jad command to decompile, the proxy classes generated by java are all in the com.sun.proxy directory. So the decompilation command is as follows

jad com.sun.proxy.$Proxy0

package com.sun.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.UserService;

public final class $Proxy0 extends Proxy implements UserService {
    private static Method m1;
    private static Method m6;
    private static Method m2;
    private static Method m7;
    private static Method m0;
    private static Method m3;
    private static Method m4;
    private static Method m5;

    public final void addUser() {
        try {
            this.h.invoke(this, m3, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void updateUser() {
        try {
            this.h.invoke(this, m4, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void deleteUser() {
        try {
            this.h.invoke(this, m5, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void batchUpdateUser() {
        try {
            this.h.invoke(this, m6, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void batchDeleteUser() {
        try {
            this.h.invoke(this, m7, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m6 = Class.forName("proxy.UserService").getMethod("batchUpdateUser", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m7 = Class.forName("proxy.UserService").getMethod("batchDeleteUser", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            m3 = Class.forName("proxy.UserService").getMethod("addUser", new Class[0]);
            m4 = Class.forName("proxy.UserService").getMethod("updateUser", new Class[0]);
            m5 = Class.forName("proxy.UserService").getMethod("deleteUser", new Class[0]);
            return;
        }
        catch (NoSuchMethodException noSuchMethodException) {
            throw new NoSuchMethodError(noSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException classNotFoundException) {
            throw new NoClassDefFoundError(classNotFoundException.getMessage());
        }
    }

    public final boolean equals(Object object) {
        try {
            return (Boolean)this.h.invoke(this, m1, new Object[]{object});
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String toString() {
        try {
            return (String)this.h.invoke(this, m2, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int hashCode() {
        try {
            return (Integer)this.h.invoke(this, m0, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
}

From the above code, we can see that our proxy class has been generated. When we call a method such as addUser(), it is actually dispatched to the invoke method of the h variable for execution:

this.h.invoke(this, m3, null);

What is the h variable? In fact, we have implemented the DynamicProxy class of InvocationHandler.

cglib dynamic proxy

By observing the above static proxy and JDK dynamic proxy modes, it is found that the target object is required to implement an interface , but sometimes the target object is just a single object and does not implement any interface. What to do at this time? The following leads to the famous CGlib dynamic agent

The cglib agent, also called the subclass agent, is to construct a subclass object in memory to realize the expansion of the target object function.

To use cglib, you need to import its jar package, because spring has integrated it, so you can import the spring package

Write the proxy class:

public class CGLibProxy implements MethodInterceptor {
    private Object target; // 目标对象
    public Object bind(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(this.target.getClass());
        //设置回调函数
        enhancer.setCallback(this);
        //创建子类(代理对象)
        return enhancer.create();
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object result = methodProxy.invokeSuper(obj, args);
        System.out.println("拿个小本本记录一下");
        return result;
    }
}

Among them, Enhancer needs to set the target object as the parent class (because the generated proxy class needs to inherit the target object)

Test category:

 public static void main(String[] args) throws IOException {
        CGLibProxy cgLibProxy = new CGLibProxy();
        UserServiceImpl userService = (UserServiceImpl)cgLibProxy.bind(new UserServiceImpl());
        userService.addUser();
        userService.updateUser();
        System.in.read();
    }

operation result:

We see that it has been successfully represented. However, garbled characters appeared in the result. Here is a // TODO. I guess it is caused by Spring's re-encapsulation of CGlib. Please also give me a big answer if you know.

Use arthas to view the classes generated by the cglib dynamic proxy

The steps are the same as the JDK proxy class, except that the proxy class of cglib is generated in the same package as the test class. Due to too much code, only part of the code

package com.example.demo.proxy;

import com.example.demo.proxy.UserServiceImpl;
import java.lang.reflect.Method;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.cglib.core.Signature;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

public class UserServiceImpl$$EnhancerByCGLIB$$3ca8cfc3
extends UserServiceImpl
implements Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$deleteUser$0$Method;
    private static final MethodProxy CGLIB$deleteUser$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$addUser$1$Method;
    private static final MethodProxy CGLIB$addUser$1$Proxy;
    private static final Method CGLIB$updateUser$2$Method;
    private static final MethodProxy CGLIB$updateUser$2$Proxy;
    private static final Method CGLIB$batchUpdateUser$3$Method;
    private static final MethodProxy CGLIB$batchUpdateUser$3$Proxy;
    private static final Method CGLIB$batchDeleteUser$4$Method;
    private static final MethodProxy CGLIB$batchDeleteUser$4$Proxy;
    private static final Method CGLIB$equals$5$Method;
    private static final MethodProxy CGLIB$equals$5$Proxy;
    private static final Method CGLIB$toString$6$Method;
    private static final MethodProxy CGLIB$toString$6$Proxy;
    private static final Method CGLIB$hashCode$7$Method;
    private static final MethodProxy CGLIB$hashCode$7$Proxy;
    private static final Method CGLIB$clone$8$Method;
    private static final MethodProxy CGLIB$clone$8$Proxy;

    public final void deleteUser() {
        MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
        if (methodInterceptor == null) {
            UserServiceImpl$$EnhancerByCGLIB$$3ca8cfc3.CGLIB$BIND_CALLBACKS(this);
            methodInterceptor = this.CGLIB$CALLBACK_0;
        }
        if (methodInterceptor != null) {
            Object object = methodInterceptor.intercept(this, CGLIB$deleteUser$0$Method, CGLIB$emptyArgs, CGLIB$deleteUser$0$Proxy);
            return;
        }
        super.deleteUser();
    }

    public final void addUser() {
        MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
        if (methodInterceptor == null) {
            UserServiceImpl$$EnhancerByCGLIB$$3ca8cfc3.CGLIB$BIND_CALLBACKS(this);
            methodInterceptor = this.CGLIB$CALLBACK_0;
        }
        if (methodInterceptor != null) {
            Object object = methodInterceptor.intercept(this, CGLIB$addUser$1$Method, CGLIB$emptyArgs, CGLIB$addUser$1$Proxy);
            return;
        }
        super.addUser();
    }

    public final void updateUser() {
        MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
        if (methodInterceptor == null) {
            UserServiceImpl$$EnhancerByCGLIB$$3ca8cfc3.CGLIB$BIND_CALLBACKS(this);
            methodInterceptor = this.CGLIB$CALLBACK_0;
        }
        if (methodInterceptor != null) {
            Object object = methodInterceptor.intercept(this, CGLIB$updateUser$2$Method, CGLIB$emptyArgs, CGLIB$updateUser$2$Proxy);
            return;
        }
        super.updateUser();
    }

among them

public class UserServiceImpl E n h a n c e r B y C G L I B EnhancerByCGLIB EnhancerByCGLIB3ca8cfc3 extends UserServiceImpl

You can see that the generated proxy class inherits the target object, so there are two points to note:

  1. The target object cannot be modified by the final keyword, because the object modified by the final is not inheritable.
  2. If the method of the target object is final/static, it will not be intercepted, that is, the additional business methods of the target object will not be executed.

Guess you like

Origin blog.csdn.net/doubututou/article/details/109299388