JAVA Dynamic Proxy
proxy pattern
Provide a proxy for other objects to control access to an object. The agent class is mainly responsible for preprocessing messages, filtering messages and passing messages to the delegate class for the delegate (real object). The agent class does not realize specific services, but uses the delegate class to complete the services and encapsulates the execution results.
In fact, the agent class preprocesses messages for the proxy class, filters messages, forwards messages to the proxy class, and then carries out post-processing of messages. There is usually an association between the proxy class and the proxied class (that is, the references held from the object mentioned above). The proxy class itself does not implement services, but provides services by calling methods in the proxied class.
Static proxy
Create an interface, and then create the proxy class to implement the interface and implement the abstract methods in the interface. Then create a proxy class and make it also implement this interface. In the proxy class, a reference to the proxy is held, then the method of the object is called in the proxy class method.
Interface:
public interface HelloInterface { void sayHello(); }
Proxy class:
public class Hello implements HelloInterface{ @Override public void sayHello() { System.out.println("Hello zhanghao!"); } }
Agent class:
public class HelloProxy implements HelloInterface{ private HelloInterface helloInterface = new Hello(); @Override public void sayHello() { System.out.println("Before invoke sayHello" ); helloInterface.sayHello(); System.out.println("After invoke sayHello"); } }
Proxy class call:
The proxy class is passed to the proxy class HelloProxy, and the proxy class completes the call through the proxy class when executing the specific method.
public static void main(String[] args) { HelloProxy helloProxy = new HelloProxy(); helloProxy.sayHello(); } //Output: //Before invoke sayHello //Hello zhanghao! //After invoke sayHello
Using a static proxy, you can easily complete the proxy operation on a class. However, the disadvantages of static proxy are also exposed: because the proxy can only serve one class, if there are many proxy classes, you need to write a large number of proxy classes, which is cumbersome.
Dynamic agent
Create proxy classes at run time using reflection mechanisms.
The interface and the proxy class remain unchanged. We build a handler class to implement the InvocationHandler interface.
public class ProxyHandler implements InvocationHandler{ private Object object; public ProxyHandler(Object object){ this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before invoke " + method.getName()); method.invoke(object, args); System.out.println("After invoke " + method.getName()); return null; } }
Execute dynamic proxy:
public static void main(String[] args) { System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); HelloInterface hello = new Hello(); InvocationHandler handler = new ProxyHandler(hello); HelloInterface proxyHello = (HelloInterface) Proxy.newProxyInstance( hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), handler ); proxyHello.sayHello(); } //Output: //Before invoke sayHello //Hello zhanghao! //After invoke sayHello
Return the Proxy instance of an interface through the static method newProxyInstance of the Proxy class. For different agent classes, pass in the corresponding agent controller InvocationHandler.
If there is a new proxy class Bye, for example:
public interface ByeInterface { void sayBye(); } public class Bye implements ByeInterface { @Override public void sayBye() { System.out.println("Bye zhanghao!"); } }
Then the execution process:
public static void main(String[] args) { System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); HelloInterface hello = new Hello(); ByeInterface bye = new Bye(); InvocationHandler handler = new ProxyHandler(hello); InvocationHandler handler1 = new ProxyHandler(bye); HelloInterface proxyHello = (HelloInterface) Proxy.newProxyInstance( hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), handler ); ByeInterface proxyBye = (ByeInterface) Proxy.newProxyInstance( bye.getClass().getClassLoader(), bye.getClass().getInterfaces(), handler1 ); proxyHello.sayHello(); proxyBye.sayBye(); } //Output: //Before invoke sayHello //Hello zhanghao! //After invoke sayHello //Before invoke sayBye //Bye zhanghao! //After invoke sayBye
Underlying implementation of dynamic agent
Specific steps of dynamic agent:
- Create your own calling processor by implementing the InvocationHandler interface;
- Create a dynamic Proxy class by specifying a ClassLoader object and a set of interface s for the Proxy class;
- The constructor of the dynamic proxy class is obtained through the reflection mechanism, and its only parameter type is the calling processor interface type;
- The dynamic proxy class instance is created through the constructor, and the calling processor object is passed in as a parameter during construction.
Since the Proxy object is generated using the static newProxyInstance of the Proxy class, let's go to its source code to see what it has done?
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)throws IllegalArgumentException { Objects.requireNonNull(h); final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } //Generate proxy class object Class<?> cl = getProxyClass0(loader, intfs); //Gets the constructor object of the proxy class using the specified call handler try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; //If the Class scope is private, access is supported through setAccessible if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } //Get the Proxy Class constructor and create a Proxy instance. return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }
Use getProxyClass0(loader, intfs) to generate the Class object of Proxy Class Proxy.
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { //If the number of interfaces is greater than 65535, an illegal parameter error is thrown if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } //If the proxy class of the specified interface already exists in the cache, you can directly get it from the cache without creating a new one; //If no proxy object is specified in the cache, a proxy object is created through ProxyClassFactory. return proxyClassCache.get(loader, interfaces); }
ProxyClassFactory internal class creates and defines proxy class, and returns proxy class of given ClassLoader and interfaces.
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>{ // The prefix of Proxy class name is "$Proxy" private static final String proxyClassNamePrefix = "$Proxy"; // Each proxy class prefix is followed by a unique number, such as $Proxy0, $Proxy1, $Proxy2 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 whether the object obtained by the class loader loading interface is the same as the object passed in by the apply function parameter 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 whether this Class object is an interface if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } 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. */ 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; /* * * Generates a bytecode file for the specified proxy class */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); try { 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 limitation * exceeded). */ throw new IllegalArgumentException(e.toString()); } } }
After a series of checks, call ProxyGenerator.. Generateproxyclass to generate bytecode files.
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) { ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2); // The real method used to generate proxy class bytecode files is here final byte[] var4 = var3.generateClassFile(); // Save bytecode file of proxy class if(saveGeneratedFiles) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { try { int var1 = var0.lastIndexOf(46); Path var2; if(var1 > 0) { Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar), new String[0]); Files.createDirectories(var3, new FileAttribute[0]); var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class"); } else { var2 = Paths.get(var0 + ".class", new String[0]); } Files.write(var2, var4, new OpenOption[0]); return null; } catch (IOException var4x) { throw new InternalError("I/O exception saving generated file: " + var4x); } } }); } return var4; }
generateClassFile method for generating proxy class bytecode file:
private byte[] generateClassFile() { //The following series of addProxyMethod methods add the methods in the interface and Object to the proxy method (proxyMethod) this.addProxyMethod(hashCodeMethod, Object.class); this.addProxyMethod(equalsMethod, Object.class); this.addProxyMethod(toStringMethod, Object.class); Class[] var1 = this.interfaces; int var2 = var1.length; int var3; Class var4; //Get all the methods in the interface and add them to the proxy method for(var3 = 0; var3 < var2; ++var3) { var4 = var1[var3]; Method[] var5 = var4.getMethods(); int var6 = var5.length; for(int var7 = 0; var7 < var6; ++var7) { Method var8 = var5[var7]; this.addProxyMethod(var8, var4); } } Iterator var11 = this.proxyMethods.values().iterator(); List var12; while(var11.hasNext()) { var12 = (List)var11.next(); checkReturnTypes(var12); } Iterator var15; try { //Generate constructor for proxy class this.methods.add(this.generateConstructor()); var11 = this.proxyMethods.values().iterator(); while(var11.hasNext()) { var12 = (List)var11.next(); var15 = var12.iterator(); while(var15.hasNext()) { ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next(); this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, "Ljava/lang/reflect/Method;", 10)); this.methods.add(var16.generateMethod()); } } this.methods.add(this.generateStaticInitializer()); } catch (IOException var10) { throw new InternalError("unexpected I/O Exception", var10); } if(this.methods.size() > '\uffff') { throw new IllegalArgumentException("method limit exceeded"); } else if(this.fields.size() > '\uffff') { throw new IllegalArgumentException("field limit exceeded"); } else { this.cp.getClass(dotToSlash(this.className)); this.cp.getClass("java/lang/reflect/Proxy"); var1 = this.interfaces; var2 = var1.length; for(var3 = 0; var3 < var2; ++var3) { var4 = var1[var3]; this.cp.getClass(dotToSlash(var4.getName())); } this.cp.setReadOnly(); ByteArrayOutputStream var13 = new ByteArrayOutputStream(); DataOutputStream var14 = new DataOutputStream(var13); try { var14.writeInt(-889275714); var14.writeShort(0); var14.writeShort(49); this.cp.write(var14); var14.writeShort(this.accessFlags); var14.writeShort(this.cp.getClass(dotToSlash(this.className))); var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy")); var14.writeShort(this.interfaces.length); Class[] var17 = this.interfaces; int var18 = var17.length; for(int var19 = 0; var19 < var18; ++var19) { Class var22 = var17[var19]; var14.writeShort(this.cp.getClass(dotToSlash(var22.getName()))); } var14.writeShort(this.fields.size()); var15 = this.fields.iterator(); while(var15.hasNext()) { ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next(); var20.write(var14); } var14.writeShort(this.methods.size()); var15 = this.methods.iterator(); while(var15.hasNext()) { ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next(); var21.write(var14); } var14.writeShort(0); return var13.toByteArray(); } catch (IOException var9) { throw new InternalError("unexpected I/O Exception", var9); } } }
After the bytecode is generated, defineClass0 is used to parse the bytecode and generate the Class object of Proxy. After understanding the dynamic generation process of proxy class, what kind of proxy class is produced and who will execute this proxy class.
Where, in proxygenerator saveGeneratedFiles in the generateproxyclass function is defined as follows. It refers to whether to save the generated proxy class file. By default, false is not saved.
In the previous example, we modified this system variable:
System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
As shown in the figure, two files named Proxy1.class file for class.
Dynamic agent flow chart:
Original link: https://www.jianshu.com/p/9bcac608c714