Agency model

Agency model

If you don't accumulate steps, you can't reach a thousand miles.

Agent, as the name suggests, is to find someone to help me do things. Putting it in the program means that one object lets another object perform part of the logic for itself.

Now there is one

UserService
Interface, which provides a query and an update method.

public interface UserService { /** * User inquiry */ void query () ; /** * User update */ void update () ; } Copy code

Its implementation class

UserServiceImpl
as follows

public class UserServiceImpl implements UserService { @Override public void query () { System.out.println( "UserService performs user query" ); } @Override public void update () { System.out.println( "UserService performs user update" ); } } Copy code

Now consider a scenario where the current time needs to be printed before and after the method is executed. How to achieve this? Because it is a learning agent model, we skip the way of adding logic before and after the business code, and go straight to the topic.

Static proxy

Now there is an implementation class

UserServiceStaticProxy
And it also achieves
UserService
Interface, so he also implemented
query
and
update method
. Through the constructor, you will need
UserServiceStaticProxy
The object of help is injected in, so that you can do things for it later. When required, the current time is printed before and after the method is executed, so the proxy class is implemented
before
with
after
Method, used to output time. In the proxy class
query
with
update
In the method, the corresponding method of the proxy object is called, and the time input code is added before and after it. In this way, when the corresponding method of the proxy class is called, additional logic will be executed and the original method of the proxy object will be called to achieve the requirements.

public class UserServiceStaticProxy implements UserService { private UserService target; public UserServiceStaticProxy (UserService target) { this .target = target; } @Override public void query () { before(); target.query(); after(); } @Override public void update () { before(); target.update(); after(); } private void before () { System.out.println(String.format( "static proxy log start time [%s] " , new Date())); } private void after () { System.out.println(String.format( "static proxy log end time [%s] " , new Date())); } } Copy code

When calling, pass in the object that needs to be proxied, and call the method on the proxy class

public static void staticProxy () { UserService userService = new UserServiceImpl(); UserService proxyService = new UserServiceStaticProxy(userService); proxyService.query(); proxyService.update(); } Copy code

summary:

  • The static proxy needs to write its own proxy class --> the proxy class needs to implement the same interface as the target object
  • If the target interface has many methods, they need to be implemented one by one, which is more troublesome

Dynamic proxy

JDK dynamic proxy

achieve

InvocationHandler
, This is the logic that the proxy object needs to enhance. In the code below, we add the logic of time output before and after calling the proxy object.

public class LogHandler implements InvocationHandler { private Object target; public LogHandler (Object target) { this .target = target; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { before(); Object result = method.invoke(target, args); after(); return result; } private void before () { System.out.println(String.format( "jdk proxy log start time [%s] " , new Date())); } private void after () { System.out.println(String.format( "jdk proxy log end time [%s] " , new Date())); } } Copy code

Create proxy objects need to pass

java.lang.reflect.Proxy
of
newProxyInstance
method

This method receives three parameters:

  • Parameter 1: Which class loader is used to generate the proxy object [Generally we use the loader of the proxy class]
  • Parameter 2: Generate the proxy object of which object, and specify through the interface [Specify the interface of the class to be proxied]
  • Parameter 3: What is going on in the method of the generated proxy object [implement the handler interface, we can implement it as we want]

Before writing a dynamic agent, a few concepts should be clarified:

  • The proxy object has the same methods as the target object [because the second parameter specifies the interface of the object, the proxy object will implement all the methods of the interface]
  • What method the user calls the proxy object is all calling the invoke method of the processor. [Intercepted]
  • Use JDK dynamic proxy must have an interface [parameter 2 requires an interface]

The code snippet below is a simple process of getting the proxy object and calling it

public static void jdkProxy () { //1. Get the proxy object UserService userService = new UserServiceImpl(); //2. Get the class loader used by the proxy object ClassLoader classLoader = userService.getClass().getClassLoader(); //3. Get the proxy interface Class[] interfaces = userService.getClass().getInterfaces(); //4. The implementation needs to enhance the proxy object, and implement the InvocationHandler interface InvocationHandler localHandler = new LogHandler(userService); //Get the proxy object UserService proxyService = (UserService) Proxy.newProxyInstance(classLoader, interfaces, localHandler); //Method call through proxy object proxyService.query(); proxyService.update(); ProxyUtils.generateClassFile(userService.getClass(), "UserServiceProxy" ); } Copy code

The generation of the proxy object in this example uses the JDKAPI to dynamically build the proxy object in memory (we need to specify the type of interface implemented by the proxy object/target object), and all methods of the interface will be implemented by default .

Below

UserServiceProxy
The class is the proxy object we generate

  • UserServiceProxy
    inherit
    java.lang.reflect.Proxy
    Class, and implements all the interfaces that are proxied, as well as equals, hashCode, toString and other methods
  • due to
    UserServiceProxy
    inherit
    java.lang.reflect.Proxy
    Class, so each proxy class will be associated with a
    InvocationHandler
    Method call handler
  • Class and all methods are
    public final
    Modification, so the proxy class can only be used, and can no longer be inherited
  • Each method has a Method object to describe, and the Method object is created in a static static code block to
    m + number
    Format naming
  • Pass when calling the method
    super.h.invoke(this, m1, (Object[])null);
    Call, where
    super.h.invoke
    Is actually passed to
    Proxy.newProxyInstance
    of
    LogHandler
    Object, it implements
    InvocationHandler
    interface,
    LogHandler
    After receiving the method, args and other parameters, the invoke method executes the enhanced logic and makes the proxy target execute the corresponding method through reflection
// //Source code recreated from a .class file by IntelliJ IDEA //(powered by FernFlower decompiler) // import design.pattern.proxy.service.UserService; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class UserServiceProxy extends Proxy implements UserService { private static Method m1; private static Method m4; private static Method m2; private static Method m0; private static Method m3; public UserServiceProxy (InvocationHandler var1) throws { super (var1); } 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); } } public final void query () throws { try { super .h.invoke( this , m4, (Object[]) null ); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } 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); } } public final int hashCode () throws { try { return (Integer) super .h.invoke( this , m0, (Object[]) null ); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void update () throws { try { super .h.invoke( this , m3, (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( "design.pattern.proxy.service.UserService" ).getMethod( "query" ); m2 = Class.forName( "java.lang.Object" ).getMethod( "toString" ); m0 = Class.forName( "java.lang.Object" ).getMethod( "hashCode" ); m3 = Class.forName( "design.pattern.proxy.service.UserService" ).getMethod( "update" ); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } } Copy code
CGLIB dynamic proxy

Existing user information access controller

UserController
, It is also necessary to add the demand for output time.

public class UserController { public String query () { System.out.println( "UserController performs user query" ); return "result" ; } public Boolean update () { System.out.println( "UserController performs user update" ); return false ; } } Copy code

Since Controller does not implement the interface, JDK dynamic proxy cannot be used, so CGLIB dynamic proxy is used here to achieve this requirement. The processing mode of CGLIB is as follows:

  1. Find all non-final public type method definitions on the target class;
  2. Convert the definition of these methods into bytecode;
  3. Convert the composed bytecode into the corresponding proxy class object;
  4. achieve
    MethodInterceptor
    Interface, used to process requests for all methods on the proxy class
public static void cglibProxy () { LogInterceptor logInterceptor = new LogInterceptor(); Enhancer enhancer = new Enhancer(); //Set the super class, cglib is implemented through inheritance enhancer.setSuperclass(UserController.class); //Set the interceptor, which is the enhanced logic implemented enhancer.setCallback(logInterceptor); //Create proxy class UserController userControllerProxy = (UserController) enhancer.create(); userControllerProxy.query(); userControllerProxy.update(); } Copy code

Interceptor implementation

public class LogInterceptor implements MethodInterceptor { /** * @param object indicates the object to be enhanced * @param method indicates the method of interception * @param objects array represents the parameter list, and the basic data type needs to be passed in its packaging type, such as int-->Integer, long-Long, double-->Double * @param methodProxy represents the proxy of the method, and the invokeSuper method represents the call to the method of the proxy object * @return execution result * @throws Throwable */ @Override public Object intercept (Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { before(); Object result = methodProxy.invokeSuper(object, args); after(); return null ; } private void before () { System.out.println(String.format( "cglib proxy log start time [%s] " , new Date())); } private void after () { System.out.println(String.format( "cglib proxy log end time [%s] " , new Date())); } } Copy code
summary

JDK dynamic proxy: Based on the Java reflection mechanism, the business class that implements the interface must be used to generate proxy objects in this way.

CGLIB dynamic proxy: Based on the ASM mechanism, the subclass of the business class is generated as the proxy class.

Advantages of JDK dynamic proxy:

  • Minimizing dependencies and reducing dependencies means simplifying development and maintenance. The support of JDK itself may be more reliable than CGLIB.
  • Smoothly upgrade the JDK version, and the bytecode library usually needs to be updated to ensure that it can be used on the new version of Java.
  • The code is simple to implement.

Based on the advantages of a similar CGLIB framework:

  • No need to implement the interface, to achieve the proxy class without intrusion
  • Only operate the classes we care about, without having to increase the workload for other related classes.
  • high performance

application:

Spring AOP is based on dynamic proxy. If the object to be proxied implements a certain interface, then Spring AOP will make

Use JDK Proxy to create proxy objects. For objects that have not been connected, you cannot use JDK Proxy to proxy

After all, Spring AOP will use CGLIB at this time. At this time, Spring AOP will use CGLIB to create a proxy object.

Subclasses are used as agents, as shown in the following figure: