Dynamic agent
Use a simple example to describe a dynamic agent. You want to rent a house. Generally, you need to look for a house everywhere. It's very hard. You want to lie at home and pay the money, So you find an agent (intermediary). The agent will find the house and discuss with the landlord. Just come and look at the house and sign the contract and pay the money. This is the role of the agent. (be careful when looking for an intermediary. An intermediary is different from a code, and the code will not deceive people.)
In the actual code, when you have an existing method and you want to expand its functions without modifying it, you can use dynamic proxy. A typical case is Spring AOP.
Example code
/** * @author ddd * @create 2021-06-25 19:21 * #desc Dynamic proxy requires a behavior interface **/ public interface action { public void helloworld(); }
/** * @author ddd * @create 2021-06-25 * #desc proxy class * **/ public class agent implements InvocationHandler { private action action; public agent(action action){ this.action=action; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(" Hard to find a room! " ); Object getmethod=method.invoke(action,args); return getmethod; } }
/** * @author ddd * @create 2021-06-25 * @desc customer **/ public class Custom implements action{ @Override public void helloworld() { System.out.println(" Look at the house, sign the contract and pay the money! "); } }
/** * @author ddd * @create 2021-06-25 * @desc Run class **/ public class Main { public static void main(String[] args) { System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); action action=new Custom(); InvocationHandler handlerProxy=new agent(action); /* This step will generate a dynamic proxy class in memory */ action actionInstance = (My.DesignPattern.Agent.Jdk.action) Proxy.newProxyInstance(handlerProxy.getClass().getClassLoader(), action.getClass().getInterfaces(), handlerProxy); actionInstance.helloworld(); } }
Proxy.newProxyInstance()
It is said that dynamic proxy is very important and easy to use. The proxy class implements a behavior interface, and the proxy class implements the InvocationHandler interface to call proxy Newproxyinstance() can generate a proxy class. How exactly is the proxy class generated? Let's go to the source proxy newProxyInstance()
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { /** * The passed in proxy class is not empty */ Objects.requireNonNull(h); // Use cloning to get the interface, new knowledge, what is cloning? final Class<?>[] intfs = interfaces.clone(); // System security check final SecurityManager sm = System.getSecurityManager(); if (sm != null) { // Check proxy access can be understood by looking at the name and checking whether it can be used as a proxy checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. * * The above English is the source code note * Find or generate a proxy * Hey! Interesting. There are two ways to find, or generate, instructions */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. * * Source code note: use the constructor to generate the proxy class */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } /** * Generate proxy classes using construction methods */ final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } /** * Returns the new object */ 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); } }
getProxyClass0(loader, intfs)
There is a line of code above. The comment is to find or generate a proxy class. Let's the source code of this method
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // If the proxy class defined by the given loader implementing // the given interfaces exists, this will simply return the cached copy; // otherwise, it will create the proxy class via the ProxyClassFactory // Another layer of encapsulation, and then enter proxyclasscache get return proxyClassCache.get(loader, interfaces); }
proxyClassCache.get(loader, interfaces)
public V get(K key, P parameter) { Objects.requireNonNull(parameter); expungeStaleEntries(); /** * Search from the cache, eh, this is to find a proxy class, but we haven't generated it yet * In the future, there must be an operation to generate proxy classes and put them into the cache, * Using the cache, the re access speed will be very fast * * key What is it, handlerproxy getClass(). getClassLoader() */ Object cacheKey = CacheKey.valueOf(key, refQueue); // lazily install the 2nd level valuesMap for the particular cacheKey ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey); if (valuesMap == null) { ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>()); if (oldValuesMap != null) { valuesMap = oldValuesMap; } } // create subKey and retrieve the possible Supplier<V> stored by that // subKey from valuesMap Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); /** * Supplier It is necessary to understand the producer interface and functional interface * This is used for production agent classes */ Supplier<V> supplier = valuesMap.get(subKey); Factory factory = null; while (true) { if (supplier != null) { // supplier might be a Factory or a CacheValue<V> instance /** * If it is not empty, you can get the proxy class * The first entry into valuesMap is empty, map Get() failed to get * So the loop condition is while (true) * Later, a Factory will be assigned to supplier, and then through Factory Get() to get */ V value = supplier.get(); if (value != null) { return value; } } // else no supplier in cache // or a supplier that returned null (could be a cleared CacheValue // or a Factory that wasn't successful in installing the CacheValue) // lazily construct a Factory if (factory == null) { factory = new Factory(key, parameter, subKey, valuesMap); } if (supplier == null) { supplier = valuesMap.putIfAbsent(subKey, factory); if (supplier == null) { // successfully installed Factory /** * If */ supplier = factory; } // else retry with winning supplier } else { if (valuesMap.replace(subKey, supplier, factory)) { // successfully replaced // cleared CacheEntry / unsuccessful Factory // with our Factory supplier = factory; } else { // retry with current supplier supplier = valuesMap.get(subKey); } } } }
Factory:factory.get()
private final class Factory implements Supplier<V> { private final K key; private final P parameter; private final Object subKey; private final ConcurrentMap<Object, Supplier<V>> valuesMap; Factory(K key, P parameter, Object subKey, ConcurrentMap<Object, Supplier<V>> valuesMap) { this.key = key; this.parameter = parameter; this.subKey = subKey; this.valuesMap = valuesMap; } @Override public synchronized V get() { // serialize access // re-check Supplier<V> supplier = valuesMap.get(subKey); if (supplier != this) { // something changed while we were waiting: // might be that we were replaced by a CacheValue // or were removed because of failure -> // return null to signal WeakCache.get() to retry // the loop return null; } // else still us (supplier == this) // create new value V value = null; try { /** * The key code is only in this sentence, valuefactory Apply (key, parameter) is not empty * Then go to valuefactory apply(key, parameter) */ value = Objects.requireNonNull(valueFactory.apply(key, parameter)); } finally { if (value == null) { // remove us on failure valuesMap.remove(subKey, this); } } // the only path to reach here is with non-null value assert value != null; // wrap value with CacheValue (WeakReference) CacheValue<V> cacheValue = new CacheValue<>(value); // put into reverseMap reverseMap.put(cacheValue, Boolean.TRUE); // try replacing us with CacheValue (this should always succeed) if (!valuesMap.replace(subKey, this, cacheValue)) { throw new AssertionError("Should not reach here"); } // successfully replaced us with new CacheValue -> return the value // wrapped by it return value; } }
Proxy:ProxyClassFactory:apply()
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) { /** * Access to all interface information and convenience */ 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 { /** * Class.forName The function is to require the JVM to find and load the specified class, which is equivalent to instantiating the interface * ClassLoader loader It is the loader of our own class that we first passed in */ interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } /** * Judge whether the instantiation of the current class is generated by the same loader as the class we created */ 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 // Under which path is the proxy class placed // Permissions for interfaces 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) { /** * Get permission for interface */ int flags = intf.getModifiers(); /** * If it is public, it will be placed under the same path */ 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. */ 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 limitations * exceeded). */ throw new IllegalArgumentException(e.toString()); } } }
Generated proxy class
The above process generates the proxy class. Where is the proxy class we finally generate?
In the initial example, we added such a line of code in the Main class, which can save the proxy class we generated locally
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
After running, you can see
helloworld() of proxy class
Let's look at the helloworld method of the proxy class
public final class $Proxy0 extends Proxy implements action{ --- /* At first, in the original main method, InvocationHandler handlerProxy=new agent(action); action actionInstance = (My.DesignPattern.Agent.Jdk.action) Proxy.newProxyInstance(handlerProxy.getClass().getClassLoader(), action.getClass().getInterfaces(), handlerProxy); new agent(action) is passed through the $Proxy0 constructor, which is the implementation class of InvocationHandler So the invocationhandle in the Proxy class is the Proxy class we wrote */ public $Proxy0(InvocationHandler var1) throws { super(var1); } public final void helloworld() throws { try { /** * Invoke of the h variable of the parent class is called, that is, invoke in the agent class */ super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } --- }
summary
1. Based on reflection, dynamic agent can get all the information of a class when the program runs.
2. Reflect the JVM based class loading mechanism and the storage of class and interface templates in the JVM memory model.
3. Dynamic proxy dynamically generates a proxy object at runtime through the above technologies.
4. Why is it dynamic? Because the parameter passed in by the agent is the action interface, you can pass in any class that implements the action interface to complete the agent at runtime, which is more flexible.
5. This article is only for personal understanding, and because reflection involves JVM knowledge, it has too much content and is not further deepened.