• preface
  • JDK dynamic proxy
    • The proxy class
  • CGLIB dynamic proxy
    • The proxy class
    • Spring @Configuration
  • summary
  • conclusion

preface

Dynamic proxies are a common feature in Java, and while you don’t usually need to use them yourself, it’s important to understand how they work.

This blog post is about the simple use and understanding of JDK dynamic proxies and CGLIB dynamic proxies.

JDK dynamic proxy

JDK dynamic proxies rely on interfaces to determine which methods it needs to delegate, and can be used in the following roles:

  • TargetInterfaces– The target interface (s) for which the JDK dynamic proxy will be requiredThe method callTo create the agent
  • TargetObject– The object that implements the target interface
  • InvocationHandlerThe method callThe processor, JDK dynamic proxy is passed internallyInvocationHandlerObject to handle calls to the target method
  • java.lang.reflect.Proxy– assemblyInvocationHandlerTargetObjectCreates a proxy object that is an instance of its subclasses

TargetInterfaces and TargetObject are relatively easy to understand. They are interfaces and objects that implement those interfaces, such as:

interface TargetInterfaceA {
  void targetMethodA(a);
}

interface TargetInterfaceB {
  void targetMethodB(a);
}

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

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

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

Now that we want to intercept the invocation of the TargetClass interface’s method, we need to define the proxy logic with InvocationHandler:

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()));
    returnresult; }}Copy the code

The InvocationHandler interface defines only one method, invoke, which takes the following parameters:

  • proxy– Proxy object instance, note, notTargetObject, it isProxySubclass instance, therefore, we need inInvocationHandlerInternal instance holdingTargetObject
  • method– The method to call
  • args– Method call parameters

Now that we have InvocationHandler and TargetClass, we can create TargetObject and create Proxy objects from Proxy assembly, mainly through the newProxyInstance method:

TargetClass targetObject = new TargetClass();
Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), newSimpleInvocationHandler(targetObject);) ;Copy the code

The parameter to the proxy. newProxyInstance method is:

  • ClassLoader– aClassLoaderIn simple words, you can use it directlytargetObjectClassLoaderIt is ok
  • Class<? > []– Array of interfaces to be proxied, again, directly obtainedtargetObjectAll interfaces implemented
  • InvocationHandler– Defines the method call processing logicInvocationHandler

As you can see, when creating the proxy object, you need to create the TargetObject first, and also need to manually pass the TargetObject to the InvocationHandler.

Complete code and tests:

interface TargetInterfaceA {
    void targetMethodA(a);
}

interface TargetInterfaceB {
    void targetMethodB(a);
}

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

    @Override
    public void targetMethodB(a) {
        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()));
        returnresult; }}public class ProxyTest {
  public static void main(String[] args) {
    Object proxy = SimpleInvocationHandler.bind(newTargetClass()); ((TargetInterfaceA) proxy).targetMethodA(); ((TargetInterfaceB) proxy).targetMethodB(); }}Copy the code

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
Copy the code

The proxy class

When running the code, you can put the following line of code at the top of the list to see how the Proxy class looks dynamically generated by Proxy:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles"."true");
Copy the code

The previous code generated the proxy class as follows:

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

      // The method defined in the target interface
      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 newNoClassDefFoundError(var3.getMessage()); }}public $Proxy0(InvocationHandler var1) throws  {
    super(var1);
  }

  // Call the target method with InvocationHandler
  public final void targetMethodA(a) throws  {
    try {
      super.h.invoke(this, m3, (Object[])null);
    } catch (RuntimeException | Error var2) {
      throw var2;
    } catch (Throwable var3) {
      throw newUndeclaredThrowableException(var3); }}// Call the target method with InvocationHandler
  public final void targetMethodB(a) throws  {
    try {
      super.h.invoke(this, m4, (Object[])null);
    } catch (RuntimeException | Error var2) {
      throw var2;
    } catch (Throwable var3) {
      throw newUndeclaredThrowableException(var3); }}}Copy the code

By reading the code for the proxy class, we can see:

  • The proxy class inheritsProxyAnd realize the target interface
  • The proxy class obtains the methods of the target interface through reflection in the static initialization block
  • Interface methods implemented by proxy classes passInvocationHandlerTo call the target method
  • InvocationHandlerThe first argument passed is the proxy object, notTargetObject1

In addition, the proxy class also gets the Object’s hashCode, Equals, and toString methods. They all use the same call logic.

public final int hashCode(a) throws  {
  try {
    return (Integer)super.h.invoke(this, m0, (Object[])null);
  } catch (RuntimeException | Error var2) {
    throw var2;
  } catch (Throwable var3) {
    throw newUndeclaredThrowableException(var3); }}Copy the code

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

CGLIB dynamic proxy

CGLIB dynamic proxies are similar to JDK dynamic proxies, except that CGLIB dynamic proxies are class-based and do not require an interface. To use CGLIB dynamic proxies, you simply need to define a MethodInterceptor. Equivalent to the InvocationHandler in the 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()));
    returnresult; }}Copy the code

With the MethodInterceptor we can now create a proxy object:

class ProxyTest {
  public static void main(String[] args) {
    Enhancer enhancer = new Enhancer();
    // Set the class to be proxied
    enhancer.setSuperclass(TargetClass.class);
    / / set the MethodInterceptor
    enhancer.setCallback(new SimpleMethodInterceptor());
    // Create a proxy object
    TargetClass proxyObject = (TargetClass) enhancer.create();
    // Call the methodproxyObject.targetMethodA(); proxyObject.targetMethodB(); }}class TargetClass {
  public void targetMethodA(a) {
    System.out.println("Target method A...");
  }

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

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
Copy the code

The proxy class

Can be set up for additional DebuggingClassWriter. DEBUG_LOCATION_PROPERTY the value of the attribute to store the generated proxy class 2:

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

  static void CGLIB$STATICHOOK1() {
    // Target method to delegate
    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");
  }

  // A simple proxy for the target method
  final void CGLIB$targetMethodA$0() {
    super.targetMethodA();
  }

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

    Call the target method from the MethodInterceptor when the MethodInterceptor is not null
    if(var10000 ! =null) {
      var10000.intercept(this, CGLIB$targetMethodA$0$Method, CGLIB$emptyArgs, CGLIB$targetMethodA$0$Proxy);
    } else {
      super.targetMethodA(); }}// A simple proxy for the target method
  final void CGLIB$targetMethodB$1() {
    super.targetMethodB();
  }

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

    Call the target method from the MethodInterceptor when the MethodInterceptor is not null
    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();
  }

  // Proxy for the Object method
  public final int hashCode(a) {
    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(); }}// ...
}
Copy the code

You can see

  • CGLIB sets up two proxies per method, one to call the parent method directly and the other to determine whether it existsMethodInterceptorTo make the call
  • The proxy class inheritsTargetClass, and JDK dynamic proxy inheritanceProxyIn a different way

When we set the MethodInterceptor additional can invoke the target method through MethodInterceptor, in addition, call MethodInterceptor. Intercept method is passed the first parameter to the agent class instance, Therefore, when a propped Method needs to be executed, it should be done with methodProxy.invokesuper, which would result in an infinite recursive call if method.invoke was used.

Spring @Configuration

When using Spring, we can define beans as follows:

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

  @Bean
  public Person getPerson(a) {
    return newPerson(getAddress()); }}Copy the code

One of the confusing things about this approach was how Spring intercepted the call to the getAddress method, because I was under the impression that JDK dynamic proxies could not do this. Spring creates proxy objects for Config via CGLIB. Intercepts calls to the getAddress method to ensure singleton of the Bean.

CGLIB creates a proxy object that overrides the methods of the parent class because Java looks for methods based on the actual type of the parent object. You can avoid creating beans repeatedly by intercepting method calls in the proxy class using the MethodInterceptor.

The corresponding MethodInterceptor in Spring for ConfigurationClassEnhancer. BeanMethodInterceptor.

summary

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

  • JDK dynamic proxies are inherited by creationProxyAnd implementedTargetInterfacesProxy class to complete the proxy, callTargetInterfacesThe proxy class passes the method call toInvocationHandlercomplete
  • The CGLIB dynamic proxy inherits through creationTargetClassProxy class to complete the proxy, callTargetClassIfMethodInterceptorNot null, then the method call is passed toMethodInterceptorcomplete

As you can see, the two ways to implement dynamic proxies are very similar, but one is through interfaces and one is through subclasses.

conclusion

The first time I came into contact with the concept of dynamic proxy was when I read the book Java Core Technology Volume. At that time, I just started to learn Java for a short time. After seeing this thing, I thought, who would use such an inconvenient thing

As a result, it is widely used (´ゝ ‘)

Similar source annotations, have to say, these operations up trouble, but powerful features, there will always be someone to complete the pattern to ╮( ̄▽ ̄)╭

Footnotes

When you first see the InvocationHandler interface, you always think that its first argument is TargetObject

A lot of other unnecessary content is omitted for easy reading