Dynamic agent
summary
Previously, when implementing static Proxy, you need to define and generate Proxy classes according to the implemented interface. The JDK dynamic Proxy uses the methods in the java.lang.reflect.Proxy class. If you want to generate a class Proxy class, you only need to customize a handler that handles the logic before and after the execution of the method, and call the method of generating the Proxy class in the Proxy class to get the Proxy class. You don't need to write your own Proxy class for different interfaces.
give an example
Define the interface implemented by the proxy class and the proxy class
interface Human { String getBelief(); void eat(String food); }
Defines the SuperMan of the proxied class
class SuperMan implements Human { @Override public String getBelief() { return "I believe I can fly!"; } @Override public void eat(String food) { System.out.println("I like to eat " + food); } }
Create a general proxy class production factory, pass in an object, and return its proxy class
class ProxyFactory { // Call this method to return an object of proxy class. Solve 1 public static Object getProxyInstance(Object obj) { // obj: object of the proxied class Object instance = Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), // This is a user-defined class that implements the InvocationHandler interface. It is a function interface // There is only the invoke method (proxy, method, args) -> { System.out.println("Before method call......"); // Call the method of the proxied object through reflection Object o = method.invoke(obj, args); System.out.println("After method call......"); return o; }); return instance; } }
The last parameter in newProxyInstance() is an object that implements the InvocationHandler interface. InvocationHandler is a function interface. The specific source code is as follows
public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
When the method of the proxy class is called, the proxy class will call the invoke method of the handler to execute the relevant logic. In this way, we can weave the logic we want to execute before and after the proxy class executes the method (method.invoke(obj, args)).
test
public class ProxyTest { @Test public void test() { SuperMan superMan = new SuperMan(); // Get proxy object Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan); // Calling the method of the interface will automatically call the invoke method in the handler and dynamically call the method with the same name proxyInstance.eat("apple"); } }
- results of enforcement
It can be seen that the user-defined logic is also executed before and after the proxy object method is executed
Source code
The source code is divided into two parts: the Proxy generation method and the generated Proxy execution target method
Note: the JDK version corresponding to the source code of this article is 15
Generate proxy class source code
-
Enter Proxy.newProxyInstance() method
/** * Returns the proxy class that implements the given interface */ public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) { Objects.requireNonNull(h); final Class<?> caller = System.getSecurityManager() == null ? null : Reflection.getCallerClass(); /* * Find the constructor used to generate the proxy class */ Constructor<?> cons = getProxyConstructor(caller, loader, interfaces); return newProxyInstance(caller, cons, h); }
The key statement is the penultimate statement. Enter this method
-
Proxy.getProxyConstructor()
/** * Returns the constructor of the proxy class */ private static Constructor<?> getProxyConstructor(Class<?> caller, ClassLoader loader, Class<?>... interfaces) { // Optimize the case where there is only one interface if (interfaces.length == 1) { Class<?> intf = interfaces[0]; if (caller != null) { checkProxyAccess(caller, loader, intf); } // Insert a new proxyBuilder into the proxy cache return proxyCache.sub(intf).computeIfAbsent( loader, (ld, clv) -> new ProxyBuilder(ld, clv.key()).build() ); } else { // Make a deep copy of the interface array final Class<?>[] intfsArray = interfaces.clone(); if (caller != null) { checkProxyAccess(caller, loader, intfsArray); } // Convert array to list final List<Class<?>> intfs = Arrays.asList(intfsArray); return proxyCache.sub(intfs).computeIfAbsent( loader, (ld, clv) -> new ProxyBuilder(ld, clv.key()).build() ); } }
There is a property proxyCache in the Proxy class, which is a cache with a Proxy class constructor. In line 17, ld is the class loader and clv is the class loader value. Here, the key of clv is the interface Human implemented by the Proxy class
After the anonymous object new of ProxyBuilder comes out, it enters its build() method
-
Proxy.ProxyBuilder.build() method
/** * Generate the Class class of the proxy Class and return the required constructor */ Constructor<?> build() { // Class class that defines the proxy class Class<?> proxyClass = defineProxyClass(module, interfaces); final Constructor<?> cons; try { cons = proxyClass.getConstructor(constructorParams); } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); return cons; }
Line 6 is the statement to generate the proxy Class. Enter this method
-
java.lang.reflect.Proxy.ProxyBuilder#defineProxyClass
/** * Generate Class class of proxy Class */ private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) { String proxyPkg = null; // The package to which the proxy class belongs int accessFlags = Modifier.PUBLIC | Modifier.FINAL; /* * Record the packages of all non-public interfaces to ensure that the packages of the proxy classes and non-public interfaces generated later are the same * same. At the same time, to verify that all non-public interfaces are in the same package (if not in the same package, * The generated proxy class cannot access these interfaces) */ for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; // non-public, final String pkg = intf.getPackageName(); if (proxyPkg == null) { proxyPkg = pkg; } else if (!pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } } if (proxyPkg == null) { // If all interfaces are public, the default package is used proxyPkg = m.isNamed() ? PROXY_PACKAGE_PREFIX + "." + m.getName() : PROXY_PACKAGE_PREFIX; } else if (proxyPkg.isEmpty() && m.isNamed()) { // If the package name is an empty string and the module is not unnamed, an exception is thrown throw new IllegalArgumentException( "Unnamed package cannot be added to " + m); } if (m.isNamed()) { if (!m.getDescriptor().packages().contains(proxyPkg)) { throw new InternalError(proxyPkg + " not exist in " + m.getName()); } } /* * The name of the generated proxy class */ // Because a new proxy class is to be generated, the global proxy class serial number is increased by one long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg.isEmpty() ? proxyClassNamePrefix + num : proxyPkg + "." + proxyClassNamePrefix + num; ClassLoader loader = getLoader(m); trace(proxyName, m, loader, interfaces); /* * Generate proxy class */ // Generate bytecode of proxy class byte[] proxyClassFile = ProxyGenerator.generateProxyClass(loader, proxyName, interfaces, accessFlags); try { // Define the Class according to the generated bytecode Class<?> pc = JLA.defineClass(loader, proxyName, proxyClassFile, null, "__dynamic_proxy__"); // Add this proxy class to the proxy class cache reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE); return pc; } catch (ClassFormatError e) { /* * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). */ throw new IllegalArgumentException(e.toString()); } }
First, determine whether all non-public interfaces are under the same package to ensure that the proxy classes generated later are also under the package (otherwise, these interfaces cannot be accessed). Then generate the name of the proxy class. Here, the generation rule of the name of the proxy class is the unified prefix of the proxy class + serial number. Each time a proxy class is generated, add one with cas operation.
Line 59 is the statement that generates the bytecode of the proxy class. It calls the method of the ProxyGenerator class. Let's take a look at the class diagram of this class first
It inherits ClassWriter. It is judged here that it has the function of generating class bytecode files
-
java.lang.reflect.ProxyGenerator#generateProxyClass
static byte[] generateProxyClass(ClassLoader loader, final String name, // The name of the previously generated proxy class List<Class<?>> interfaces, int accessFlags) { // new is a proxy class generator ProxyGenerator gen = new ProxyGenerator(loader, name, interfaces, accessFlags); // Generate bytecode file of proxy class final byte[] classFile = gen.generateClassFile(); // ... return classFile; }
The method called by the statement entering line 8
-
java.lang.reflect.ProxyGenerator#generateClassFile
private byte[] generateClassFile() { visit(V14, accessFlags, dotToSlash(className), null, JLR_PROXY, typeNames(interfaces)); /* * Add some common methods to the proxy class: hashCode, equals, toString * This step is performed before the interface method is generated because if the interface method overrides the methods in the three Object classes * You can achieve coverage */ addProxyMethod(hashCodeMethod); addProxyMethod(equalsMethod); addProxyMethod(toStringMethod); /* * Add all methods of all interfaces */ for (Class<?> intf : interfaces) { for (Method m : intf.getMethods()) { if (!Modifier.isStatic(m.getModifiers())) { addProxyMethod(m, intf); } } } /* * Check whether the return types of methods with the same name and parameters conflict */ for (List<ProxyMethod> sigmethods : proxyMethods.values()) { checkReturnTypes(sigmethods); } // Generation construction method generateConstructor(); for (List<ProxyMethod> sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { // Add the static modifier to the Method object in the proxy class visitField(Modifier.PRIVATE | Modifier.STATIC, pm.methodFieldName, LJLR_METHOD, null, null); // Generate the code for the proxy class to execute this method pm.generateMethod(this, className); } } // The static code block of the generated class is used to initialize those static Method objects generateStaticInitializer(); return toByteArray(); }
First, add the default hashcode, equals and toString methods, and then add all the methods of the interface (in fact, add the Method object to the proxy class). The constructor is then generated. Since the Method added before only adds Method member variables to the class (an example will be given later), adding static modifiers to these variables also needs to generate Method code for the proxy class (code can be executed). Finally, a static code block is added to initialize the static Method objects.
-
Go back to step 4, line 60 of the code. After getting the bytecode, generate the proxy Class, and then add the proxy Class to the cache.
-
Go back to step 3. After getting the proxy class, get its constructor and return
-
Return to step 2 and return to the resulting constructor
-
Returning to the last sentence of step 1, executing the newProxyInstance() method is actually executing the following statement
return cons.newInstance(new Object[]{h});
This constructor has only one input parameter, which is the handler of the previously customized invoke method
Disassembly of generated bytecode file
final class $Proxy0 extends Proxy implements Human { // The Method object mentioned earlier private static Method m0; private static Method m1; private static Method m2; private static Method m3; private static Method m4; // Constructor, the input parameter is of type InvocaitonHandler public $Proxy0(InvocationHandler param1) { super(var1); } public final int hashCode() { 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) { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() { 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 String getBelief() { try { return (String)super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void eat(String var1) { try { super.h.invoke(this, m4, new Object[]{var1}); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } 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("example.Human").getMethod("getBelief"); m4 = Class.forName("example.Human").getMethod("eat", Class.forName("java.lang.String")); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
It can be seen that there are static member variables in the proxy class, all of which are Method types. When the Method is called, these Method objects are passed into the handler. These static methods will be initialized in the static code block and obtained by reflection. When a Method is called, you can pass the proxy class, the corresponding Method object and the corresponding parameters into the handler.