The content of this article is compiled from the wild architect of Erudite Valley
Introduction to Dynamic Proxy
Proxy mode is a commonly used design mode. Its characteristic is that the proxy class and the delegate class have the same interface. The proxy class is mainly responsible for preprocessing messages for the delegate class, filtering messages, forwarding messages to the delegate class, and processing messages after the event.
Users can further structure the diagram and complete the Proxy mode by their own coding. This implementation is called a static proxy.
Java provides the java.lang.reflect.Proxy class and the InvocationHandler interface. With reflection, dynamic proxy can be realized. The proxy class and proxy operation of the static proxy are coded in advance, and the proxy structure cannot be modified during the running process. The proxy and proxy operation of the dynamic proxy are dynamically generated during the running process, and the proxy structure can be modified during the running process, which conforms to the object-oriented opening and closing principle.
The most important reason is to enhance the method without changing the method of the target object. For example, we want to add logging to the method call, or intercept the method call, etc...
Dynamic proxy is used to add code without modifying the original code. AOP and transactions in spring are all implemented using dynamic proxy. We use dynamic proxy every day but we don’t know it.
Three elements of dynamic proxy
- An interface needs to be defined. The java dynamic proxy class can only proxy interfaces (abstract classes are not supported). If there is no interface, use cjlib
- Need an implementation class to inherit this interface
- Write an enhanced class to implement the InvocationHandler interface, and all proxy classes need to implement the invoke method of the InvocationHandler interface
one example
Define an interface first
Define an interface for overseas purchasing agents
/** * Overseas Shopping */ public interface Buying { public String buy(); }
write an implementation class
implement class implement interface
public class BuyingImpl implements Buying { @Override public String buy() { System.out.println("start logical processing"); return "bought a hammer"; } }
write an adder class
Write an enhanced class, mainly to wrap an object that needs to be enhanced, that is, our BuyingImpl, and implement the InvocationHandler interface, and write the enhanced implementation in the invoke method
/** * Enhanced Overseas Purchasing * Note the implementation of InvocationHandler * Dynamic proxy classes can only proxy interfaces (abstract classes are not supported), and proxy classes need to implement the InvocationHandler class and implement the invoke method. * The invoke method is what needs to be called when calling all methods of the proxied interface. */ public class BuingHandler implements InvocationHandler { /** * Wrap a target object that needs to be augmented */ private Object targetObject; public BuingHandler(Object targetObject){ this.targetObject = targetObject; } /** * Get proxy class * * @return */ public Object getProxy() { /** * This method is used to generate a dynamic proxy class instance for a specified class loader, a set of interfaces, and a call handler * The first parameter specifies the class loader that generates the proxy object, which needs to be specified as the same class loader as the target object * The second parameter needs to implement the same interface as the target object, so you only need to get the implementation interface of the target object * The third parameter indicates which InvocationHandler's invoke method needs to be executed when these intercepted methods are intercepted * Returns a proxy object based on the passed in target */ return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this); } /** * The associated method of this implementation class will be executed when it is called * InvocationHandler interface method * * @param proxy represents the proxy object * @param method Indicates the method that was called on the original object * @param args Indicates the parameters of the method * @return Returns an interface of the object * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Pre-enhancement"); //Reflection calls the original method that needs to be enhanced Object value = method.invoke(targetObject, args); System.out.println("Post Enhancement"); return value; } }
It should be noted that method is the method we need to enhance, and args is the parameter array we need to enhance
Write the Main method
public static void main(String[] args) { //Create the BuingHandler class BuingHandler buingHandler = new BuingHandler(new BuyingImpl()); //Get proxy object Buying buying = (Buying) buingHandler.getProxy(); //call specific interface String value = buying.buy(); System.out.println(value); }
output
Pre-enhancement start logical processing Post Enhancement bought a hammer
We implemented dynamic proxy in this way, and we made enhancements without modifying the original code
We implemented the other and post-enhanced
Let's run it to see the interface object
We see that the actual object is $Proxy0, we found that the dynamic proxy has changed an object for us, we need to study how it is implemented
Source code implementation
Read the source code first to find the entrance, if there is no entrance, it is like a headless fly, and the fly does not bite seamless eggs
The following content is a bit much, and it is also a bit convoluted, please follow the train of thought to analyze it a little bit
1. First find the entrance
What we create the proxy object calls is
Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
So let's start with Proxy.newProxyInstance
2. newProxyInstance method
Enter inside the newProxyInstance method
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { //The enhanced implementation cannot be empty, and an exception will be thrown if it is empty Objects.requireNonNull(h); //clone the interface array final Class<?>[] intfs = interfaces.clone(); //Entry permission check final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. * ********Core code entry ************* * Find or generate a specific proxy class object */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. * invokes its constructor with the specified call handler */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } // Find the constructor whose parameter is InvocationHandler from the proxy class object final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; // Check whether the constructor is Public decoration, if not, force conversion to be accessible. if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } //Through reflection, use h as a parameter, instantiate the proxy class, and return the proxy class 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); } }
The core method of the above code is
Class<?> cl = getProxyClass0(loader, intfs);
Found the core method and continued to deepen
3. getProxyClass0 method entry
Method to generate a proxy object
/** * Generate a proxy object * Generate a proxy class. Must call the checkProxyAccess method * to perform permission checks before calling this. */ private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { //The number of interfaces cannot be greater than 65535, otherwise an error will be reported. The specific reason is not clear if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } //Generate proxy bytecode file according to class loader // If the proxy class defined by the given loader implementing //If the interface exists in the cache, we will get it from the cache // the given interfaces exists, this will simply return the cached copy; //Otherwise, it will create the proxy class via proxyClassFactory // otherwise, it will create the proxy class via the ProxyClassFactory return proxyClassCache.get(loader, interfaces); }
This piece of code is to get the proxy object from the cache, and the core code is still inside proxyClassCache.get(loader, interfaces);
Because proxyClassCache is a class of WeakCache, let's learn about WeakCache first
4. WeakCache class
WeakCache method declaration
In this method, it is read directly from a cache called proxyClassCache. Take a look at the statement of this cache:
/** * a cache of proxy classes * The class bytecode file of the cache proxy, if not created using ProxyClassFactory */ private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
There are three classes involved: WeakCache, KeyFactory, and ProxyClassFactory. The latter two classes are static internal classes of the Proxy class. From the class name, it can be roughly guessed that keyFactory is used to produce keys, and ProxyClassFactory is used to produce proxy classes. object, which will be mentioned later.
The approximate structure of the WeakCache class
final class WeakCache<K, P, V> { private final ReferenceQueue<K> refQueue = new ReferenceQueue<>(); // the key type is Object for supporting null key // The type of the key is Object, which supports null key. The null key here does not really use null as the key, but a new Objdec() object instance. ConcurrentHashMap does not allow key or value null, while HashMap can. ConcurrentHashMap is thread safe, HashMap is not. private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>(); private final ConcurrentMap<Supplier<V>, Boolean> reverseMap = new ConcurrentHashMap<>(); private final BiFunction<K, P, ?> subKeyFactory; private final BiFunction<K, P, V> valueFactory; // Construction method public WeakCache(BiFunction<K, P, ?> subKeyFactory, BiFunction<K, P, V> valueFactory) { this.subKeyFactory = Objects.requireNonNull(subKeyFactory); this.valueFactory = Objects.requireNonNull(valueFactory); } //Core entry method We will introduce this class next public V get(K key, P parameter) { } ...
In the source code above, it is stated that the core method of the proxy object is get, and we found that the key is the loader class loader in combination with the context, and the parameter is the interface array interfaces
5,proxyClassCache.get
This object is to obtain the bytecode object from the cache, the key is the interface, and the value is the bytecode file of the object. If the given interface exists, the bytecode file is returned. If it does not exist, the proxyClassFactory is called to create a proxy class to create
/** * return proxyClassCache.get(loader, interfaces); * <p> * The core method of obtaining the proxy object * * @param key class loader loader * @param parameter array of interfaces interfaces * @return */ public V get(K key, P parameter) { //The interface array cannot be empty, otherwise an exception will be thrown Objects.requireNonNull(parameter); // remove obsolete entries expungeStaleEntries(); // Generate a cache key object instance, if key = null, cacheKey = new Object(); Object cacheKey = WeakCache.CacheKey.valueOf(key, refQueue); // lazily install the 2nd level valuesMap for the particular cacheKey // Read the cache data valuesMap of the specified cacheKey from the cache map ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey); if (valuesMap == null) { //If valuesMap is null, add // putIfAbsent method explanation: If the value exists, return the value, and do not make any changes to the original value, if it does not exist, add it, and return null //map.putIfAbsent is a new method in map, if it exists, it will return, if there is no put, it will return ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>()); //assignment if (oldValuesMap != null) { valuesMap = oldValuesMap; } } // create subKey and retrieve the possible Supplier<V> stored by that // subKey from valuesMap //To get subKey, the static inner class of Proxy mentioned above is used here KeyFactory:subKeyFactory.apply(ket,parameter) Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); // Get supplier from valuesMap Supplier<V> supplier = valuesMap.get(subKey); WeakCache.Factory factory = null; while (true) { if (supplier != null) { // supplier might be a Factory or a CacheValue<V> instance // 4. Obtain the proxy class object from the factory V value = supplier.get(); if (value != null) { //5. Return 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 //1. Instantiation factory if (factory == null) { factory = new WeakCache.Factory(key, parameter, subKey, valuesMap); } if (supplier == null) { //2. Save the supplier to the valuesMap supplier = valuesMap.putIfAbsent(subKey, factory); if (supplier == null) { // successfully installed Factory // 3. Assignment supplier = factory; } // else retry with winning supplier } else { //If both subKey and supplier match, replace supplier with newly generated factory if (valuesMap.replace(subKey, supplier, factory)) { // successfully replaced // cleared CacheEntry / unsuccessful Factory // with our Factory //Substituting successful assignment supplier = factory; } else { // retry with current supplier //Retry with the current supplier supplier = valuesMap.get(subKey); } } } }
Because Proxy.newProxyInstance is executed for the first time in the program, when the while loop starts, the supplier and valuesMap are all null. Under this premise, I made a number for the execution order of the code, executed from 1-5.
You can see that the result is returned in step 5, that is, line 47 of the source code, so the proxy class object is generated in step 4, that is, line 43. And you can also find that the supplier is the factory from step 3, which is line 65.
Then, let's analyze the Factory.get method.
6. Factory.get method
The Factory class is the inner class of WeakCache. In addition to the construction method in this class, it is the get method. The following is the implementation of this code:
/** * Factory Implement class Supplier interface */ private final class Factory implements Supplier<V> { //class loader loader private final K key; array of interfaces interfaces private final P parameter; //The subkey here is the KeyFactory above, you can see the WeakCache method declaration private final Object subKey; //The provider's MAP key is KeyFactory, and the value is the Factory itself private final ConcurrentMap<Object, Supplier<V>> valuesMap; //Construction method 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 //Check if supplier is not self return 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 //define a new object V value = null; try { /** * valueFactory It is the valueFactory property of WeakCache, because Factory is the internal class of WeakCache, so you can directly access the valueFactory property of WeakCache * We can go back and look at the simple structure of the fourth and fifth proxyClassCache.get and WeakCache. Note that valueFactory is found to be ProxyClassFactory * In this step, the proxy object is generated */ 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 //check object is not empty assert value != null; // wrap value with CacheValue (WeakReference) WeakCache.CacheValue<V> cacheValue = new WeakCache.CacheValue<>(value); // put into reverseMap //cache proxy object reverseMap.put(cacheValue, Boolean.TRUE); // try replacing us with CacheValue (this should always succeed) //and replace valuesMap with the latest generated object 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 object return value; } }
Our core focus is
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
The valueFactory here is the static internal class ProxyClassFactory of Proxy, as mentioned above, so let's analyze the apply method of ProxyClassFactory.
7. ProxyClassFactory.apply method
/** * A factory method that generates, defines and returns a proxy class object using a given class loader and an array of interface classes * A factory function that generates, defines and returns the proxy class given * the ClassLoader and array of interfaces. */ private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> { // prefix for all proxy class names //The prefix of all proxy class objects This answers why proxy classes have $Proxy private static final String proxyClassNamePrefix = "$Proxy"; // next number to use for generation of unique proxy class names //The next number used to generate a unique proxy class name private static final AtomicLong nextUniqueNumber = new AtomicLong(); /** * Start our core method apply * @param loader class loader * @param interfaces interface array * @return */ @Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); //interface check loop for (Class<?> intf : interfaces) { /* * Verify that the class loader resolves the name of this * interface to the same Class object. */ Class<?> interfaceClass = null; try { //Load the interface class and get the class object of the interface class. The second parameter is false to indicate that it will not be instantiated interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } //check if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } /* * Verify that the Class object actually represents an * interface. * Verify whether it is an interface or not and report an error */ if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } /* * Verify that this interface is not a duplicate. * Verify that this interface is not repeated, if it is repeated, an error will be reported */ if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } } //The package name of the proxy class String proxyPkg = null; // package to define proxy class in //access permission 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 the interface is public, skip it. Our interface will basically not go here. 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 //If there is no public interface, use the package prefix of com.sun.proxy //Similar to com.sun.proxy.$Proxy0 proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } /* * Choose a name for the proxy class to generate. * Generate the class name of the proxy class */ //Generate the serial number of the proxy class long num = nextUniqueNumber.getAndIncrement(); //Fully qualified name of generated proxy class String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * Generate the specified proxy class. * Generate proxy class file * This is the core method of generating */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { //Return proxy class object 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()); } } }
On line 111 of the code, the class file of the proxy class is generated, and the proxy class object we need is returned on line 115. So how do you find the generated proxy class file?
So far, we have followed the core process of dynamic proxy. We explained why proxy classes all have $Proxy, and how the following sequence numbers come from.
The core code that generates the code is
byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags);
ProxyGenerator is the core code for generating proxy classes based on the proxy name interface. We will not follow up on it. We will go in later when we have time. It contains knowledge of bytecode operations. It is also under the sun.misc package and is generally not open source. , if you need to download the source code of the sun package, it will not be open source after 1.8.
View generated proxy classes
We finally followed the ProxyGenerator class above. ProxyGenerator is the core code for generating bytecode files. We want to see what to do with the generated bytecode. We will generate and output it ourselves.
look at the code
//Generate proxy bytecode array file Pass in an interface array byte[] proxyClassFile = ProxyGenerator.generateProxyClass("com.sun.proxy", new Class[]{Buying.class}, 1); //Convert the byte array into a class file and output it locally FileOutputStream fos = new FileOutputStream(new File("d:/com.sun.proxy.class")); fos.write(proxyClassFile); fos.flush(); fos.close();
We decompile the following com.sun.proxy.class
//Inherited the Proxy class and implemented the Buying interface public class proxy extends Proxy implements Buying { private static Method m1; private static Method m2; private static Method m3; private static Method m0; //The construction method directly calls the parent class, that is, the construction method of Proxy, and the parameter paramInvocationHandler is our BuingHandler instantiation object handler public proxy(InvocationHandler paramInvocationHandler) { super(paramInvocationHandler); } /** * Implement the equals method * @param var1 * @return */ public final boolean equals(Object var1) { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } /** * Implement the toString method * @return */ 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); } } //buy that implements the Buying interface public final String buy() { try { /** * Here h is our BuingHandler instance * Call the BuingHandler object we passed in in the parent class Proxy */ return (String)super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } /** * Implemented the hashCode method * @return */ 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); } } //Static code block, do initialization static { try { //Obtain the equals method of the Object object method object through reflection m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); //Through reflection, get the toString method of the Object object method object m2 = Class.forName("java.lang.Object").getMethod("toString"); //Obtain the buy method of the Buying object method object through reflection m3 = Class.forName("com.test.proxy.Buying").getMethod("buy"); //Through reflection, get the hashCode method of the Object object method object m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
The code to instantiate the proxy class is: cons.newInstance(new Object[]{h}). Here, the constructor of the proxy class object is called through reflection, and the parameter h (our BuingHandler instantiation object handler) is passed in.
This construction method is the construction method in the above decompiled code, and the construction method in the above decompiled code calls the construction method of the Proxy class. Let’s take a look at the construction method of the Proxy class:
protected InvocationHandler h; protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; }
Here, the handler we passed in is directly assigned to InvocationHandler h. The super.h in the above decompiled code is the handler we passed in.
So the proxy.buy(); method will call the invoke method of the BuingHandler class when it is executed.
Well, our source code analysis is over here.
This article was released by the teaching and research team of Chuanzhi Education Erudite Valley Wild Architects.
If this article is helpful to you, please pay attention and like it; if you have any suggestions, you can also leave a comment or private message. Your support is my motivation to persist in creation.
Please indicate the source!