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 article focuses on 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 – TargetInterfaces that require proxies that JDK dynamic proxies will create for method calls to
  • TargetObject – The object that implements the target interface
  • InvocationHandler – a method InvocationHandler that the JDK dynamic proxy internally handles calls to target methods through the InvocationHandler object
  • Java.lang.reflect. Proxy – Assembs InvocationHandler and TargetObject to create a Proxy object that is an instance of its subclass

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

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..." ); }}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())); return result; }}Copy the code

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

  • Proxy-proxy object instance, not TargetObject, but an instance of a proxy subclass, so we need to hold TargetObject inside the InvocationHandler instance
  • 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(), new SimpleInvocationHandler(targetObject);) ;Copy the code

The parameter to the proxy. newProxyInstance method is:

  • ClassLoader – a ClassLoader. Simply use targetObject ClassLoader
  • Class
    [] – Array of interfaces to proide, again, directly get all interfaces of targetObject implementation
  • InvocationHandler – InvocationHandler that defines the method call processing logic

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(); } 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(); }}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"); / / target interface methods defined in the m4 = Class. 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); 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); 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); }}}Copy the code

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

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

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() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(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())); return result; }}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(); SetSuperclass (targetclass.class); // Set the class to be propped. SetCallback (new SimpleMethodInterceptor()); // create the proxyObject TargetClass proxyObject = (TargetClass) enhancer.create(); / / call the method proxyObject. TargetMethodA (); proxyObject.targetMethodB(); } } class TargetClass { public void targetMethodA() { System.out.println("Target method A..." ); } public void targetMethodB() { 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() {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; } // Call the target method 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; } // Call the target method 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(); 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(); }} / /... }Copy the code

You can see

  • CGLIB sets up two proxies for each method, one that calls the parent method directly and the other that determines if a MethodInterceptor exists to call it
  • The Proxy class inherits TargetClass in a different way from JDK dynamic proxies

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() { return new Address("High Street", 1000); } @Bean public Person getPerson() { return new Person(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:

  • The JDK dynamic Proxy completes the Proxy by creating a Proxy class that inherits the Proxy and implements TargetInterfaces. When the TargetInterfaces method is called, the Proxy class passes the method call to the InvocationHandler for completion
  • The CGLIB dynamic proxy completes the proxy by creating a proxy class that inherits from TargetClass. If the MethodInterceptor method is not null when it calls the TargetClass method, it passes the method call to the MethodInterceptor for completion

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

I first came into contact with the concept of dynamic proxy when I read the “JVM Performance Tuning Field Notes”. At that time, I just started to learn Java for a short time. After seeing this thing, I thought, such an inconvenient thing, who would use it

As a result, it is widely used, as well as source annotations, which, it must be said, are cumbersome but powerful features that will always be tweaked.