Dynamic proxy is a kind of usage scenario of Java reflection. As long as there is one interface, it can generate byte code files of type security dynamically at runtime, which can realize the function of resource delay loading (with simultaneous interpreting of traditional proxy mode) and section enhancement. It can be said to be one of the cornerstones of Spring.
This article mainly introduces the use of Java Dynamic Proxy and the class libraries related to dynamic proxy provided by JDK.
First look at the class diagram below (there are many details, and you will be prompted to return to see it later):
- Proxy and InvocationHandler are class libraries provided by JDK
- UserService and UserServiceImpl are the demo code of the proxy
- BusinessInvocationHandler is the demo code that implements the agent logic
- Finally, dynamic proxy classes and dynamic proxy instances are generated.
UserService Code:
public interface UserService { /** * A simple business function for demonstrating dynamic agents * * @param username username * @param password password */ void login(String username, String password); }
UserServiceImpl Code:
public class UserServiceImpl implements UserService { @Override public void login(String username, String password) { log.info("user {} login", username); } }
The detailed code of this article can be found in Github's Java tutorials repository Found.
Dynamic proxy class
Get Class object
Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) throws IllegalArgumentException
This function is used to create the Class object of the dynamic proxy Class. The dynamic proxy Class class object will be loaded by the Class loader specified by the loader and will implement all interfaces specified by interfaces.
The parameters loader and interfaces must meet the following requirements:
- All objects in interfaces must be interfaces, not ordinary classes or basic data types.
- There cannot be duplicate interfaces in interfaces.
- All interfaces in interfaces are visible to the Class loader by name, which is reflected in code (interface is a parameter, a Class object in interfaces, and loader is the corresponding loader): Class forName(interface.getName(), false, loader) == interface .
- All non-public interfaces in interfaces must be in the same package path, otherwise the proxy class cannot be generated.
- If methods with the same signature (function name and formal parameter list are consistent) are defined in different interfaces in interfaces:
- As long as the return value type of a method is void or basic data type, all methods must have the same return type.
- The return value type of one method must be a subclass (or implementation class) of the return value types of all other methods
- The dynamic proxy class is also limited by the virtual machine, so the length of the interfaces parameter cannot exceed 65535.
If any condition is not met, an IllegalArgumentException will be thrown.
Dynamic agent class features
All dynamic proxy classes have the following features:
-
Dynamic proxy class inherits Java lang.reflect. Proxy itself is public, final and non abstract.
-
Dynamic Proxy class names start with "$Proxy". If there is a non-public interface in interfaces, the generated dynamic Proxy class is under the package path of the interface.
-
The dynamic proxy Class implements all interfaces in a fixed order (the order of interfaces). If it is called again in a different order, another Class object will be generated. If it is called again in the same order, it will directly return the existing Class object. The code is as follows:
//Influence of interfaces parameter order Class<?> listSetProxy = Proxy.getProxyClass(ClassLoader.getPlatformClassLoader(), List.class, Set.class); Class<?> setListProxy = Proxy.getProxyClass(ClassLoader.getPlatformClassLoader(), Set.class, List.class); log.info("listSetProxy hashCode: {}", listSetProxy.hashCode()); log.info("setListProxy hashCode: {}", setListProxy.hashCode()); //Both tests passed assertNotEquals(listSetProxy, setListProxy); assertEquals(listSetProxy, Proxy.getProxyClass(ClassLoader.getPlatformClassLoader(), List.class, Set.class));
-
Proxy#isProxyClass can be used to check whether the specified Class object is a dynamic proxy.
-
The instance of the dynamic Proxy class is composed of the implementation of the special invocation processor interface (InvocationHandler, described in detail below), and inherits the Proxy class. The class diagram at the beginning of the article also reflects this relationship.
Create instance
As mentioned above, dynamic Proxy class instances are composed of calling processors (protected attribute of Proxy class). When creating an instance, you must specify a specific implementation of the calling processor interface. There are two ways to get a dynamic Proxy instance:
-
After obtaining the corresponding Class object, create an instance by reflection (after upgrading to Java 9, Jigsaw project has a little impact on the process of creating an instance by dynamic proxy, and Proxy#getProxyClass has been marked as obsolete.):
//In actual use, it is mostly created by Spring UserService realImpl = new UserServiceImpl(); Class<?> proxyClass = Proxy.getProxyClass(realImpl.getClass().getClassLoader(), realImpl.getClass().getInterfaces()); Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class); UserService instance = (UserService) constructor.newInstance(new BusinessInvocationHandler(realImpl));
-
If the Class object is not obtained in advance, you can use the Proxy#newProxyInstance function to directly create an instance:
//In actual use, it is mostly created by Spring UserService realImpl = new UserServiceImpl(); UserService instance = (UserService) Proxy.newProxyInstance( realImpl.getClass().getClassLoader(), realImpl.getClass().getInterfaces(), new BusinessInvocationHandler(realImpl) );
Call processor
InvocationHandler interface defines a function:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
When calling the function defined by each interface on the dynamic proxy instance, the invoke function will actually be called to pass relevant information through parameters, as shown below:
- The proxy parameter is the dynamic proxy instance itself.
- The parameter method is a function defined in the interface and expected to be called. You can freely choose whether to call it or not in the implementation of the invoke function (review the ER diagram, the implementation of dynamic proxy class and InvocationHandler is a composite relationship, while UserServiceImpl is an aggregate relationship).
- The parameter args is an argument passed to method and can be used arbitrarily in the implementation of the invoke function.
- The return value of the invoke function is returned to the actually called function.
- Exceptions thrown in the invoke function will also be thrown at the actually called function.
- When toString is called on a dynamic proxy instance, hashcode and equals will also be passed to the invoke function. Generally, the related functions of the proxy instance will be called directly.
Suppose the BusinessInvocationHandler is implemented as follows:
public class BusinessInvocationHandler implements InvocationHandler { /** * Most of the proxied objects are injected by the Spring container in actual use */ private final UserService service; public BusinessInvocationHandler(UserService service) { this.service = service; } @Override public Object invoke(Object proxy, Method method, Object[] args) { log.info("invoke {}'s {} with args {} start", service.getClass().getSimpleName(), method.getName(), Arrays.toString(args)); Object result = method.invoke(service, args); log.info("invoke {}'s {} with args {} done", service.getClass().getSimpleName(), method.getName(), Arrays.toString(args)); return result; } }
The codes in actual use are as follows:
//Get the dynamic proxy instance first UserService proxyService = (UserService) Proxy.newProxyInstance( realImpl.getClass().getClassLoader(), realImpl.getClass().getInterfaces(), new BusinessInvocationHandler(realImpl) ); //Call the interface function and observe the log to find that the BusinessInvocationHandler#invoke function is actually called proxyService.login("cncsl", "password"); //toString, hashCode and equals will also be forwarded to invoke, and finally invoke will be transferred to realImpl instance log.info("dynamicProxyInstance toString: {}, realImpl toString: {}", proxyService, realImpl); log.info("dynamicProxyInstance hashCode: {}, realImpl hashCode: {}", proxyService.hashCode(), realImpl.hashCode()); assertTrue(proxyService.equals(realImpl));
Generated dynamic proxy class
Add the following contents to the JVM startup parameters, and the JVM will output the generated proxy class bytecode file to the classpath:
- Java 8 and earlier: - dsun misc. ProxyGenerator. saveGeneratedFiles=true
- Java version above 8: JDK proxy. ProxyGenerator. saveGeneratedFiles=true
Decompile the bytecode file to get the source code below. The author added some comments to facilitate understanding.
//Before Java 8, dynamic proxy classes were generally located in com sum. In the proxy package path (or in the package path of the implemented non-public interface) 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 pers.cncsl.jt.reflect.dynamicproxy.UserService; //Inherit Proxy and implement UserService public final class $Proxy0 extends Proxy implements UserService { // m0~m2 are hashCode, equals and toString methods respectively private static Method m0; private static Method m1; private static Method m2; // The method defined in the proxy interface. This example is specifically the login method of UserService private static Method m3; //Class is initialized when it is loaded static { try { m0 = Class.forName("java.lang.Object").getMethod("hashCode"); m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("pers.cncsl.jt.reflect.dynamicproxy.UserService").getMethod("login", Class.forName("java.lang.String"), Class.forName("java.lang.String")); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } //The parent constructor assigns the InvocationHandler instance to the h property public $Proxy0(InvocationHandler var1) throws { super(var1); } //All functions of the dynamic proxy class work through the down processor 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 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 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 void login(String var1, String var2) throws { try { super.h.invoke(this, m3, new Object[]{var1, var2}); } catch (RuntimeException | Error var4) { throw var4; } catch (Throwable var5) { throw new UndeclaredThrowableException(var5); } } }