Javabase java reflection mechanism
Application of reflection: dynamic proxy
Dynamic agent overview
- Principle of agent design pattern:
- Wrap the object with a proxy object, and then replace the original object with the proxy object. Any call to the original object must be operated through the proxy object. The proxy object determines whether and when to transfer the method call to the original object
- Static proxy:
- The characteristic is that the proxy class and the class of the target object are determined during compilation, which is not conducive to the expansion of the program. At the same time, each proxy class can only serve one interface, which leads to too many proxy classes in program development. The ideal way is to complete the full proxy function through a proxy class.
- Dynamic proxy refers to the method that the client calls other objects through the proxy class, and it is the proxy object that dynamically creates the target class when the program runs.
- Application of dynamic agent:
- debugging
- Remote method call
- Compared with static agents, dynamic agents have some advantages:
- All methods declared in the abstract role (Interface) are transferred to a centralized method of the calling processor. In this way, many methods can be processed more flexibly and uniformly
Examples of dynamic agents
Static proxy
-
step
- Create an interface class called closhfactory to define the method of the required proxy
- The mycloshfactory proxy interface is created by the mycloshfactory class
- Create proxy class ProxyClothFactory to implement interface ClothFactory
- In the proxy class, add the attribute of the proxy class mycloshfactory as a follow-up from the proxy class to control when to call the proxied class
- In the proxy class, add a method to assign a value to the proxy class attribute
- In the proxy method of the proxy class, the method of the proxy class is called through the properties of the proxy class
-
code
-
package com.jl.java.base.proxy; /** * Static proxy * Features: the proxy class and the proxy class are determined during compilation * @author jiangl * @version 1.0 * @date 2021/5/7 15:14 */ /** * 1.Create an interface so that both the proxy class and the proxied class implement this interface, and create an interface class closefactory to define the methods of the required proxy */ interface ClothFactory{ void produceCloth(); } /** * 2.The mycloshfactory proxy interface is created by the mycloshfactory class */ class MyClothFactory implements ClothFactory{ @Override public void produceCloth() { System.out.println("Produce clothes"); } } /** * 3.Create proxy class ProxyClothFactory to implement interface ClothFactory */ class ProxyClothFactory implements ClothFactory{ /** * 4.In the proxy class, add the attribute of the proxy class mycloshfactory as a follow-up from the proxy class to control when to call the proxied class */ private ClothFactory myClothFactory; /** * 5.In the proxy class, add a method to assign a value to the proxy class attribute * @param myClothFactory */ public ProxyClothFactory(MyClothFactory myClothFactory) { this.myClothFactory = myClothFactory; } /** * 6.In the proxy method of the proxy class, the method of the proxy class is called through the properties of the proxy class */ @Override public void produceCloth() { System.out.println("Obtain raw materials"); myClothFactory.produceCloth(); System.out.println("complete"); } } public class StaticProxyTest { public static void main(String[] args) { ClothFactory proxyClothFactory = new ProxyClothFactory(new MyClothFactory()); proxyClothFactory.produceCloth(); } }
-
-
Disadvantages of static proxy
- In the compilation period, the proxy class and the proxy class are determined. If they are widely used, there will be many proxy classes in the system
Dynamic agent (JDK dynamic agent)
-
Create a Human interface class and define the method of the required proxy. Both the proxy class and the proxy class should implement this method
-
Create a surrogate class Student to implement the Human interface and rewrite the methods in the interface
-
Via proxy Newproxyinstance (classloader, loader, class <? > [] interfaces, invocationhandler h) method to generate proxy class
- Classloader: the class loader of the proxy class, which is used to dynamically create a proxy class through the class loader
- Class<?> [] interfaces: arrays of all interfaces implemented by the proxy class. Because the proxy class may implement multiple interfaces, it is an array. The proxy class needs to implement these interfaces and override the methods in the interface to proxy the proxy class.
- InvocationHandler h: callback method object. The proxy class calls and enhances the functions of the proxy class through the invoke method in the callback method object (you can write the code logic to enhance the functions of the proxy class in invoke(Object proxy, Method method, Object[] args))
- Object proxy: This is the object of the proxy class
- Method method: This is the method to proxy
- Object[] args: the parameter group of the proxy method to be
- In the InvocationHandler h class, you need to add an attribute of the proxied class (the type of this attribute should be Object for universality), which is used in the invoke() method through method Invoke method to call the method of the proxy class
-
Finally, create a proxy class object first
- Via proxy Newproxyinstance (classloader, loader, class <? > [] interfaces, invocationhandler h) method obtains the object of the proxy class, which can be assigned to Human proxy (upward transformation, class polymorphism)
- Human proxy = Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
- By calling the proxy class object proxy Eat () calls the method of the proxied class to implement the proxy mode
- Via proxy Newproxyinstance (classloader, loader, class <? > [] interfaces, invocationhandler h) method obtains the object of the proxy class, which can be assigned to Human proxy (upward transformation, class polymorphism)
-
code
-
package com.jl.java.base.proxy; import org.junit.Test; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author jiangl * @version 1.0 * @date 2021/5/7 15:19 */ interface Human{ String getBelief(); void eat(String food); } /** * Proxy class */ class Student implements Human{ @Override public String getBelief() { System.out.println("study hard and make progress every day"); return "study"; } @Override public void eat(String food) { System.out.println("Eat nutritious food"); } } /** * What problems need to be solved in order to realize dynamic agent? * Question 1: how to dynamically create a proxy class and its object according to the proxy class loaded into memory * Question 2: how to dynamically call the method with the same name in the proxy class when calling the method through the object of the proxy class */ class ProxyFactory{ /** * Call this method to return an object of proxy class to solve the problem * @param obj Object of the proxied class * @return Returns the object of the proxy class */ public static Object getProxyInstance(Object obj){ MyInvocationHandler handler = new MyInvocationHandler(); handler.bind(obj); return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler); } } class MyInvocationHandler implements InvocationHandler{ /** * You need to use the object of the proxy class for assignment */ private Object obj; public void bind(Object obj){ this.obj = obj; } /** * When method a is called through the object of the proxy class, the following methods will be called automatically * The function of method a to be executed by the proxy class is declared in invoke() * @param proxy Object of proxy class * @param method The method called by the proxy class object is also used as the method to be called by the proxy class object * @param args Method input * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("---agent---"); //obj: proxied class object Object returnValue = method.invoke(obj, args); //The return value of the above method is the return value of invoke() of the current class return returnValue; } } public class DynamicProxyTest { @Test public void test(){ Object proxyInstance1 = ProxyFactory.getProxyInstance(new Student()); System.out.println(proxyInstance1.getClass()); Class<?>[] interfaces = proxyInstance1.getClass().getInterfaces(); for(Class inter : interfaces){ System.out.println(inter); } Human proxyInstance = (Human) ProxyFactory.getProxyInstance(new Student()); proxyInstance.getBelief(); proxyInstance.eat("Apple"); } }
-
Source code analysis (JDK dynamic agent)
-
Premise:
-
Via Proxy The class created by the nexproxyinstance () method actually implements the interface of the Proxy class and inherits the anonymous class of $Proxy0 with the Proxy class
-
code
-
@Test public void test(){ Object proxyInstance1 = ProxyFactory.getProxyInstance(new Student()); System.out.println(proxyInstance1.getClass()); System.out.println(proxyInstance1.getClass().getSuperclass()); Class<?>[] interfaces = proxyInstance1.getClass().getInterfaces(); for(Class inter : interfaces){ System.out.println(inter); } }
-
result
- class com.jl.java.base.proxy.$Proxy5
class java.lang.reflect.Proxy
interface com.jl.java.base.proxy.Human
- class com.jl.java.base.proxy.$Proxy5
-
-
Source code:
-
Generate proxy class source code newProxyInstance() method
-
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { //Determine whether the callback method object is empty if (h == null) { throw new NullPointerException(); } /** * Finds or generates the specified proxy class. */ Class<?> cl = getProxyClass0(loader, interfaces); /* * The proxy class is generated by reflection through the constructor of the proxy class, and the method to be called is written into the proxy class */ try { final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; SecurityManager sm = System.getSecurityManager(); if (sm != null && java.lang.reflect.Proxy.ProxyAccessHelper.needsNewInstanceCheck(cl)) { return AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { return newInstance(cons, ih); } }); } else { return newInstance(cons, ih); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); } }
-
-
In the newProxyInstance() method, generate an anonymous proxy class through the getProxyClass0(loader,interfaces) method
-
Detected by Java Security
-
//Java security device detection SecurityManager sm = System.getSecurityManager(); if (sm != null) { final int CALLER_FRAME = 3; // 0: Reflection, 1: getProxyClass0 2: Proxy 3: caller final Class<?> caller = Reflection.getCallerClass(CALLER_FRAME); final ClassLoader ccl = caller.getClassLoader(); checkProxyLoader(ccl, loader); ReflectUtil.checkProxyPackageAccess(ccl, interfaces); }
-
-
Judge whether the implementation interface of the proxy class exceeds the maximum value of int. if it exceeds the maximum value, an exception IllegalArgumentException will be thrown
-
//Judge whether the interface of the proxy class exceeds the maximum int value if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); }
-
-
Create a proxy class variable Class proxyClass to return
-
//Proxy class variable Class proxyClass = null;
-
-
Get all interfaces (interface array) of the proxy class, and use these interface names as the key s of the proxy class cache. It is used to obtain again without secondary creation and optimize performance
-
//Gets all the interface names used as keywords for proxy class caching String[] interfaceNames = new String[interfaces.length]; //Create a HashSet of proxy class interface for de duplication Set interfaceSet = new HashSet(); //Loop through all interfaces of the proxy class for (int i = 0; i < interfaces.length; i++) { //Check whether the actual class of the interface loaded through class loading is the same as the incoming interface class String interfaceName = interfaces[i].getName(); Class interfaceClass = null; try { interfaceClass = Class.forName(interfaceName, false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != interfaces[i]) { throw new IllegalArgumentException( interfaces[i] + " is not visible from class loader"); } //Verify whether the loaded interface class is really an interface if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } //Determine whether there are duplicate interfaces if (interfaceSet.contains(interfaceClass)) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } interfaceSet.add(interfaceClass); interfaceNames[i] = interfaceName; }
-
-
Convert the interface array into a List as a key
-
//Convert the character array of interface name to list < string > /** * It is sufficient to use the string representation of the proxy interface as the key in the proxy class cache (rather than their class object), * Because we require the proxy interface to resolve by name through the provided class loader, its advantage is that using the string representation of the class will lead to implicit weak references to the class. */ Object key = Arrays.asList(interfaceNames);
-
-
According to the query of the class loader in the cache loaderToCache map, this class loads the maps of all proxy classes
-
WeakHashMap<ClassLoader,HashMap<List,Object>>
-
//Find or create a proxy class cache through the class loader Map cache; synchronized (loaderToCache) { //Find a cache Map based on the class loader cache = (Map) loaderToCache.get(loader); //If the cache map of the class loader does not exist, it is recreated if (cache == null) { cache = new HashMap(); loaderToCache.put(loader, cache); } /** * This mapping will remain valid for the duration of this method without further synchronization, because the mapping will be deleted only when the class loader becomes inaccessible. */ }
-
-
Get the proxy class in the proxy class map. Query the proxy class according to the interface array key. There are three cases here
-
Query the proxy class according to the key and return it directly
-
The object pendingGenerationMarker is obtained from key, which means that the proxy class is being created, and the cache. is called. Await() method enters wait
-
If you get null according to the key, it means that the agent has not been created. At this time, first assign the value of the key to pendingGenerationMarker to prevent multiple creation and ensure thread safety. If other threads access it, they will enter the previous branch and wait (and if you really create an agent class, it will be in the subsequent logic)
-
/** * Use the key to find the list of interfaces in the proxy class cache. This lookup results in one of three possible values: * null:The interface list in the class loader currently has no proxy class * the pendingGenerationMarker object: Proxy class currently generating interface list * a weak reference to a Class object: The proxy class for the interface list has been generated. */ synchronized (cache) { /** * Note that we don't have to worry about clearing the cache of weak reference entries in the map, * Because if the proxy class has been garbage collected, its class loader will also be garbage collected, * Therefore, this cache is cleared from the entire proxy class loader cache Map after GC */ do { //Get the proxy class from the cache according to the keyword key made by the interface string Object value = cache.get(key); //Judge that if the obtained value is the object of Reference class, call the get() method in the Reference object to obtain the actual object referenced if (value instanceof Reference) { proxyClass = (Class) ((Reference) value).get(); } //Judge agent class if (proxyClass != null) { //Proxy class created, return proxy class return proxyClass; } else if (value == pendingGenerationMarker) { // The proxy class is being created. Enter wait() to wait try { cache.wait(); } catch (InterruptedException e) { /* * The class generation that we are waiting for should * take a small, bounded time, so we can safely ignore * thread interrupts here. */ } continue; } else { /** * The proxy class for this interface list has not been generated or is being generated, so we will generate it now. Mark it as pending build. * The purpose of this is that in the case of multithreading, other threads will see an ID being created, enter the previous if condition and enter the wait */ cache.put(key, pendingGenerationMarker); break; } } while (true); }
-
-
To create a proxy class object
-
Judge whether the interfaces of the proxy class are all under the same package name. If the verification does not pass, the exception thrown is returned
-
/** * Record the packages of non-public proxy interfaces so that proxy classes can be defined in the same package. * Verify that all non-public proxy interfaces are in the same package. */ for (int i = 0; i < interfaces.length; i++) { int flags = interfaces[i].getModifiers(); if (!Modifier.isPublic(flags)) { String name = interfaces[i].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"); } } }
-
Generate the name of a Proxy class. The rule is package name + jdk Proxy class prefix $Proxy + count
-
//Generate the name of a Proxy class. The rule is package name + jdk Proxy class prefix $Proxy + count long num; synchronized (nextUniqueNumberLock) { num = nextUniqueNumber++; } String proxyName = proxyPkg + proxyClassNamePrefix + num;
-
Call proygenerator The generateproxyclass method generates a proxy class file
-
//Generate a proxy class file byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces);
-
Generate a class object of proxy class through class loader, proxy class name and proxy class file
-
try { //Generate a class object of proxy class through class loader, proxy class name and proxy class file proxyClass = 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()); }
-
Add the proxy class to the proxyClasses Map for use in isProxyClass method to judge whether the class is a proxy class
-
//Add the proxy class to the proxyClasses Map for use in isProxyClass method to judge whether the class is a proxy class // add to set of all generated proxy classes, for isProxyClass proxyClasses.put(proxyClass, null);
-
-
Finally, the final processing is done in the fianlly code block, and the cache key previously set is assigned the final value according to whether the agent is correctly generated
-
If the proxy class is created successfully, create a weak reference, WeakReference, reference the proxy class object, and assign the weak reference as value to the key of the interface name array
-
If the proxy class creation fails, delete the key
-
Finally, wake up all threads in the waiting queue
-
/** * We must somehow clear the "pending build" state of the proxy class cache entry. * If the proxy class is successfully generated, it is stored in the cache (using weak references); Otherwise, delete the reservation. * In all cases, all threads in the waiting queue that hold items in this cache are notified. */ synchronized (cache) { if (proxyClass != null) { //Agent class created successfully //According to the interface array as the key, the proxy class is encapsulated into a reference and put into the cache map cache.put(key, new WeakReference(proxyClass)); } else { //If the proxy class creation fails, delete the key from the cache cache.remove(key); } //Wake up other waiting threads, cache.notifyAll(); }
-
-
Source code
-
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { //Java security device detection SecurityManager sm = System.getSecurityManager(); if (sm != null) { final int CALLER_FRAME = 3; // 0: Reflection, 1: getProxyClass0 2: Proxy 3: caller final Class<?> caller = Reflection.getCallerClass(CALLER_FRAME); final ClassLoader ccl = caller.getClassLoader(); checkProxyLoader(ccl, loader); ReflectUtil.checkProxyPackageAccess(ccl, interfaces); } //Judge whether the interface of the proxy class exceeds the maximum int value if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } //Proxy class variable Class proxyClass = null; //Gets all the interface names used as keywords for proxy class caching String[] interfaceNames = new String[interfaces.length]; //Create a HashSet of proxy class interface for de duplication Set interfaceSet = new HashSet(); //Loop through all interfaces of the proxy class for (int i = 0; i < interfaces.length; i++) { //Check whether the actual class of the interface loaded through class loading is the same as the incoming interface class String interfaceName = interfaces[i].getName(); Class interfaceClass = null; try { interfaceClass = Class.forName(interfaceName, false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != interfaces[i]) { throw new IllegalArgumentException( interfaces[i] + " is not visible from class loader"); } //Verify whether the loaded interface class is really an interface if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } //Determine whether there are duplicate interfaces if (interfaceSet.contains(interfaceClass)) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } interfaceSet.add(interfaceClass); interfaceNames[i] = interfaceName; } //Convert the character array of interface name to list < string > /** * It is sufficient to use the string representation of the proxy interface as the key in the proxy class cache (rather than their class object), * Because we require the proxy interface to resolve by name through the provided class loader, its advantage is that using the string representation of the class will lead to implicit weak references to the class. */ Object key = Arrays.asList(interfaceNames); //Find or create a proxy class cache through the class loader Map cache; synchronized (loaderToCache) { //Find a cache Map based on the class loader cache = (Map) loaderToCache.get(loader); //If the cache map of the class loader does not exist, it is recreated if (cache == null) { cache = new HashMap(); loaderToCache.put(loader, cache); } /** * This mapping will remain valid for the duration of this method without further synchronization, because the mapping will be deleted only when the class loader becomes inaccessible. */ } /** * Use the key to find the list of interfaces in the proxy class cache. This lookup results in one of three possible values: * null:The interface list in the class loader currently has no proxy class * the pendingGenerationMarker object: Proxy class currently generating interface list * a weak reference to a Class object: The proxy class for the interface list has been generated. */ synchronized (cache) { /** * Note that we don't have to worry about clearing the cache of weak reference entries in the map, * Because if the proxy class has been garbage collected, its class loader will also be garbage collected, * Therefore, this cache is cleared from the entire proxy class loader cache Map after GC */ do { //Get the proxy class from the cache according to the keyword key made by the interface string Object value = cache.get(key); //Judge that if the obtained value is the object of Reference class, call the get() method in the Reference object to obtain the actual object referenced if (value instanceof Reference) { proxyClass = (Class) ((Reference) value).get(); } //Judge agent class if (proxyClass != null) { //Proxy class created, return proxy class return proxyClass; } else if (value == pendingGenerationMarker) { // The proxy class is being created. Enter wait() to wait try { cache.wait(); } catch (InterruptedException e) { /* * The class generation that we are waiting for should * take a small, bounded time, so we can safely ignore * thread interrupts here. */ } continue; } else { /** * The proxy class for this interface list has not been generated or is being generated, so we will generate it now. Mark it as pending build. * The purpose of this is that in the case of multithreading, other threads will see an ID being created, enter the previous if condition and enter the wait */ cache.put(key, pendingGenerationMarker); break; } } while (true); } //Start production agent class try { String proxyPkg = null; // package to define proxy class in /** * Record the packages of non-public proxy interfaces so that proxy classes can be defined in the same package. * Verify that all non-public proxy interfaces are in the same package. */ for (int i = 0; i < interfaces.length; i++) { int flags = interfaces[i].getModifiers(); if (!Modifier.isPublic(flags)) { String name = interfaces[i].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 the interface is not passed in or the package name is not found, the default package name is com sun. proxy. if (proxyPkg == null) { // if no non-public proxy interfaces, use com.sun.proxy package proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } { //Generate the name of a Proxy class. The rule is package name + jdk Proxy class prefix $Proxy + count long num; synchronized (nextUniqueNumberLock) { num = nextUniqueNumber++; } String proxyName = proxyPkg + proxyClassNamePrefix + num; /** * It seems that there is no logic to check whether the proxy class has been loaded * Verify that the class loader hasn't already * defined a class with the chosen name. */ //Generate a proxy class byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); try { proxyClass = 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()); } } //Add the proxy class to the proxyClasses Map for use in isProxyClass method to judge whether the class is a proxy class // add to set of all generated proxy classes, for isProxyClass proxyClasses.put(proxyClass, null); } finally { /** * We must somehow clear the "pending build" state of the proxy class cache entry. * If the proxy class is successfully generated, it is stored in the cache (using weak references); Otherwise, delete the reservation. * In all cases, all threads in the waiting queue that hold items in this cache are notified. */ synchronized (cache) { if (proxyClass != null) { //Agent class created successfully //According to the interface array as the key, the proxy class is encapsulated into a reference and put into the cache map cache.put(key, new WeakReference(proxyClass)); } else { //If the proxy class creation fails, delete the key from the cache cache.remove(key); } //Wake up other waiting threads, cache.notifyAll(); } } return proxyClass; }
-
-