preface
Q: How do you implement dynamic proxy in JDK? How do you implement method invocation
From: One and a half years of experience sharing (including Ali, Meituan, headlines, JD, Didi)
This article summarizes the knowledge points you need to answer, the whole process less nonsense, if dry goods, the article is longer, you can click like to see, like this article, I will always share, hardcore articles will regularly share!
The article was first published on the official account (Moon with Flying fish) and then synchronized to the personal website: Xiaoflyfish.cn /
The proxy pattern
Proxy mode is a design mode that provides an additional way to access the target object. In this way, additional functions are provided to extend the functions of the target object without modifying the original target object
An example: When renting a house, some people will rent directly through a landlord, while others will rent through an intermediary.
Which of the two cases is more convenient? Of course, it’s more convenient to go through an intermediary.
Here, the intermediary is equivalent to the agent. Users complete a series of operations (house viewing, deposit payment, rental and cleaning) of renting through the intermediary. The agent mode can effectively decouple the specific implementation from the caller, and completely hide the specific implementation inside through interface-oriented coding.
Classification:
Static proxy: implemented at compile time, when the proxy class is an actual class file
Dynamic proxy: generated dynamically at run time, i.e. there is no actual class file after compilation, but instead class bytecode is generated dynamically at run time and loaded into the JVM
Static agent
use
Create an interface, then create a proxied class that implements that interface and implements the abstract methods in that interface. Then create a proxy class that also implements the interface. Holds a reference to a proxied object in the proxy class, and then calls methods on that object in the proxy class methods.
public interface UserDao {
void save(a);
}
Copy the code
public class UserDaoImpl implements UserDao {
@Override
public void save(a) {
System.out.println("Saving user..."); }}Copy the code
public class TransactionHandler implements UserDao {
// The target proxy object
private UserDao target;
// Pass in the target object when constructing the proxy object
public TransactionHandler(UserDao target) {
this.target = target;
}
@Override
public void save(a) {
// Processing before calling the target method
System.out.println("Open transaction control...");
// Call the method of the target object
target.save();
// Processing after the target method is called
System.out.println("Close transaction control..."); }}Copy the code
public class Main {
public static void main(String[] args) {
// Create the target object
UserDaoImpl target = new UserDaoImpl();
// Create a proxy object and reference it with an interface
UserDao userDao = new TransactionHandler(target);
// Make the call to the interfaceuserDao.save(); }}Copy the code
Proxying a class is easy to do with JDK static proxies. However, the disadvantages of the JDK static proxy are also exposed: since the proxy can only serve one class, writing a large number of proxy classes can be cumbersome if there are many classes that need the proxy
JDK Dynamic proxy
Five steps to using the JDK dynamic proxy:
-
Define your own InvocationHandler by implementing the InvocationHandler interface;
-
Get a dynamic Proxy class from proxy.getProxyClass;
-
The constructor of the proxy class is obtained by reflection mechanism. The method signature is getConstructor(InvocationHandler.class).
-
Get the proxy object through the constructor and pass in a custom InvocationHandler instance object as a parameter;
-
Call the target method through the proxy object;
public interface IHello {
void sayHello(a);
}
Copy the code
public class HelloImpl implements IHello {
@Override
public void sayHello(a) {
System.out.println("Hello world!"); }}Copy the code
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
/** Target object */
private Object target;
public MyInvocationHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------ Insert pre-notification code -------------");
// Execute the corresponding target method
Object rs = method.invoke(target,args);
System.out.println("------ Insert post-processing code -------------");
returnrs; }}Copy the code
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
public class MyProxyTest {
public static void main(String[] args)
throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
/ / = = = = = = = = = = = = = = = = = = = = = = = = = the first = = = = = = = = = = = = = = = = = = = = = = = = = =
$Proxy0 = $Proxy0
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles"."true");
// 2. Get the dynamic proxy class
Class proxyClazz = Proxy.getProxyClass(IHello.class.getClassLoader(),IHello.class);
// 3. Get the constructor of the proxy class and pass in the parameter type invocationHandler.class
Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
// 4. Create a dynamic proxy object with a constructor, passing in a custom InvocationHandler instance
IHello iHello1 = (IHello) constructor.newInstance(new MyInvocationHandler(new HelloImpl()));
// 5, call the target method through the proxy object
iHello1.sayHello();
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = the second = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
NewProxyInstance (ClassLoader,Class
[] instance, InvocationHandler h) */
IHello iHello2 = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), // Load the class loader for the interface
new Class[]{IHello.class}, // A group of interfaces
new MyInvocationHandler(new HelloImpl())); // Custom InvocationHandleriHello2.sayHello(); }}Copy the code
There are some similarities between JDK static proxies and JDK dynamic proxies, such as the creation of proxy classes and the implementation of interfaces for proxy classes.
Differences: In static proxy, we need to create a proxy class for each interface and each proxy class. Therefore, we need the proxy class to implement the same interface as the proxy class before compiling, and directly call the corresponding methods of the proxy class in the implementation method. But dynamic proxy is different. We don’t know which interface and which proxy class to create for, because it is created at run time.
To sum up the difference between a static JDK proxy and a dynamic JDK proxy:
JDK static proxies are created through direct coding, while JDK dynamic proxies use reflection to create proxy classes at run time.
In fact, in the dynamic proxy, the core is the InvocationHandler. Each instance of the agent has an InvocationHandler associated with it. When the proxy instance is invoked, the invocation of the method is encoded and assigned to its Invoke method on its InvocationHandler
Calls to the methods of the proxy object instance are made via the Invoke method in the InvocationHandler, which determines which method of the proxy is called based on the proxy object, method name, and parameters passed in.
CGLIB
At the bottom of the CGLIB package is the use of ASM, a small and fast bytecode processing framework, to convert bytecode and generate new classes
The CGLIB agent is implemented as follows:
- First, implement a MethodInterceptor. Method calls are forwarded to the intercept() method of the class.
- The proxy object is then obtained through the CGLIB dynamic proxy when needed.
Use case
public class HelloService {
public HelloService(a) {
System.out.println("HelloService structure");
}
/** * This method cannot be overridden by a subclass. Cglib is not a proxy for final-decorated methods */
final public String sayOthers(String name) {
System.out.println("HelloService:sayOthers>>"+name);
return null;
}
public void sayHello(a) {
System.out.println("HelloService:sayHello"); }}Copy the code
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/** * Customize MethodInterceptor */
public class MyMethodInterceptor implements MethodInterceptor{
/** * sub: cglib generates a proxy object * method * objects: methodProxy */
@Override
public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("====== Insert pre-notification ======");
Object object = methodProxy.invokeSuper(sub, objects);
System.out.println("====== insert the latter notice ======");
returnobject; }}Copy the code
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
public class Client {
public static void main(String[] args) {
// The proxy class file is stored on the local disk so that we can decompile and view the source code
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
// The process of obtaining the proxy object through the CGLIB dynamic proxy
Enhancer enhancer = new Enhancer();
// Set the superclass of the enhancer object
enhancer.setSuperclass(HelloService.class);
// Set the enhancer callback object
enhancer.setCallback(new MyMethodInterceptor());
// Create a proxy object
HelloService proxy= (HelloService)enhancer.create();
// Call the target method through the proxy objectproxy.sayHello(); }}Copy the code
JDK proxies have a very limited requirement that the classes being proxied must implement interfaces.
CGLIB dynamic proxies have no such mandatory requirement. In simple terms, CGLIB makes the generated proxy class inherit from the proxy class, and implements enhanced processing (pre processing, post processing, and so on) of the proxy methods in the proxy class.
Summarize what CGLIB does when it comes to proxy
- The generated proxy class inherits from the proxy class. One thing to note here is that if the delegate class is final, it cannot be inherited, that is, it cannot be proxied. Similarly, if there is a finally-decorated method in the delegate class, that method cannot be proxied
- The proxy class generates two methods for the delegate method. One is the method with the same signature as the delegate method, and it passes in the method
super
Call delegate methods; The other method is unique to the proxy class - When a method on a proxy object is executed, it is first determined whether an implementation exists
MethodInterceptor
Of the interfaceCGLIB$CALLBACK_0
; , if it exists, is calledMethodInterceptor
In theintercept
methods
In the Intercept method, we do some enhancement in addition to calling the delegate method. In Spring AOP, a typical application scenario is operation logging before and after the execution of some sensitive method
In CGLIB, method calls are not done by reflection, but by calling them directly: the FastClass mechanism is used for special handling of Class objects, such as holding a reference to a method in an array, and holding a reference to the method with an index index each time a method is called
Fastclass mechanism
CGLIB uses the FastClass mechanism to implement calls to intercepted methods.
The FastClass mechanism indexes the methods of a class and calls them directly
public class test10 {// Here, tt can be regarded as the target object, fc can be regarded as the proxy object; First, get the index of the target method based on the getIndex method of the proxy object.
// Then invoke the invoke method of the proxy object to call the method of the target class directly, avoiding reflection
public static void main(String[] args){
Test tt = new Test();
Test2 fc = new Test2();
int index = fc.getIndex("f()V");
fc.invoke(index, tt, null); }}class Test{
public void f(a){
System.out.println("f method");
}
public void g(a){
System.out.println("g method"); }}class Test2{
public Object invoke(int index, Object o, Object[] ol){
Test t = (Test) o;
switch(index){
case 1:
t.f();
return null;
case 2:
t.g();
return null;
}
return null;
}
// This method indexes the methods in the Test class
public int getIndex(String signature){
switch(signature.hashCode()){
case 3078479:
return 1;
case 3108270:
return 2;
}
return -1; }}Copy the code
In the example above, Test2 is the Fastclass of Test, and there are two methods getIndex and Invoke in Test2.
Create an index for each method of Test in the getIndex method and return the corresponding index based on the input parameter (method name + method descriptor).
Invoke calls methods on object O with ol as an incoming parameter, according to the specified index. This avoids reflection calls and improves efficiency
Comparison between the three proxy methods
Proxy mode | implementation | advantages | disadvantages | The characteristics of |
---|---|---|---|---|
JDK static proxy | The proxy class implements the same interface as the delegate class, and the hard-coded interface is required in the proxy class | Simple implementation and easy to understand | Proxy classes require hard-coded interfaces, which in practice can lead to repetitive coding, wasteful storage, and low efficiency | It doesn’t seem to have any characteristics |
JDK Dynamic proxy | The proxy class implements the same interface as the delegate class, mainly by implementing the InvocationHandler overwrite through the proxy classinvoke Method to perform dynamic proxy, the method is enhanced in the Invoke method |
No hard coded interface required, high code reuse rate | You can only proxy delegate classes that implement interfaces | The underlying mechanism uses reflection to invoke methods |
CGLIB dynamic proxy | The proxy class takes the delegate class as its parent and creates two methods for the non-final delegate methods in it. One is the method that has the same signature as the delegate method, and it passes in the methodsuper Call delegate methods; The other method is unique to the proxy class. In the proxy method, it determines whether there is an implementationMethodInterceptor Interface, if present, calls the intercept method to proxy the delegate method |
The class or interface can be enhanced at run time, and the delegate class does not need to implement the interface | To be unable tofinal Class and final methods |
The bottom layer stores all the methods in an array and calls them directly through the array index |
The problem
CGlib faster than JDK?
-
Using CGLiB to achieve dynamic proxy, CGLiB bottom using ASM bytecode generation framework, using bytecode technology to generate proxy class, before JDK6 than using Java reflection efficiency is higher. The only thing to note is that CGLib cannot proxy methods that are declared final, because CGLib works by dynamically generating subclasses of the proxied class.
-
After jdK6, JDK7, and JDK8 gradually optimize the JDK dynamic proxy, the efficiency of JDK proxy is higher than that of CGLIB proxy when the number of calls is less. Jdk6 and JDk7 are a little less efficient than the CGLIB agent only when a large number of calls are made, but by the time jdK8 is made, the JDK agent is more efficient than the CGLIB agent. In short, with each JDK version upgrade, the JDK agent is more efficient, and the CGLIB agent messages do not keep pace.
How does Spring choose to use JDK or CGLIB?
- Spring uses the JDK’s dynamic proxy when the Bean implements the interface.
- When beans do not implement interfaces, Spring uses CGlib implementations.
- You can force CGlib