Detailed explanation of JDK dynamic proxy
This article mainly introduces the basic principles of JDK dynamic proxy, so that everyone can understand JDK Proxy more deeply, know what it is. Understand the true principle of JDK dynamic proxy and its generation process. In the future, when we write JDK Proxy, we can write a perfect Proxy without checking the demo. Let’s first take a simple demo. The subsequent analysis process depends on this demo to introduce. The example is run using JDK 1.8.
JDK Proxy HelloWorld
package com.yao.proxy;/** * Created by robin */public interface Helloworld { void saysHello();} package com.yao.proxy;import com.yao.HelloWorld;/** * Created by robin */public class HelloworldImpl implements HelloWorld { public void sayHello() { System.out.print("hello world"); }} package com.yao.proxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;/** * Created by robin */public class MyInvocationHandler implements InvocationHandler{ private Object target; public MyInvocationHandler(Object target) { this.target=target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("method :"+ method.getName()+" is invoked!"); return method.invoke(target,args); }} package com.yao.proxy;import com.yao.HelloWorld;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Proxy;/** * Created by robin */public class JDKProxyTest { public static void main(String[]args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { //There are two ways to write here. We use a slightly complex way of writing, which will help everyone understand more. Class<?> proxyClass= Proxy.getProxyClass(JDKProxyTest.class.getClassLoader(),HelloWorld.class); final Constructor<?> cons = proxyClass.getConstructor(InvocationHandler.class); final InvocationHandler ih = new MyInvocationHandler(new HelloworldImpl()); HelloWorld helloWorld= (HelloWorld)cons.newInstance(ih); helloWorld.sayHello(); //The following is a simpler way of writing, essentially the same as above/* HelloWorld helloWorld=(HelloWorld)Proxy. newProxyInstance(JDKProxyTest.class.getClassLoader(), new Class<?>[]{HelloWorld.class}, new MyInvocationHandler(new HelloworldImpl())); helloWorld.sayHello(); */ }}Run the above code and such a simple JDK Proxy will be implemented.
Agent generation process
The reason why we call JDK dynamic proxy every day is that this proxy class is generated dynamically by JDK for us at runtime. Before explaining the proxy generation process, we first add the parameter -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true to the JVM startup parameter. Its function is to help us save the bytecode of the proxy class dynamically generated by JDK to the hard disk, and help us view the specific content of the proxy generated. I used Intellij IDEA, and after the proxy class was generated, it was placed directly in the root directory of the project , with the specific package name as the directory structure.
The process of generating a proxy class mainly includes two parts:
The getProxyClass method entry of the Proxy class: you need to pass in the class loader and interface
Then call the getProxyClass0 method, and the annotation in it is very clear. If the proxy class that implements the current interface exists, it will be returned directly from the cache. If it does not exist, it will be created through ProxyClassFactory. It can be clearly seen here that there is a limit on the number of interface interfaces, which cannot exceed 65535. The specific initialization information of proxyClassCache is as follows:
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
The specific logic for creating a proxy class is created through the apply method of ProxyClassFactory.
The logic in ProxyClassFactory includes the creation logic of the package name, calling ProxyGenerator. generateProxyClass to generate the proxy class, and loading the proxy class bytecode into the JVM.
1. The default package name generation logic is com.sun.proxy. If the proxy class is non-public proxy interface, the same package name as the proxy class interface is used. The default class name is $Proxy plus a self-incremented integer value.
2. After the package name is ready, the proxy bytecode is created according to the specific incoming interface through ProxyGenerator. generateProxyClass. The parameter -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true plays a role in this method. If true, save the bytecode to disk. In the proxy class, all proxy methods logic are the same to call the invoke method of invocationHander. We can see the specific proxy decompilation results later.
3. Load the bytecode into the JVM through the passed class loader: defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);.
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> { // prefix for all proxy class names private static final String proxyClassNamePrefix = "$Proxy"; // next number to use for generation of unique proxy class names private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<?> intf : interfaces) { /* * Verify that the class loader resolves the name of this * interface to the same Class object. */ Class<?> interfaceClass = null; try { interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } /* * Verify that the Class object actually represents an * interface. */ if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } /* * Verify that this interface is not a duplicate. */ if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } } String proxyPkg = null; // package to define proxy class in int accessFlags = Modifier.PUBLIC | Modifier.FINAL; /* * Record the package of a non-public proxy interface so that the * proxy class will be defined in the same package. Verify that * all non-public proxy interfaces are in the same package. */ //Generate package name and class name logic for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String name = intf.getName(); int n = name.lastIndexOf('.'); String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { proxyPkg = pkg; } else if (!pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } } } if (proxyPkg == null) { // if no non-public proxy interfaces, use com.sun.proxy package proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } /* * Choose a name for the proxy class to generate. */ long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * Generate the specified proxy class. Generate the bytecode of the proxy class* -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true to work in this section*/ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { //Load into JVM return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } 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()); } } } }We can decompile based on the bytecode of the proxy class, and we can get the following results. HelloWorld only has the sayHello method, but there are four methods in the proxy class that include three methods on the Object: equals, toString, and hashCode.
The rough structure of the agent includes 4 parts:
package com.sun.proxy;import com.yao.HelloWorld;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy0 extends Proxy implements HelloWorld { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue(); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final void saysHello() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } 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 int hashCode() throws { try { return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue(); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")}); m3 = Class.forName("com.yao.HelloWorld").getMethod("sayHello", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } }}FAQ:
1. toString() hashCode() equal() method calling logic: If the methods on these three Objects are called, they will pass through the invocationHandler logic like other interface methods and methods. They can be clearly seen from the bytecode results above. Other methods on Objects will not follow the proxy processing logic, but will directly follow the method logic on Object inherited by Proxy.
2. When the interface contains equals and toString hashCode methods, it will follow the invocation handler logic just like handling ordinary interface methods, and trigger the method logic based on the rewrite of the target object;
3. The interface contains duplicate method signatures, which are subject to the order in which the interface is passed. Whoever uses the method in front of it will only retain one method in the proxy class, and there will be no duplicate method signatures;
Thank you for reading, I hope it can help you. Thank you for your support for this site!