Scan the qr code at the end of this article or search the wechat official account, you can follow the wechat official account, rookie Fei Yafei, read more Spring source analysis articles

Problem 1.

Here are some questions to consider before reading this article.

  • Why do JDK dynamic proxies need to be implemented based on interfaces and not inheritance?
  • In the JDK’s dynamic proxy, why is it that when you call another method inside a target object method, the other method doesn’t pass through the proxy object when it executes?

2. JDK dynamic proxy fixed writing method

  • JDK dynamic proxies are written in a fixed way, defining an interface and its implementation class, followed by an implementation class that implements the InvocationHandler interface. Then call the newInstance() method of the Proxy class. Example code is as follows:
  • Define an interface, UserService, that has two methods
public interface UserService {

    int insert(a);

    String query(a);
}
Copy the code
  • Define another implementation class for the UserService interface: UserServiceImpl
public class UserServiceImpl implements UserService{

    @Override
    public int insert(a) {
        System.out.println("insert");
        return 0;
    }

    @Override
    public String query(a) {
        System.out.println("query");
        return null; }}Copy the code
  • To define an InvocationHandler interface implementation class: UserServiceInvocationHandler. In the custom InvocationHandler, we define a property, target, which is defined to hold a reference to the target object in the InvocationHandler. The target property is initialized in the constructor.
public class UserServiceInvocationHandler implements InvocationHandler {

    // Hold the target object
    private Object target;

    public UserServiceInvocationHandler(Object target){
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("invocation handler");
        // Call the target object's method through reflection
        returnmethod.invoke(target,args); }}Copy the code
  • Create a Proxy object with the proxy.newProxyInstance () method
public class MainApplication {

    public static void main(String[] args) {
        // Specify a class loader to operate on the class file
        ClassLoader classLoader = MainApplication.class.getClassLoader();
        // Specify the interface to implement for the proxy object. Here we are creating a dynamic proxy for the target object, UserServiceImpl, so we need to implement the UserService interface for the proxy object
        Class[] classes = new Class[]{UserService.class};
        // Initialize an InvocationHandler and initialize the target object in the InvocationHandler
        InvocationHandler invocationHandler = new UserServiceInvocationHandler(new UserServiceImpl());
        // Create a dynamic proxy
        UserService userService = (UserService) Proxy.newProxyInstance(classLoader, classes, invocationHandler);
        // Execute the proxy object's methods, and by looking at the console's results, determine whether we have enhanced the methods of the target object (UserServiceImpl)userService.insert(); }}Copy the code
  • The console prints the following, which shows that the agent (enhancement) to the target object UserServiceImpl is complete.

3. How to view the class file of the proxy class

NewProxyInstance () method is used to create a Proxy object, which is used to enhance the method of the target object. How does that work?

  • The common way to learn a technology is to look at the source.java file or decompile the.class file using a decompile tool and analyze it. With JDK dynamic proxies, however, the proxy object is created at runtime, and there is no way to get the corresponding.class file for the proxy object through normal operations. If you can get the proxy object corresponding to the class file, that dynamic proxy principle, we will be good to analyze.
  • So there are two ways to get the.class file of a proxy object

3.1 Manually Writing Data to disks

  • . The call to the Proxy newProxyInstance () method, will call to ProxyGenerator. GenerateProxyClass () method, the effect of this method is to generate a Proxy object class file, the return value is a byte [] array. So we can write the generated byte[] array to disk through the output stream.
public static void main(String[] args) throws IOException {
    String proxyName = "com.tiantang.study.$Proxy0";
    Class[] interfaces = new Class[]{UserService.class};
    int accessFlags = Modifier.PUBLIC;
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
    // Write out the byte array to disk
    File file = new File("/Users/liujinkun/Downloads/dynamic/$Proxy0.class");
    OutputStream outputStream = new FileOutputStream(file);
    outputStream.write(proxyClassFile);
}
Copy the code
  • From the directory after running the main() method/Users/liujinkun/Downloads/dynamic/$Proxy0.classFind the generated file, because it is a class file, so we need to decompiler it to compile, for example: in idea, put the file in the target directory, open the file can see the decompilated code.

3.2 Automatic Writing to Disks

  • The second method is to search through Baidu, add a line of code in the code:System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"), can realize the dynamic proxy object generated in the process of running the class file written to disk. The following is an example:
public class MainApplication {

    public static void main(String[] args) {
        // Write the class file of the proxy object to disk
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles"."true");
        // Specify a class loader to operate on the class file
        ClassLoader classLoader = MainApplication.class.getClassLoader();
        // Specify the interface to implement for the proxy object. Here we are creating a dynamic proxy for the target object, UserServiceImpl, so we need to implement the UserService interface for the proxy object
        Class[] classes = new Class[]{UserService.class};
        // Initialize an InvocationHandler and initialize the target object in the InvocationHandler
        InvocationHandler invocationHandler = new UserServiceInvocationHandler(new UserServiceImpl());
        // Create a dynamic proxy
        UserService userService = (UserService) Proxy.newProxyInstance(classLoader, classes, invocationHandler);
        // Execute the proxy object's methods, and by looking at the console's results, determine whether we have enhanced the methods of the target object (UserServiceImpl)userService.insert(); }}Copy the code
  • Run the program and finally find a package in the root directory of the project: com.sun.proxy. There is a file under the package$Proxy0.class. Open in IDEA and find the source code for the generated proxy class.

4. Problem solving

The source code of the proxy object obtained by the above two methods is as follows:

package com.sun.proxy;

import com.tiantang.study.UserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements UserService {
    private static Method m1;
    private static Method m3;
    private static Method m4;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw newUndeclaredThrowableException(var4); }}public final int insert(a) throws  {
        try {
            return (Integer)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}public final String query(a) throws  {
        try {
            return (String)super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}public final String toString(a) throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}public final int hashCode(a) throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.tiantang.study.UserService").getMethod("insert");
            m4 = Class.forName("com.tiantang.study.UserService").getMethod("query");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw newNoClassDefFoundError(var3.getMessage()); }}}Copy the code
  • Through source code we found that $Proxy0 class inherit Proxy class, at the same time the implementation of UserService interface. Dynamic proxies in the JDK can only be implemented based on interfaces, not inheritance. Java does not support multiple inheritance, and dynamic proxies in the JDK inherit Proxy classes by default when creating Proxy objects, so the JDK can only implement dynamic proxies through interfaces.

  • $Proxy0 implements the UserService interface, so it overrides two methods in the interface ($Proxy0 also overrides several methods in the Object class). So when we call query(), we call $proxy0.query (). In this method, we call super.h.invoke() directly. The parent class is Proxy, and h in the parent class is InvocationHandler. So here will be called to UserServiceInvocationHandler. The invoke () method. So when the invocation object is invoked from the invocation object, the Invoke () method of the InvocationHandler is first invoked and then the method. Invoke () reflection is used to invoke the target method, so the invocation Handler is printed first.

public class UserServiceInvocationHandler implements InvocationHandler {

    // Hold the target object
    private Object target;

    public UserServiceInvocationHandler(Object target){
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("invocation handler");
        // Call the target object's method through reflection
        returnmethod.invoke(target,args); }}Copy the code
  • For the second question at the beginning of this article, let’s take a look at the following example code
public class UserServiceImpl implements UserService{

    @Override
    public int insert(a) {
        System.out.println("insert");
        query();
        return 0;
    }

    @Override
    public String query(a) {
        System.out.println("query");
        return null; }}Copy the code
public class MainApplication {

    public static void main(String[] args) {
        // Write the class file of the proxy object to disk
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles"."true");
        // Specify a class loader to operate on the class file
        ClassLoader classLoader = MainApplication.class.getClassLoader();
        // Specify the interface to implement for the proxy object. Here we are creating a dynamic proxy for the target object, UserServiceImpl, so we need to implement the UserService interface for the proxy object
        Class[] classes = new Class[]{UserService.class};
        // Initialize an InvocationHandler and initialize the target object in the InvocationHandler
        InvocationHandler invocationHandler = new UserServiceInvocationHandler(new UserServiceImpl());
        // Create a dynamic proxy
        UserService userService = (UserService) Proxy.newProxyInstance(classLoader, classes, invocationHandler);
        // Execute the proxy object's methods, and by looking at the console's results, determine whether we have enhanced the methods of the target object (UserServiceImpl)userService.insert(); }}Copy the code
  • In this code, we just change the Insert () method of UserServiceImpl, and after printing, we call the Query () method. Before we look at the print, let’s analyze the print: as we understand it$Proxy0The proxy class enhances the methods in UserServiceImpl, and each time a method of the Class in UserServiceImpl is called, it should pass through the Invoke () method in InvocationHandler, printing each timeinvocation handlerThis sentence. So when we calluserService.insert(), the printed result should be:
invocation handler
insert
invocation handler
query
Copy the code
  • Then, are the results really what we think they are? The following screenshot is the result of the program running:

  • From the results, we can see that it was printed only onceinvocation handler. When the Query () method is called, the invoke() method in InvocationHandler is not executed. Why is that? And here’s why:
public int insert(a) {
    System.out.println("insert");
    query();
    return 0;
}
Copy the code
  • When the query() method is called from the insert() method, it is actually called this.query(), and this object is UserServiceImpl, which is not$Proxy0This proxy object will only pass through the invocationHandler.invoke () method when the query() method of the proxy object is called, so it will only print onceinvocation handler. So the answer to the second question is: During the call, the orientation of this changed.

5. Summarize and think

  • This article introduces the general usage of JDK dynamic proxy, and then through special means, to obtain the class file of the proxy object, and then according to the class file, analyzes two more common problems in JDK dynamic proxy.
  • You’ve seen how JDK dynamic proxies work, and the problem with JDK dynamic proxies is that they do not enhance method B by calling other methods of the target object from within method A.
  • Transactional Transactional Transactional Transactional Transactional Transactional Transactional Transactional Transactional Transactional Transactional Transactional Transactional Transactional Transactional Transactional Transactional Transactional For example of Spring transaction failure, you can search: JDK dynamic proxy transaction failure.
  • There are also cases such as @async, which enable asynchrony in two methods of the same class and then call each other inside the methods, eventually leading to asynchrony failure.
  • Given these problems with JDK dynamic proxies, how do you avoid them now in Spring? The famous CGLIB, of course, for the next article on CGLIB.

6. I guess you like it

  • See the Bean creation process from the source code
  • The Spring source code series container startup process
  • In the Spring! The most! The most! Important afterprocessor! No one!!
  • @ Import and @ EnableXXX
  • Write a Redis/Spring integration plug-in by hand

You can scan the qr code below to follow the wechat official account, Cainiao Feiyafei, read more Spring source code together.