Interview questions for senior engineers in large factories: detailed explanation of Java Dynamic agent mechanism and implementation principle

proxy pattern

  • Java dynamic proxy uses the proxy pattern commonly used in design patterns
  • Proxy mode:
    • The purpose is to provide a proxy for other objects to control access to a real object
  • Role of agent class:
    • Preprocessing messages for delegate classes
    • Filter and forward messages
    • Perform subsequent processing after the message is executed by the delegate class

    Through the middle layer of proxy layer, the direct access to real delegate class objects can be effectively controlled, and user-defined control strategies, such as AOP mechanism in Spring, can be implemented, which makes the design more flexible

  • Basic composition of agent:
  • There are Subject role, RealSubject role and Proxy role in the Proxy mode:
    • Subject: responsible for defining the interfaces that RealSubject and Proxy roles should implement
    • RealSubject: used to truly complete business service functions
    • Proxy: it is responsible for making its own request and calling the request function corresponding to RealSubject to realize business functions. It does not do real business
  • Static proxy mode:
    • When this Proxy relationship is specified in the code phase, the Proxy class is compiled into a class file by the compiler. When the system runs, this class already exists
    • This static proxy mode can access inaccessible resources and has great advantages in enhancing the existing interface business functions However, the extensive use of this static agent will increase the class size in the system and is not easy to maintain
    • Since the functions of Proxy and RealSubject are essentially the same, Proxy is only an intermediary. The existence of this kind of Proxy in the system will cause code redundancy
  • In order to solve the problem of static Proxy mode, there is dynamic Proxy creation:
    • In the running state, where a Proxy is needed, a Proxy is dynamically created according to the Subject and RealSubject
    • After the Proxy is used, it will be destroyed, so as to avoid the redundancy of the class of the Proxy role in the system

Java Dynamic Proxy

  • java.lang.reflect.Proxy:
    • Main class of Java Dynamic Proxy Mechanism
    • Provides a set of static methods to dynamically generate objects and proxy classes for a set of interfaces
// This method is used to get the calling processor associated with the specified proxy object
public static InvocationHandler getInvocationHandler(Object proxy);

// This method is used to obtain the class object of the dynamic proxy class associated with the specified class loader and a set of interfaces
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces); 

// This method is used to determine whether the specified class object is a dynamic proxy class
public static boolean isProxyClass(Class<?> cl);

// This method is used to generate a dynamic proxy class instance for the specified class loader, a set of interfaces, and the calling processor
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
  • java.lang.reflect.InvocationHandler:
    • Call the processor interface and customize the invoke method to realize proxy access to the real delegate class
/**
 * This method is responsible for centralized processing of all method calls on the dynamic proxy class
 * 
 * @param proxy Proxy instance
 * @param method Called method object
 * @param args Call parameters
 * @return Returns the object that the calling processor preprocesses or assigns to the delegate class instance for reflection execution according to the three parameters
 */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
  • java.lang.ClassLoader:
    • Class loader
    • Load the bytecode of the class into the Java virtual machine, that is, the JVM, and define the class object for it before the class can be used
    • The only difference between Proxy class and ordinary class is that the Proxy class bytecode is dynamically generated by the JVM at runtime, rather than pre stored in any one In the calss file
    • Each time a dynamic proxy class object is generated, a class loader object needs to be specified

Java Dynamic Proxy Mechanism

The process of creating objects by Java Dynamic Proxy:

  • Create your own calling handler by implementing the InvocationHandler interface
/* 
 * InvocationHandlerImpl InvocationHandler interface is implemented
 * It can realize the dispatch and forwarding of method calls from proxy class to delegate class, and the reference to the instance of delegate class, which is used to really execute the method calls forwarded by dispatch
 */
 InvocationHandler handler = new InvocationHandlerImpl(...);
  • Create a dynamic Proxy class by specifying a ClassLoader object and a set of interface s for the Proxy class
// Dynamically create the class object of Proxy class for a group of interfaces including Interface interface through Proxy
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.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
// Get the constructor object from the generated class object by reflection
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
  • The dynamic proxy class instance is created through the constructor. When constructing, the calling processor object is passed in as a parameter
// Create a dynamic proxy class instance through the constructor
Interface proxy = (Interface)constructor.newInstance(new Object[] { handler });

In order to simplify the object creation process, the Proxy class uses newInstanceProxy to encapsulate steps 2-4, so it only takes two steps to create the Proxy object

// InvocationHandlerImpl implements the InvocationHandler interface and can dispatch and forward method calls from proxy class to delegate class
InvocationHandler handler = new InvocationHandlerImpl(...); 
// Create an instance of a dynamic Proxy class directly through Proxy
Interface proxy = (Interface)Proxy.newProxyInstance(classLoader, new Class[] { Interface.class }, handler);

Java Dynamic Proxy considerations

  • Package:
    • If the proxy interface is public, the proxy class is defined in the top-level package and the package is empty. Otherwise, by default, the proxy class is defined in the package where the interface is located
/*
 * Record the packages of non-public proxy interfaces to define proxy classes 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 packaes");
		}
	}
}
		if (proxyPkg == null) {
		// There is no package that uses a non-public proxy interface proxy class
		proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
		}
  • The generated proxy class is public final and cannot be inherited
  • The format of the class name is: "$ProxyN"
    • N is an increasing number one by one, representing that Proxy is the Proxy class generated by the nth dynamic Proxy
    • For the same set of interfaces, the order of interfaces is the same. Instead of creating dynamic proxy classes repeatedly, a proxy class object that has been created and cached previously is returned to improve efficiency
synchronized (cache) {
	/*
	 * Don't worry about getting a cache that cleans out weak references
	 * Because if a proxy class has been garbage collected, the class loader of the proxy class will also be garbage collected
	 * Therefore, the obtained cache is the mapping loaded into the cache
	 */
	 do {
	 	Object value = cache.get(key);
	 	if (value instanceof Reference) {
	 		proxyClass = (Class) ((Reference) value).get();
	 		if (proxyClass != null) {
	 			/*
	 			 * The proxy class has been generated. Return the proxy class
	 			 */
	 			return proxyClass;
	 		} else if (value == pendingGenerationmarker) {
	 			/*
	 			 * Proxy class generation in progress, waiting for proxy class generation
	 			 */
	 			try {
	 				cache.wait();
	 			} catch (InterruptedException e) {
	 				/*
	 				 * The proxy class waiting to be generated has a very small limited time, so the impact of threads here can be ignored
	 				 */
	 			}
	 			continue;
	 		} else {
	 			/*
	 			 * If there is no proxy class generated or being generated for this interface list
	 			 * You need to generate proxy classes for these interfaces and mark these interfaces as to be generated
	 			 */
	 			 cache.put(key, pendingGenerationMarker);
	 			 break;
	 		}
	 	}while (true);
	 }
  • Class inheritance relationship:

Proxy class is the parent class. This rule applies to all dynamic proxy classes created by proxy (which also leads to the defect of Java dynamic proxy. Because Java does not support multiple inheritance, it cannot implement dynamic proxy for class and can only proxy for Interface). This class implements a set of interfaces of all proxies, so proxy class can be safely type converted to an Interface it proxies

  • The root class of the proxy class is Java hashCode(),equals() and () in lang.Object The toString method is also dispatched to the invoke processor to execute the invoke method

Java Dynamic Proxy testing

Create a dynamic proxy class
public class serviceProxy implements InvocationHandler {
	private Object target;
	/**
	 * Bind the delegate object and return a proxy object
	 * @param target Real object
	 * @return Proxy object
	 */
	public Object bind(Object target, Class[] interfaces) {
		this.target = target;
		// Get proxy object
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
	}

	/**
	 * The method is first entered by calling the method through the proxy object
	 * @param proxy Proxy object
	 * @param Method Method, called method
	 * @param args Method parameters
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		/*
		 * JDK Dynamic agent
		 */
		 Object result = null;
		 // Call before reflection method
		 System.err.println("--Method executed before reflection method--");
		 // Reflection execution method, which is equivalent to calling target xXX()
		 result = method.invoke(target, args);
		 // Call after reflection method
		 System.err.println("--Method executed after reflection method--");
		 return result;
	}
}
  • bind method:
    • The newProxyInstance method in the bind method is to generate a proxy object
      • First parameter: class loader
      • The second parameter: the interface implemented by the real delegate object The proxy object is hung under that interface
      • The third parameter: this represents the current HelloServiceProxy class, that is, the proxy using HelloServiceProxy as the object
  • invoke method:
    • The invoke method has three parameters:
      • The first proxy is the proxy object
      • The second is to call that method at the moment.
      • The third is the parameters of the method
ProxyTest
public class ProxyTest {
	public static void main(String[] args) {
		HelloServiceProxy proxy = new HelloServiceProxy();
		HelloService service = new HelloServiceImpl();
		// Bind proxy object
		service = (HelloService) proxy.bind(service, new Class[] {HelloService.class});
		service.sayHello("user");
	}
}

class file analysis

  • After the Java compiler compiles the Java file, the class file on disk:
    • The class file is a binary file that contains the machine code that only the JVM virtual machine can recognize
    • The JVM virtual machine reads the bytecode file and takes out the binary data
    • Load into memory and parse Generate the corresponding Class object based on the information in the Class file
  • Load the bytecode of the class file into the system, convert it into a class object, and then instantiate it:
    • Define a class
    • Customize a class loader to convert bytecode into class object
    • compile. Class file, read the bytecode in the program, convert it into the corresponding class object, and then instantiate it

Generate binary bytecode at run time

  • In the code, create a class dynamically:
    • Since the JVM loads classes through binary information of bytecode, it follows the Java compilation system organization in the run-time system Class file format and structure, generate the corresponding binary data, and then load and convert the binary data into the corresponding class
  • The open source framework can be used to generate the corresponding binary bytecode according to the organization rules of Java virtual machine specification For example, ASM,Javassist

ASM

  • ASM is a Java bytecode manipulation framework:
    • It can modify existing classes in binary form or dynamically generate classes
    • In the process of creating class bytecode, ASM manipulates the assembly instruction level of the underlying JVM
    • ASM can directly generate binary class files, or dynamically change the class behavior before the class is loaded into the Java virtual machine
    • After reading information from class files, ASM can change class behavior, analyze class information, and even generate new classes according to user requirements
  • Bytecode of class generated by ASM:
    • Use the ClassWriter interface provided by ASM framework to dynamically create class bytecode through visitor mode
    • Then use the Java decompile tool (JD_GUI) to open the class generated on the hard disk Class file to view class information
    • Then use the defined class loader to load the class file into memory, then create a class object, instantiate an object, call the code method, and view the results in the code method
  • So far, it shows that it is completely possible to generate bytecode in the code and dynamically load it into class objects and create instances

Javassist

  • Javassist is an open source class library for analyzing, editing and creating Java bytecode. It has been added to the JBoss application server project to realize the dynamic AOP framework for JBoss by using javassist to operate bytecode:
    • Javassist is a sub project of JBoss. Its main advantage is that it is simple and fast
    • Using Java coding directly, you can change the class structure or dynamically generate classes without virtual machine instructions

Source code analysis

Proxy class
// Mapping table: used to maintain the cache of class loader objects to their corresponding proxy classes
private static Map loaderToCache = new WeakHashMap();

// Tag: used to mark that a dynamic proxy class is being created
private static Object pendingGenerationMarker = new Object();

// Synchronization table: records the types of dynamic proxy classes that have been created. It is mainly judged by the isProxyClass method
private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap());

// Associated call processor reference
protected InvocationHandler h;
newProxyInstance
  • Proxy static method newProxyInstance:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
	/*
	 * Check whether the associated call processor is empty. If it is empty, an exception will be thrown
	 */
	 if (h == null) {
	 	throw new NullPointerException();
	 }
	 /*
	  * Gets the proxy class type object associated with the specified type loader and a set of interfaces
	  */
	  Class<?> cl = getProxyClass0(loader, interfaces);
	  /*
	   * Get the constructor object through reflection and generate a proxy class instance
	   */
	   try {
	       final Constructor<?> cons = cl.getConstructor(constructorParams);
	   	   final  InvocationHandler ih = h;
	   	   SecurityManager sm = System.getSecurityManager();
	   	   if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
	   	       /* 
	   	        * Create a dynamic proxy class instance using doPrivilege
	   	        * Because the proxy class implements a non-public interface that may require special permissions
	   	        */
	   	        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());
	   }
}

private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
	try {
		return cons.newInstance(new Object[] {h});
	} catch (IllegalAccessException e) {
		throw new InternalError(e.toString());
	} catch (InstantationException e) {
		throw new InternalException(e.toString());
	} catch (InvocationTargetException e) {
		Throwable t = e.getCause();
		if (t instanceof RuntimeException) {
			throw (RuntimeException) t;
		} else {
			throw new InternalException(e.toString());
		}
	}
}
  • The real key to dynamic proxy is in the getProxyClass0() method

getProxyClass0 method analysis

  • The process of generating a specific class file through the getProxyClass0 method:
    • Define path
    • Write the class file to the specified hard disk
    • Decompile the generated class file

The getProxyClass0() method is divided into four steps:

  1. Conduct a certain degree of security inspection on this group of interfaces: 1.1 is the interface class object visible to the class loader 1.2 the interface class object is exactly the same as the interface class object recognized by the class loader 1.3 make sure that the interface type is not the class type
for (int i = 0; i < interfaces.length; i++ ) {
	/*
	 * Verify that the class loader resolves the name of this interface to the same class object
	 */
	 String interfaceName = interface[i].getName();
	 Class interfaceClass = null;
	 try {
	 	/*
	 	 * forName(String name, boolean initialize, ClassLoader loader)
	 	 * Returns the Class object associated with the class or interface with the given string name,
	 	 * using the given class loader
		 */ 
	 	interfaceClass = Class.forName(interfaceName, false, loader);
	 } catch (ClassNotFoundException e) {
	 }
	 if (interfaceClass != interface[i]) {
	 	throw new IllegalArgumentException(interface[i] + "is not visible from class loader.");
	 }

	/*
	 * Verify whether the class object obtained by the class loader is of interface type
	 */
	 if (! interfaceClass.isInterface()) {
	 	throw new IllegalArgumentException(interfaceClass.getName() + "is not an interface.");
	 }

	/*
	 * Verify that the class object interface obtained by the class loader is not a duplicate interface
	 */
	 if (interfaceSet.contains(interfaceClass)) {
	 	throw new IllegalArgumentException("repeated interface:" + interface.getName());
	 }
	 interfaceSet.add(interfaceClass);
	 interfaceName[i] = interfaceName;
}
  1. Obtain the cache table corresponding to the class loader object as the keyword from the loaderToCache mapping table. If it does not exist, a new cache table will be created and updated to loaderToCahe: 2.1 loaderToCache stores key value pairs: interface name list: references to class objects of dynamically generated proxy classes 2.2 when the proxy class is being created, it will be saved temporarily: interface name list: pendingGenerationMarker 2.3 the function of pendingGenerationMarker is to inform subsequent similar requests (the interface array is the same and the interface arrangement order in the group is the same) that the proxy class is being created. Please wait until the creation is completed
/*
 * Find the cache table of the class loader. If not, create a proxy class cache for the class loader
 */
Map cache;
synchronized (loaderToCache) {
	cache = (Map) loaderToCache.get(loader);
 	if (cache == null) {
 		cache = new HashMap();
 		loaderToCache = put(loader, cache);
 	}
}
do {
	/* 
	 * Obtain the corresponding cache value with the interface name as the keyword
	 */
	 Object value = cache.get(key);
	 if (value instanceof Reference) {
	 	proxyClass = (Class)((Reference)value).get();
	 }
	 if (proxyClass != null) {
	 	// If it has been created, return directly
	 	return proxyClass;
	 } else if (value == pendingGenerationMarker) {
	 	// Proxy class is being created, keep waiting
	 	try {
	 		cache.wait()
	 	} catch (InterruptException e) {
	 	}
	 	// Wait to be awakened, continue the cycle and pass the secondary check to ensure that the creation is completed, otherwise wait again
	 	continue;
	 } else {
	 	// Tag proxy class is being created
	 	cache.put(key, pendingGenerationMarker);
	 	// Jump out of the loop and enter the creation process
	 	break;
	 }
} while(true)
  1. Dynamically create class object of proxy class
/* 
 * Select a name proxy class to generate
 */
long num;
synchronized (nextUniqueNumberLock) {
	num = nextUniqueNumber ++;
}
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
 * Verify that there is no class defined with this name in the class loader
 */
 ...
 
// Dynamically generate bytecode array of proxy class
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
try {
	// Dynamically define the newly generated proxy class
	proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
	/*
	 * The class format error here refers to the error in the generation agent class code
	 * There are also some errors that apply to the parameters generated by the proxy class, such as the overload of some virtual machines
	 */
	 throw new IllegalArgumentException(e.toString());
}
// Record the generated proxy class object in proxyClasses
proxyClasses.put(proxyClass, null);

Firstly, the proxy class name - $ProxyN format is generated according to the rule of whether the interface is public or not, and then the proxy class is generated dynamically All code generation is completed by ProxyGenerator. This class is in rt.jar and needs to be decompiled

public static byte[] generateProxyClass(final String name, Class[] interfaces) {
	ProxyGenerator gen = new ProxyGenerator(name, interfaces);
	// Dynamically generate bytecode of proxy class
	final byte[] classFile = gen.generateClassFile();
	// If the value of saveGeneratedFiles is true, the bytecode of the generated agent class will be saved to the hard disk
	if (saveGeneratedFiles) {
		java.security.AccessController.doPrivileged(
			new java.security.PrivilegedAction<Void>() {
				public Void run() {
					try{
						FileOutputStream file = new FileOutputStream(doToSlash(name) + ".class");
						file.write(classFile);
						file.close();
						return null;
					} catch (IOException e) {
						throw new InternalError("I/O exception saving generated file :" + e);
					}
				}
			}
		);
	} 
	// Returns the bytecode of the proxy class
	return classFile;
}
  1. At the end of the code generation process, the cache table is updated according to the results If the proxy class is generated successfully, update the class object reference of the proxy class into the cache table, otherwise clear the corresponding key values in the cache table, and finally wake up all possible waiting threads
finally {
	synchronized (cache) {
		if (proxyClass != null) {
			cache.put(key, new WeakReference(proxyClass));
		} else {
			cache.remove(key);
		}
		cache.notifyAll();
	}
}
return proxyClass;

InvocationHandler parsing

  • When the Proxy role performs Proxy business, it is to complete some additional functions before or after calling the real business
  • The proxy class is to do some additional business before or after calling the methods of the real role
  • In order to construct a universal and simple proxy class, you can give all the actions that trigger the real role to a trigger manager, which manages the trigger uniformly. This trigger manager is InvocationHandler
  • Basic working mode of dynamic agent:
    • Delegate the implementation of the method function to the InvocationHandler role
    • For external calls to each of the Proxy roles, the Proxy role will be handed over to InvocationHandler for processing
    • InvocationHandler calls the method of the specific object role
  • In this mode, Proxy and RealSubject should implement the public method of the same class in two ways:
    • A more intuitive way is to define a functional interface, and then let Proxy and RealSubject implement this interface (dynamic Proxy mechanism in JDK - Java dynamic Proxy mechanism)
    • More obscure way: proxy inherits RealSubject through inheritance Because proxy inherits from RealSubject, proxy has the function of RealSubject. Proxy can also implement polymorphism (cglib) by rewriting the methods in RealSubject

JDK dynamic proxy mechanism

  • JDK dynamic proxy mechanism creates a dynamic proxy object for RealSubject through the interface:
    • Get a list of all interfaces on RealSubject
    • Determine the class name of the proxy class to be generated
    • Dynamically create the bytecode of the Proxy class in the code according to the interface information to be implemented
    • Convert the corresponding bytecode to the corresponding class object
    • Create InvocationHandler to handle all Proxy method calls
    • The class object of proxy instantiates a proxy with the created handler as the parameter
  • JDK dynamic agent instance:
    • Define two interfaces Vehicle and Rechargeable
    • The Vehicle interface represents the Vehicle class and has a drive() method
    • The Rechargeable interface indicates that it is Rechargeable and has the recharge() method
    • Define an ElectricCar class that implements two interfaces. The class diagram is as follows:
  • Create a dynamic proxy class for electrocar:
/**
 * Vehicle interface
 */
 public interface Vehicle {
 	public void drive();
 }
/**
 * Charging interface
 */
 public interface Rechargable {
 	public void recharge();
 }
/**
 * Electric vehicles
 * Implement the rechargeable and vehicle interface
 */
 public class ElectricCar implements Rechargable, Vehicle {
 	@Override
 	public void drive() {
 		System.out.println("ElectricCar can drive.");
 	}
 
 	@Override
 	public void recharge() {
 		System.out.println("ElectricCar can recharge.");
 	}
 }
/**
 * Triggers for dynamic proxy classes
 */
 public class InvocationHandlerImpl implements InvocationHandler {
 	private ElectricCar car;
 	
 	public InvocationHandlerImpl(Electric car) {
 		this.car = car;
 	} 
 
 	@Override
 	public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {
 		System.out.println("Calling method:" + paramMethod.getName() + "...");
 		paramMethod.invoke(car, null);
 		System.out.println("method" + paramMethod.getName() + "End of call.");
 		return null;
 	}
 }
public class ProxyTest {
	public static void main(String[] args) {
		ElectricCar car = new ElectricCar();
		// Get the corresponding ClassLoader
		ClassLoader classLoader = car.getClass().getClassLoader();
		// Get all interfaces implemented by electrocar
		Class[] interfaces = car.getClass().getInterfaces();
		// Set up a method call request processor from the proxy to handle all method calls on the proxy object
		InvocationHandler handler = new InvocationHandlerImpl(car);
		/*
 	 	 * To create a proxy object:
 	 	 * 		a. JDK Dynamically create and update data in memory according to the passed parameter information class equivalent bytecode
 		 *		b. Convert to the corresponding class according to the corresponding bytecode
 		 *		c. Then call newInstance() to create an instance.
 		 */
 		Object o = Proxy.newProxyInstance(classLoader, interfaces, handler);
 		Vehicle vehicle = (Vehicle) o;
 		vehicle.drive();
 		Rechargable rechargable = (Rechargable) o;
 		rechargable.recharge();
	}
}
  • Generate the bytecode of the dynamic proxy class and save it to the hard disk:
  • The JDK provides sun misc. ProxyGenerator. Generateproxyclass (string proxyname, calss [] interfaces) uses the underlying method to generate the bytecode of the dynamic proxy class
  • Define a tool class to save the generated dynamic agent class to the hard disk:
public class proxyUtils {
	/*
     * According to the class information, the binary bytecode is dynamically generated and saved to the hard disk
     * The default is under the clazz directory
     * 
     * @params clazz Classes that need to generate dynamic proxy classes 
     * @proxyName Name of dynamically generated proxy class
     */
     public static void generateClassFile(Class clazz, String proxyName) {
     	// Generate bytecode according to the provided proxy class name and class information
     	byte[] classFile = ProxyGenerator.generateProxyClass(ProxyName, clazz.getInterfaces());
     	String paths = clazz.getResource(".").getPath();
     	System.out.println(paths);
     	FileOutputStream out = null;
     	try {
     		// Keep to hard disk
     		out = new FileOutputStream(paths + proxyName + ".class");
     		out.write(classFile);
     		out.flush();
     	} catch (Exception e) {
     		e.printStackTrace();
     	} finally {
     		try {
     			out.close();
     		} catch (IOException e) {
     			e.printStackTrace();
     		}
     	}
     }
}
  • Modify the proxy class name to "ElectricCarProxy" and save it to the hard disk, using the following statement:
 ProxyUtils.generateClassFile(car.getClass(), "ElectricCarProxy");

This will be done at electrocar Class generates electriccarproxy under the same level directory Class file

  • Use the decompile tool JD GUI Exe opens and you will see the following information:
/**
 * The organization mode of the generated dynamic Proxy class is to inherit the Proxy class and then implement all interfaces on the class that needs to implement the Proxy
 * In the implementation process, all methods are handed over to InvocationHandler for processing
 */
 public final class ElectricCarProxy extends Proxy implements Rechargable,Vehicle {
 	private static Method m1;
 	private static Method m3;
 	private static Method m4;
 	private static Method m0;
 	private static Method m2;

	public ElectricCarProxy(InvocationHandler paramInvocationHandler) throws {
		super(paramInvocationHandler);
	}  
 
 	public final boolean equals(Object paramObject) throws {
 		try {
 			/*
 			 * The implementation of the method function is handed over to InvocationHandler
             */
             return ((Boolean) this.h.invoke(this, m1, new Object[] {paramObject})).booleanValue();
 		} catch (Error | RuntimeException localError) {
 			throw localError;
 		} catch (Throwable localThrowable) {
 			throw new Undeclared ThrowableException(localThrowable);
 		}
 	} 
 	
 	public final void recharge() throws {
 		try {
 			/*
 			 * The implementation of the method function is handed over to InvocationHandler
              */
             this.h.invoke(this, m3, null);
             return;           
 		} catch (Error | RuntimeException localError) {
 			throw localError;
 		} catch (Throwable localThrowable) {
 			throw new Undeclared ThrowableException(localThrowable);
 		}
 	}
  
  	public final drive() throws {
  		try {
  			/* 
   			 * The method implementation is handed over to InvocationHandler for processing
   			 */
   			this.h.invoke(this, m4, null);
   			return;
  		} catch (Error | RuntimeException localError) {
  			throw localError;
  		} catch (Throwable localThrowable) {
  			throw new Undeclared ThrowableException(localThrowable);
  		}
  	}
   
   	public final int hasCode() throws {
   		try {
   			/*
   			 * The method function is handed over to InvocationHandler for processing
   			 */
   			return ((Integer) this.h.invoke(this, m0, null)).intValue();
   		} catch (Error | RuntimeException localError) {
   			throw localError;
   		} catch (Throwable localThrowable) {
   			throw new Undeclared ThrowableException(localThrowable);
   		}
   	} 
   
   	public final String toString() throws {
   		try {
   			/*
   			 * The method function implementation is handed over to InvocationHandler for processing
   			 */
   			return (String)this.h.invoke(this, m2, null);
   		} catch (Error | RuntimeException localError) {
   			throw localError;
   		} catch (Throwable localThrowable) {
   			throw new Undeclared ThrowableException(localThrowable);
   		}
   	}
   
    static {
   		try {
   			/*
   			 * For each method object required 
   			 * When the corresponding method is called, the method object is passed as a parameter to InvocationHandler for processing
   			 */
   			m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
   			m3 = Class.forName("com.oxford.proxy.Rechargable").getMethod("recharge", new Class[0]);
   			m4 = Class.forName("com.oxford.proxy.Vehicle").getMethod("drive", new Class[0]);
   			m0 = Class.forName("java.lang.Object").getMethod("hasCode", new Class[0]);
   			m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
   			return;
   			} catch (NoSuchMethodException localNoSuchMethodException) {
   				throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
   			} catch (ClassNotFoundException localClassNotFoundException) {
   				throw new NoClassDefFoundError(localClassNotFoundException.getMessge());
   			}
   	}
 }
  • Features of the generated dynamic proxy class:
    • Inherited from Java lang.reflect. Proxy, which implements the two electrocar interfaces of rechargeable and vehicle
    • All methods in the class are final
    • The invoke() method of InvocationHandler is uniformly called by the implementation of all method functions

CGLIB dynamic proxy mechanism

  • CGLIB generates dynamic proxy classes through class inheritance
  • Features of JDK dynamic proxy class:
    • A class must have an implemented interface, and the generated proxy class can only proxy the methods specified by a class interface This will cause the subclass implementation to inherit the methods of the two interfaces, and the other implemented methods will not have this method in the generated dynamic proxy class
    • If a class does not implement an interface, it cannot use JDK dynamic proxy
  • CGLIB: Code Generation Library, CGLIB is a powerful, high-performance and high-quality Code generation class library, which can extend Java classes and implement Java interfaces at runtime
  • The mode of CGLIB creating dynamic proxy class:
    • Find the method definitions of all non final public types in the class
    • Convert the definitions of these methods into bytecode
    • Convert the composed bytecode into the class object of the corresponding agent
    • Implement the MethodInterceptor interface to handle requests for all methods on the proxy class (similar to the InvocationHandler in JDK dynamic proxy)
  • Define a Programmer class and a Hacker class
/**
 * Developer class
 */
 public class Programmer {
 	public void code() {
 		System.out.println("Developers develop program code.");
 	}
 }
/**
 * Hacker class
 * The method interceptor interface is implemented
 */
 public class Hacker implements MethodInterceptor {
 	@Override
 	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
 		System.out.println("Hacker what to do.");
 		proxy.invokeSuper(obj, args);
 		System.out.println("Hacker done this.");
 		return null;
 	}
 }
  • Test class:
public class Test {
		public static void main(String[] args) {
			Programmer programmer = new Programmer();
			Hacker hacker = new Hacker();
			// Enhancer in CGLIB to create dynamic proxy
			Enhancer enhancer = new Enhancer();
			// Set the class to create the dynamic proxy
			enhancer.setSuperclass(programmer.getClass());
			/*
			 * Set callback
			 * This is equivalent to calling CallBack for all methods on the proxy class
			 * CallBack needs to implement the intercept() method to intercept
			 */
			enhancer.setCallBack(hacker);
			Programmer proxy = (Programmer) enhancer.create();
			proxy.code();
		}
}
  • Contents of the class file generated by CGLIB:
public class Programmer EnhancerByCGLIB fa7aa2cd extends Programmer implements Factory {
	/* 
	 * ....
	 */
	 
	 // methodInterceptor passed in by Enhancer
	 private MethodInterceptor CGLIB$CALLBACK_0;
	 
	 /*
	  * ...
	  */

	  public final void code() {
	  	MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
	  	if (tmp4_1 == null) {
	  		tmp4_1;
	  		// If callback is not empty, the intercept() method of methodInterceptor will be called
	  		CGLIB$BIND_CALLBACKS(this);
	  	}
	  	if (this.CGLIB$CALLBACK_0 != null)
	  		return;
	  		// If the callback callback function is not set, the method of the parent class is executed by default
	  		super.code();
	  }
	  /*
	   * ...
	   */
}

Added by kkessler on Sat, 22 Jan 2022 08:29:18 +0200