Spring AOP principle - dynamic proxy analysis and simple use

In Java, dynamic proxies are a very commonly used function. Although it is generally not necessary to use them directly, it is still necessary to understand how they work.

The main content of this blog is the simple use and understanding of JDK dynamic proxy and CGLIB dynamic proxy.

JDK Dynamic Proxy

The JDK dynamic proxy relies on the interface to determine the method it needs to proxy, and can be divided into the following roles when used:

  • TargetInterfaces - the target interface(s) that need to be proxied, JDK dynamic proxies will create proxies for method calls of these interfaces
  • TargetObject - an object that implements the target interface InvocationHandler - method invocation handler, JDK dynamic proxy passed internally
  • InvocationHandler object to handle invocation of target method java.lang.reflect.Proxy-assembly
  • InvocationHandler and TargetObject create proxy objects, and the created proxy objects are instances of its subclasses

TargetInterfaces and TargetObject are relatively easy to understand, they are some interfaces and objects that implement these interfaces, for example:

interface TargetInterfaceA {
    
    
  void targetMethodA();
}

interface TargetInterfaceB {
    
    
  void targetMethodB();
}

class TargetClass implements TargetInterfaceA, TargetInterfaceB {
    
    
  @Override
  public void targetMethodA() {
    
    
    System.out.println("Target method A...");
  }

  @Override
  public void targetMethodB() {
    
    
    System.out.println("Target method B...");
  }
}

In the above example, the target interface is [TargetInterfaceA, TargetInterfaceB], and the target object is an instance of TargetClass.

Now, we want to intercept the invocation of the method of the interface implemented by TargetClass, we need to define the proxy logic through InvocationHandler first:

class SimpleInvocationHandler implements InvocationHandler {
    
    
  private Object target;

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

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
    System.out.println(String.format("Before invocation method %s", method.getName()));
    Object result = method.invoke(target, args);
    System.out.println(String.format("After invocation method %s", method.getName()));
    return result;
  }
 }

The InvocationHandler interface only defines one method invoke, whose parameters are:

  • proxy - Proxy object instance, note that it is not TargetObject, but an instance of Proxy subclass, therefore, we need to
    hold TargetObject inside the InvocationHandler instance
  • method - the method to invoke
  • args - the method call arguments

With InvocationHandler and TargetClass, we can create TargetObject and create a proxy object through Proxy assembly, mainly through the newProxyInstance method:

TargetClass targetObject = new TargetClass();
Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), new SimpleInvocationHandler(targetObject););

The parameters of the method Proxy.newProxyInstance are:

  • ClassLoader - a ClassLoader, simply use the ClassLoader of targetObject directly
  • Class<?>[] - array of interfaces to be proxied, similarly, get all interfaces implemented by targetObject directly
  • InvocationHandler - InvocationHandler that defines method invocation processing logic

It can be seen that when creating a proxy object, we need to create a TargetObject first, and we also need to manually pass the TargetObject to the InvocationHandler, which is very troublesome...

Full code and tests:

interface TargetInterfaceA {
    
    
    void targetMethodA();
}

interface TargetInterfaceB {
    
    
    void targetMethodB();
}

class TargetClass implements TargetInterfaceA, TargetInterfaceB {
    
    
    @Override
    public void targetMethodA() {
    
    
        System.out.println("Target method A...");
    }

    @Override
    public void targetMethodB() {
    
    
        System.out.println("Target method B...");
    }
}

class SimpleInvocationHandler implements InvocationHandler {
    
    
    private Object target;

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

    public static Object bind(Object targetObject) {
    
    
        SimpleInvocationHandler handler = new SimpleInvocationHandler(targetObject);
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), handler);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        System.out.println(String.format("Before invocation method %s", method.getName()));
        Object result = method.invoke(target, args);
        System.out.println(String.format("After invocation method %s", method.getName()));
        return result;
    }
}

public class ProxyTest {
    
    
  public static void main(String[] args) {
    
    
    Object proxy = SimpleInvocationHandler.bind(new TargetClass());
    ((TargetInterfaceA) proxy).targetMethodA();
    ((TargetInterfaceB) proxy).targetMethodB();
  }
}

The output is:

Before invocation method targetMethodA
Target method A...
After invocation method targetMethodA
Before invocation method targetMethodB
Target method B...
After invocation method targetMethodB

JDK proxy class

When running the code, you can put the following line of code at the front to see what the proxy class dynamically generated by Proxy looks like:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

The proxy class generated by the previous code is:

final class $Proxy0 extends Proxy implements TargetInterfaceA, TargetInterfaceB {
    
    
  private static Method m0;
  private static Method m1;
  private static Method m2;
  private static Method m4;
  private static Method m3;

  static {
    
    
    try {
    
    
      m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
      m2 = Class.forName("java.lang.Object").getMethod("toString");

      // 目标接口中定义的方法
      m4 = Class.forName("classload.TargetInterfaceB").getMethod("targetMethodB");
      m3 = Class.forName("classload.TargetInterfaceA").getMethod("targetMethodA");

      m0 = Class.forName("java.lang.Object").getMethod("hashCode");
    } catch (NoSuchMethodException var2) {
    
    
      throw new NoSuchMethodError(var2.getMessage());
    } catch (ClassNotFoundException var3) {
    
    
      throw new NoClassDefFoundError(var3.getMessage());
    }
  }

  public $Proxy0(InvocationHandler var1) throws  {
    
    
    super(var1);
  }

  // 通过 InvocationHandler 来调用目标方法
  public final void targetMethodA() throws  {
    
    
    try {
    
    
      super.h.invoke(this, m3, (Object[])null);
    } catch (RuntimeException | Error var2) {
    
    
      throw var2;
    } catch (Throwable var3) {
    
    
      throw new UndeclaredThrowableException(var3);
    }
  }

  // 通过 InvocationHandler 来调用目标方法
  public final void targetMethodB() throws  {
    
    
    try {
    
    
      super.h.invoke(this, m4, (Object[])null);
    } catch (RuntimeException | Error var2) {
    
    
      throw var2;
    } catch (Throwable var3) {
    
    
      throw new UndeclaredThrowableException(var3);
    }
  }
}

By reading the code of the proxy class we can find:

  • The proxy class inherits Proxy and implements the target interface
  • The proxy class obtains the method of the target interface through reflection in the static initialization block
  • The interface method implemented by the proxy class will call the target method through InvocationHandler
  • The first parameter passed by InvocationHandler is the proxy object, not TargetObject1

In addition, the proxy class also obtains the hashCode, equals and toString methods of Object, and their calling logic is the same, that is, directly calling the method corresponding to the InvocationHandler object, for example:

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);
  }
}

Therefore, we can also proxy these methods of the target object.

CGLIB dynamic proxy

CGLIB dynamic proxy is similar to JDK dynamic proxy, except that CGLIB dynamic proxy is based on classes and does not need to implement interfaces. For simple use, you only need to define a MethodInterceptor, which is equivalent to InvocationHandler in JDK dynamic proxy.

class SimpleMethodInterceptor implements MethodInterceptor {
    
    
  @Override
  public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    
    
    System.out.println(String.format("Before invocation method %s", method.getName()));
    Object result = proxy.invokeSuper(obj, args);
    System.out.println(String.format("After invocation method %s", method.getName()));
    return result;
  }
}

With MethodInterceptor, we can create proxy objects:

class ProxyTest {
    
    
  public static void main(String[] args) {
    
    
    Enhancer enhancer = new Enhancer();
    // 设置需要被代理的类
    enhancer.setSuperclass(TargetClass.class);
    // 设置 MethodInterceptor
    enhancer.setCallback(new SimpleMethodInterceptor());
    // 创建代理对象
    TargetClass proxyObject = (TargetClass) enhancer.create();
    // 调用方法
    proxyObject.targetMethodA();
    proxyObject.targetMethodB();
  }
}


class TargetClass {
    
    
  public void targetMethodA() {
    
    
    System.out.println("Target method A...");
  }

  public void targetMethodB() {
    
    
    System.out.println("Target method B...");
  }
}

The execution output is:

Before invocation method targetMethodA
Target method A...
After invocation method targetMethodA
Before invocation method targetMethodB
Target method B...
After invocation method targetMethodB

CGLIB proxy class

For CGLIB, you can set
the value of the DebuggingClassWriter.DEBUG_LOCATION_PROPERTY property to save the generated proxy class 2:

public class TargetClass$$EnhancerByCGLIB$$eb42b691 extends TargetClass implements Factory {
    
    
  private MethodInterceptor CGLIB$CALLBACK_0;

  static void CGLIB$STATICHOOK1() {
    
    
    // 要代理的目标方法
    var10000 = ReflectUtils.findMethods(new String[]{
    
    "targetMethodA", "()V", "targetMethodB", "()V"}, (var1 = Class.forName("TargetClass")).getDeclaredMethods());
    CGLIB$targetMethodA$0$Method = var10000[0];
    CGLIB$targetMethodA$0$Proxy = MethodProxy.create(var1, var0, "()V", "targetMethodA", "CGLIB$targetMethodA$0");
    CGLIB$targetMethodB$1$Method = var10000[1];
    CGLIB$targetMethodB$1$Proxy = MethodProxy.create(var1, var0, "()V", "targetMethodB", "CGLIB$targetMethodB$1");
  }

  // 目标方法的简单代理
  final void CGLIB$targetMethodA$0() {
    
    
    super.targetMethodA();
  }

  public final void targetMethodA() {
    
    
    MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    if (var10000 == null) {
    
    
      CGLIB$BIND_CALLBACKS(this);
      var10000 = this.CGLIB$CALLBACK_0;
    }

    // 当 MethodInterceptor 不为空时通过 MethodInterceptor 调用目标方法
    if (var10000 != null) {
    
    
      var10000.intercept(this, CGLIB$targetMethodA$0$Method, CGLIB$emptyArgs, CGLIB$targetMethodA$0$Proxy);
    } else {
    
    
      super.targetMethodA();
    }
  }

  // 目标方法的简单代理
  final void CGLIB$targetMethodB$1() {
    
    
    super.targetMethodB();
  }

  public final void targetMethodB() {
    
    
    MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    if (var10000 == null) {
    
    
      CGLIB$BIND_CALLBACKS(this);
      var10000 = this.CGLIB$CALLBACK_0;
    }

    // 当 MethodInterceptor 不为空时通过 MethodInterceptor 调用目标方法
    if (var10000 != null) {
    
    
      var10000.intercept(this, CGLIB$targetMethodB$1$Method, CGLIB$emptyArgs, CGLIB$targetMethodB$1$Proxy);
    } else {
    
    
      super.targetMethodB();
    }
  }

  final int CGLIB$hashCode$5() {
    
    
    return super.hashCode();
  }

  // 对 Object 方法的代理
  public final int hashCode() {
    
    
    MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    if (var10000 == null) {
    
    
      CGLIB$BIND_CALLBACKS(this);
      var10000 = this.CGLIB$CALLBACK_0;
    }

    if (var10000 != null) {
    
    
      Object var1 = var10000.intercept(this, CGLIB$hashCode$5$Method, CGLIB$emptyArgs, CGLIB$hashCode$5$Proxy);
      return var1 == null ? 0 : ((Number)var1).intValue();
    } else {
    
    
      return super.hashCode();
    }
  }
}

can be seen

  • CGLIB sets two proxies for each method, one directly calls the parent class method, and the other judges whether there is a MethodInterceptor to call
  • The proxy class inherits TargetClass, which is different from the way of inheriting Proxy in JDK dynamic proxy

After we set the MethodInterceptor, CGLIB can call the target method through the MethodInterceptor. In addition, the
first parameter passed when calling the MethodInterceptor.intercept method is the instance of the proxy class. Therefore, when the proxy method needs to be executed, it should pass MethodProxy. invokeSuper to complete, if you use Method.invoke, it will lead to infinite recursive calls.

Spring @Configuration

When using Spring, we can define beans as follows:

@Configuration
@ComponentScan(basePackageClasses = Company.class)
public class Config {
    
    
  @Bean
  public Address getAddress() {
    
    
    return new Address("High Street", 1000);
  }

  @Bean
  public Person getPerson() {
    
    
    return new Person(getAddress());
  }
}

One of the confusions about this method at the beginning was how Spring intercepted the call to the getAddress method, because in my impression, the JDK dynamic proxy could not do such a thing, and now I found out that Spring will create a proxy for Config through CGLIB Object, intercepting calls to the getAddress method to ensure the singleton of the Bean.

Because in Java, the method will be searched according to the actual type of the first object, and if it cannot be found, it will be searched in the parent class . It just so happens that the proxy object created by CGLIB overrides the method of the parent class. By intercepting method calls in the class through MethodInterceptor, repeated creation of beans can be avoided.

The corresponding MethodInterceptor in Spring is ConfigurationClassEnhancer.BeanMethodInterceptor.

summary

Here is a summary of the proxy methods of JDK dynamic proxy and CGLIB dynamic proxy:

  • The JDK dynamic proxy completes the proxy by creating a proxy class that inherits Proxy and implements TargetInterfaces.
    When the method of TargetInterfaces is called, the proxy class will transfer the method call to InvocationHandler for completion.
  • The CGLIB dynamic proxy completes the proxy by creating a proxy class that inherits TargetClass. When calling the method of TargetClass, if the MethodInterceptor is not empty, the method call will be transferred to the MethodInterceptor for completion.

It can be seen that the two ways of implementing dynamic proxy are still very close, except that one is through the interface and the other is through the subclass.

Guess you like

Origin blog.csdn.net/weixin_44947701/article/details/121871777