After reading this article, you can't understand "dynamic agent"

Posted by ScubaDvr2 on Mon, 24 Jan 2022 11:24:35 +0100

Although I learned Static proxy However, Zhaocai is still a little depressed these days, because he hasn't thought of it last time Gyroscope left its own solution to the problem.

How to add the same processing logic before and after any method of any object?

It is impossible to manually add the same piece of code logic to each method of each object. It is impossible in this life. "Laziness" is an important driving force for scientific and technological progress!

Thinking failed, Zhaocai finally asked for help.

Overstretched static agents

"Master, I didn't understand the question you left me last time. What's the practical significance of this demand?" Get to the point.

Gyro said, "if you can really add your own logic before and after any method, it will do too much! You can verify the operation permission before the logic runs; you can also start a transaction before the logic runs, and commit or roll back the transaction after the logic is completed. How to use this function depends entirely on your imagination."

"I didn't expect it to have such a great effect! So how can it be realized?"

"Do you think static proxy can solve this problem?" Asked the gyro.

Zhaocai replied, "yes, we can. We can write a static agent for each class for each logic, but that's the problem. If there are many classes and many agent logic, it will cause a class explosion."

"I think static proxy is more suitable for implementing proxy for some specific interfaces, and proxy objects must be created explicitly." Zhaocai continued.

Gyro: "you're right. The problem is that static agents need to explicitly create proxy objects. If we can dynamically generate proxy objects, and the user has no perception of this generation process, can this problem be solved?"

"Is there really such a method?" Zhaocai's eyes are shining.

"This is the dynamic agent. It's really difficult. We need to finish it a little bit. Follow my ideas and ensure that you can fully understand the dynamic agent!" Gyro confidently said to Zhaocai.

The birth of dynamic agent

"First, recall the log agent you wrote in the static agent." Then the gyro gave the code.

//Code 1-1
package designPattern.proxy.dynamicProxy.v1;

import designPattern.proxy.dynamicProxy.Payable;

public class SiShiDaDaoLogProxy implements Payable {

    //Proxied object
    private Payable payable;

    public SiShiDaDaoLogProxy(Payable payable) {
        this.payable = payable;
    }


    @Override
    public void pay() {
        System.out.println("Print log 1");

        payable.pay();

        System.out.println("Print log 2");
    }
}

"You should be very familiar with this code." The top asked Zhaocai.

"Yes, payable is the proxy object, and sishidaologproxy is the generated proxy class. The proxy object implements the payable interface, which is logically enhanced when rewriting the pay() method, but essentially it still calls the method of the proxy object." Zhaocai answered fluently.

Gyro nodded. "Well, suppose we get the above source code in some way, and now our goal is to dynamically generate this proxy object."

"Dynamic generation? I only know that when I start learning Java, I usually write a HelloWorld.java source file first, and then compile it into HelloWorld.class file with javac tools. Does your dynamic generation have anything to do with this?" Zhaocai asked.

"The principle is similar. We need to write the above sishidaologproxy to the disk to generate a. java file, then use the compilation tool provided by JDK to convert it into a. Class file, and then load the. Class file into the JVM through the class loader. Finally, we can obtain the sishidaologproxy instance object through reflection."

"This... There are too many knowledge points involved! I haven't even heard of the compilation tools provided by JDK, and I almost forget all the knowledge of class loading, so I always encounter reflection in the framework. I'm still a little impressed. Master, do I have to supplement these knowledge points first?" Zhaocai asked a little desperate.

"Your idea is a common problem for many beginners. When learning a knowledge point, you always unconsciously learn other relevant knowledge points. Finally, you forget your first learning purpose and put the cart before the horse. Remember, you should master the context first and then learn the details!" Gyro color track.

Looking at Zhaocai, I'm still a little unsure, Continue: "Don't worry. If you didn't encounter this dynamic agent, you might not be able to use the JDK built-in compiler in your life, so you just need to know what its role is, and you don't need to understand the code. As for the class loading mechanism, you need to understand that we need a class loader to load the. Class file obtained in the previous step into the JVM virtual machine, so as to generate the instance object That's enough. As for reflection, you really should master it. Fortunately, it is very simple. You can understand it with my ideas. "

Gyro's words reassured Zhaocai a lot and renewed his spirit.

v1.0 -- compile a section of source code dynamically first

"Let's create a Proxy class and define a static method of newProxyInstance in it. This method returns an Object object, which is the Proxy Object we finally generated." After that, the gyro gives the code.

/**
 * @author chanmufeng
 * @description Dynamic agent v1
 * @date 2022/1/10
 */
public class Proxy {

    //Define line breaks
    private static final String ln = "\r\n";

    public static Object newProxyInstance(ClassLoader classLoader) {
        try {
            
            /** 1.Generate source code**/
            String src = generateSrc();

            /** 2.Write the source code to disk and generate java file**/
            File file = createJavaFile(src);

            /** 3.To be generated java file compiled into class file**/
            compile(file);

            /** 4.The class loader will Load class file into JVM**/
            Class proxyClass = classLoader.loadClass("SiShiDaDaoLogProxy");
            
            /** 5.Instantiate objects with reflections**/
            Constructor proxyConstructor = proxyClass.getConstructor(Payable.class);
            file.delete();
            Payable p = (Payable) proxyConstructor.newInstance(new SiShiDaDao());
            
            return p;

        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return null;

    }

}

"In order to facilitate your understanding, I have encapsulated the code of each step. In step 2 and step 3, you only need to understand their meaning. The specific code is not the focus of the research. The code of these two steps will hardly change in the next description. Therefore, in the following description, I will use createJavaFile and compile to replace the two steps respectively, and will not give you any more Specify the code. " Gyro explained to Zhaocai.

"In this way, the customer only needs to call Proxy.newProxyInstance(ClassLoader classLoader) to get the sishidaologproxy object instance, right?" Zhaocai asked.

"That's right."

"However, I see that the newProxyInstance method has a parameter and needs to pass a ClassLoader. What does this parameter mean?" Zhaocai asked a little puzzled.

"Remember that we need a classloader to load the. class file generated in step 3 into the JVM? This parameter is an instance of the classloader. Providing this parameter allows customers to flexibly select different classloaders to complete this operation."

Zhaocai pouted, "I don't understand the necessity of this parameter. Wouldn't it be better for you to directly default to a class loader? I think most users don't know what value this parameter should pass."

"Don't worry, then you will know my intention to design this parameter. In order to let you know how to pass this parameter, I have customized a class loader, which is not difficult."

"There is step 5. I don't understand it very well." Zhaocai continued to ask.

"Don't worry. First look at all our codes so far, and then explain them to you."

package designPattern.proxy.dynamicProxy;

/**
 * @author Cicada bathes in the wind
 * @description Payment interface
 * @date 2022/1/10
 */
public interface Payable {

    /**
     * Payment interface
     */
    void pay();
}
package designPattern.proxy.dynamicProxy;

import java.util.concurrent.TimeUnit;

/**
 * @author Cicada bathes in the wind
 * @description 「The third-party interface provided by the "Forty Thieves" financial company has realized the payment interface
 * @date 2022/1/10
 */
public class SiShiDaDao implements Payable {

    @Override
    public void pay() {
        try {
            // ...
            System.out.println("「The Forty Thieves」Payment interface calling......");
            //Simulate method call delay
            TimeUnit.MILLISECONDS.sleep((long) (Math.random() * 6000));
            // ...
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
/**
 * @author Cicada bathes in the wind
 * @description Dynamic agent v1
 * @date 2022/1/10
 */
public class Proxy {

    //Define line breaks
    private static final String ln = "\r\n";

    public static Object newProxyInstance(ClassLoader classLoader) {
        try {

            /** 1.Generate source code**/
            String src = generateSrc();

            /** 2.Write the source code to disk and generate java file**/
            File file = createJavaFile(src);

            /** 3.To be generated java file compiled into class file**/
            compile(file);

            /** 4.The class loader will Load class file into JVM**/
            Class proxyClass = classLoader.loadClass("SiShiDaDaoLogProxy");
            
            /** 5.Instantiate objects with reflections**/
            Constructor proxyConstructor = proxyClass.getConstructor(Payable.class);
            file.delete();
            Payable p = (Payable) proxyConstructor.newInstance(new SiShiDaDao());
            return p;

        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;

    }

    private static String generateSrc() {
        StringBuilder sb = new StringBuilder();
        sb.append("package designPattern.proxy.dynamicProxy.v1;").append(ln)
                .append("import designPattern.proxy.dynamicProxy.Payable;").append(ln)
                .append("public class SiShiDaDaoLogProxy implements Payable { ").append(ln)
                .append("    private Payable payable;").append(ln)
                .append("    public SiShiDaDaoLogProxy(Payable payable) {").append(ln)
                .append("        this.payable = payable;").append(ln)
                .append("    }").append(ln)
                .append("    @Override").append(ln)
                .append("    public void pay() {").append(ln)
                .append("        System.out.println(\"Print log 1\");").append(ln)
                .append("        payable.pay();").append(ln)
                .append("        System.out.println(\"Print log 2\");").append(ln)
                .append("    }").append(ln)
                .append("}");
        return sb.toString();
    }

    private static File createJavaFile(String src) throws Exception {
        String filePath = Proxy.class.getResource("").getPath();
        File file = new File(filePath + "SiShiDaDaoLogProxy.java");
        FileWriter fw = new FileWriter(file);
        fw.write(src);
        fw.flush();
        fw.close();
        return file;
    }

    private static void compile(File file) throws IOException {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
        Iterable iterable = manager.getJavaFileObjects(file);
        JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
        task.call();
        manager.close();
    }
}
/**
 * @author Cicada bathes in the wind
 * @description Custom class loader
 * @date 2022/1/10
 */
public class MyClassLoader extends ClassLoader {

    private File classPathFile;

    public MyClassLoader() {
        String classPath = MyClassLoader.class.getResource("").getPath();
        this.classPathFile = new File(classPath);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        String className = MyClassLoader.class.getPackage().getName() + "." + name;
        if (classPathFile != null) {
            File classFile = new File(classPathFile, name.replaceAll("\\.", "/") + ".class");
            if (classFile.exists()) {
                FileInputStream in = null;
                ByteArrayOutputStream out = null;
                try {
                    in = new FileInputStream(classFile);
                    out = new ByteArrayOutputStream();
                    byte[] buff = new byte[1024];
                    int len;
                    while ((len = in.read(buff)) != -1) {
                        out.write(buff, 0, len);
                    }
                    return defineClass(className, out.toByteArray(), 0, out.size());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }
}

"Let me repeat what we have done so far." He explained patiently.

  1. We obtained the source code of sishidaologproxy class through the generateSrc method. This source code is the static proxy code we showed you at the beginning (focus)

  2. Write the source file to disk to generate sishidaologproxy Java files (not the point)

  3. Using the compilation tool provided by JDK, sishidaologproxy Java is compiled into sishidaologproxy Class file (not important)

  4. Use a custom class loader (MyClassLoader) to set sishidaologproxy Class loaded into memory (not important)

  5. Use reflection to get the instance object of sishidaologproxy (focus)

"You will find that the source code generated by generateSrc has only one constructor with parameters. Therefore, in step 5, you need to obtain the constructor object with parameters through reflection and pass in new sishidao() for instantiation. The effect is the same as the following code."

new SiShiDaDaoLogProxy(new SiShiDaDao());

"I see," Zhao Cai nodded, "but you said that two files were generated in step 2 and step 3 respectively. Where are the two files saved?"

"That's the magic of dynamic proxy! It automatically generates source code files and bytecode files for you, but you don't know it. You don't even need to know the name of the automatically generated class. I won't tell you where the file is saved here, because this problem doesn't matter. You can run the code and see it yourself later." Gyro explained, "let's run the client program now and see what the results are."

/**
 * @author Cicada bathes in the wind
 * @description Call client
 * @date 2022/1/10
 */
public class Client {
    public static void main(String[] args) {
        Payable payable = (Payable) Proxy.newProxyInstance(new MyClassLoader());
        payable.pay();
    }
}

The operation results are as follows

Seeing the excitement of the gyro, it was a little difficult to recruit money, because she didn't understand that after tossing for so long, she finally got the same running effect as the previous static agent.

I love my teacher, I love truth more!

Zhaocai summoned up his courage and asked, "this result is no different from the running result of static agent, isn't it?"

Gyroscope heard her confusion from the euphemism of Zhaocai, "although the results are the same, the implementation mechanism has undergone earth shaking changes. Have you found that we have not written any proxy classes. Before, the static proxy also needed to write sishidaologproxy, which is completely generated automatically."

"I understand what you said. But at present, all the automatically generated code is dead code, that is, at present, we can only proxy the pay() method in the sishidao class, and the effect is far from satisfactory."

"You're right. Next, let's make some improvements. At this stage, our goal is to get an object that can proxy a class that implements any interface, so that our log logic will be added before and after each method in the proxy class."

v2.0 -- log agent for classes that implement any interface

Gyro asked Zhaocai, "if you were a designer and asked you to design this interface from the perspective of users, how would you design it?"

Zhaocai thought for a while, "another parameter should be added to the newProxyInstance method to refer to the interface implemented by the proxy object, which means that I want to get the proxy object of the class that implements this interface."

/**
 * @author chanmufeng
 * @description Dynamic agent v2
 * @date 2022/1/10
 */
public class Proxy {

	...
        
    public static Object newProxyInstance(ClassLoader classLoader, Class intfce) {
        
        ...
        
    }

}

"Very good. In this way, we can't write the implementation relationship of the generated class in the generateSrc method, and we need to make some changes. Look at the figure below, all the parts circled with red lines need to be modified dynamically, and what's more troublesome is that we also need to dynamically generate all the methods declared in this interface, including method parameters and return value information. ”


"I think it must be inseparable from reflection." Zhaocai said helplessly.

"Yes, focus on the idea. Don't worry, the code is easy to understand, but you need to read it several times. Next, let's implement the new generateSrc method." Gyro continued, "but the following code may make you a little uncomfortable, because the readability of obtaining the source code by splicing strings is very poor. But first experience the idea, and then I will let you see the final dynamically generated source code content, and you will understand what the following code does."

private static String generateSrc(Class intfce) {

        //Get the package name of the interface
        String packageName = intfce.getPackage().getName() + "." + intfce.getSimpleName();

        StringBuilder sb = new StringBuilder();
        sb.append("package designPattern.proxy.dynamicProxy.v2;").append(ln)
                .append("import ").append(packageName).append(";").append(ln)
                .append("public class $Proxy0 implements ").append(intfce.getName()).append(" { ").append(ln)
                .append("    private ").append(intfce.getSimpleName()).append(" obj;").append(ln)
                .append("    public $Proxy0(").append(intfce.getSimpleName()).append(" obj) {").append(ln)
                .append("        this.obj = obj;").append(ln)
                .append("    }").append(ln).append(ln)

                .append(generateMethodsSrc(intfce))

                .append("}").append(ln).append(ln);

        System.out.println(sb.toString());
        return sb.toString();
    }

    private static StringBuilder generateMethodsSrc(Class intfce) {
        StringBuilder sb = new StringBuilder();

        for (Method m : intfce.getMethods()) {
            sb.append("    @Override").append(ln);

            Class<?>[] params = m.getParameterTypes();
            StringBuilder paramNames = new StringBuilder();
            StringBuilder paramValues = new StringBuilder();
            StringBuilder paramClasses = new StringBuilder();

            for (int i = 0; i < params.length; i++) {
                Class clazz = params[i];
                String type = clazz.getName();
                String paramName = toLowerFirstCase(clazz.getSimpleName()) + i;
                paramNames.append(type + " " + paramName);
                paramValues.append(paramName);
                paramClasses.append(clazz.getName() + ".class");
                if (i < params.length - 1) {
                    paramNames.append(",");
                    paramValues.append(",");
                    paramClasses.append(",");
                }
            }

            sb.append("    public ").append(m.getReturnType().getName()).append(" ").append(m.getName())
                    .append("(").append(paramNames).append("){").append(ln);

            sb.append("        System.out.println(\"Print log 1\");").append(ln)
                    .append("        obj.").append(m.getName()).append("(").append(paramValues).append(");").append(ln)
                    .append("        System.out.println(\"Print log 2\");").append(ln)
                    .append("    }").append(ln).append(ln);

        }

        return sb;
    }

    private static String toLowerFirstCase(String src) {
        char[] chars = src.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }
public static Object newProxyInstance(ClassLoader classLoader, Class intfce) {
        try {

            /** 1.Generate source code**/
            String src = generateSrc(intfce);

            /** 2.Write the source code to disk and generate java file**/
            File file = createJavaFile(src);

            /** 3.To be generated java file compiled into class file**/
            compile(file);

            /** 4.The class loader will Load class file into JVM**/
            Class proxyClass = classLoader.loadClass("$Proxy0");
            Constructor proxyConstructor = proxyClass.getConstructor(intfce);
            file.delete();
            Payable p = (Payable) proxyConstructor.newInstance(new SiShiDaDao());
            return p;

        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;

    }

The client calls

public class Client {
    public static void main(String[] args) {
        Payable payable = (Payable) Proxy.newProxyInstance(new MyClassLoader(), Payable.class);
        payable.pay();
    }
}

The operation results are as follows


The dynamically generated code is as follows

Note: the code is the original content generated dynamically and is not formatted by IDE

package designPattern.proxy.dynamicProxy.v2;
import designPattern.proxy.dynamicProxy.Payable;
public class $Proxy0 implements designPattern.proxy.dynamicProxy.Payable { 
    private Payable obj;
    public $Proxy0(Payable obj) {
        this.obj = obj;
    }

    @Override
    public void pay(){
        System.out.println("Print log 1");
        obj.pay();
        System.out.println("Print log 2");
    }

}

"Although the generateSrc method looks troublesome, the final result of the generation is easy to understand. It is to generate a class that implements an interface and add log logic before and after rewriting all methods of the interface," gyro explained

"I understand the logic, but I'm a little confused about the code of generateSrc. I'll ignore the details of generateSrc for the time being and grasp the overall idea first. I have two questions. First, I see that the automatically generated class name has changed from sishidaologproxy to $Proxy0. Why?" Zhaocai raises the first question.

"Good eyesight. In the process of proxy object generation, you will find that we haven't used the name of this class from beginning to end, so it doesn't matter what the name is. In addition, dynamic proxy will return different proxy objects according to the different parameters we pass in, so I simply gave a neutral name Proxy0. As for why we start with $, because JDK has a Specification, as long as it starts with $under ClassPath Class files are generally generated automatically. I just follow this specification. "

"Second, the function of the current version is to get the proxy of the class that implements any interface, and when the interface object passed in by the client is Payable.class, we also get the expected running results. However, I think this is just that the parameter passed in happens to be Payable.class. If other interface classes are passed in, such as Comparable.class, I don't think the client The client can call successfully because the parameter passed during object instantiation of newProxyInstance method is new sishidao(). " Zhaocai refers to the code.

// The parameter is written dead
Payable p = (Payable) proxyConstructor.newInstance(new SiShiDaDao());

"When the parameter is Comparable.class, we need to pass in the object instance that implements the Comparable interface. Am I right, master?" Zhaocai gloated.

The growth of Zhaocai surprised gyroscope, smiled and said, "you're right. If the parameter passed in is not Payable.class, it can generate the code we expect, but it can't run. The reason is as you just said. Not only that, at present, the automatically generated agent class can only add fixed log logic. We hope this logic can be defined by the user."

"So, the third version is coming." Zhao Cai is eager to listen to the top and continue.

"That's right!"

v3.0 -- act as an arbitrary proxy for a class that implements any interface

"If you want users to customize the logic, you should naturally have one more parameter when calling the newProxyInstance method. Obviously, the logic passed in by each user is different, but there is only one parameter. What did you think of?" The top asked Zhaocai.

"Polymorphism. This parameter should be an interface or a highly abstract class. Users can implement interfaces or rewrite methods to write their own logic."

"That's right. Here we use the interface. I name this interface InvocationHandler and define a method invoke in it. Users must rewrite this method to write their own logic."

public interface InvocationHandler {

    Object invoke(...) throws Throwable;

}

"This is the declaration of our newProxyInstance method."

/**
 * @author Cicada bathes in the wind
 * @description Dynamic agent v3
 * @date 2022/1/14
 */
public class Proxy {
	
    ...

    public static Object newProxyInstance(ClassLoader classLoader, Class intfce, InvocationHandler handler) {
       
        ...
            
    }
    
    ...
 
}

"Next, we need to determine the parameters in the invoke method," gyro continued. "Because we need to add logic before and after the method, the code structure should be like this when the user implements the InvocationHandler interface and rewrites the invoke method." After that, the gyro gives the code.

public class LogInvocationHandler implements InvocationHandler {
    
    @Override
    public Object invoke(...) throws Throwable {
        // Logical processing before method call
        before();

        //Make the actual method call here
        ...

        // Logical processing after method call
        after();
    }
    
    private void before() {
        System.out.println("Print log 1");
    }

    private void after() {
        System.out.println("Print log 2");
    }
}

Gyro then said, "we need to call a Method between the before and after methods, and we can pass in the Method object, so we can use reflection to call this Method. Therefore, the invoke Method should at least contain the Method object and Method parameters, such as invoke(Method m, Object[] args)."

Zhaocai asked a question: "but when you call a method by reflection, you still need to know which object's method is called. How can you get this parameter?"

Gyro replied, "it's easy. When implementing InvocationHandler, we can create a constructor and pass in the proxy object through the constructor. In this way, the code becomes like this."

public class LogInvocationHandler implements InvocationHandler {

    // Proxied object
    private Object target;

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

    @Override
    public Object invoke(Method m, Object[] args) throws Throwable {
        before();
        Object res = m.invoke(target, args);
        after();

        return res;
    }

    private void before() {
        System.out.println("Print log 1");
    }

    private void after() {
        System.out.println("Print log 2");
    }

}

Seeing here, Zhaocai has opened his eyes, Shout out: "I see! The invoke Method we rewrite now actually contains the most complete logic, and this Object will also be passed into the newProxyInstance Method as a parameter, that is, in the proxy Object automatically generated later, just call the invoke Method of the LogInvocationHandler instance Object, and then connect the Method parameter and Object [] Just pass in the parameter! "

Looking at the excited appearance of Zhaocai, gyro couldn't help but be happy, "hahaha, that's right! You have said the core idea of dynamic proxy. Now, regardless of the implementation details inside the newProxyInstance function, how can the client call the encapsulation we have completed?"

"First, we need to create a proxy object. Here we take the instance object of SiShiDaDao as an example; secondly, we implement the InvocationHandler interface to rewrite the invoke method and create our own logic; secondly, we call the Proxy.newProxyInstance method to get the proxy object; finally, we call the proxy object's target method." Zhaocai answered fluently.

public class Client {
    public static void main(String[] args) {

        // Create proxied object
        SiShiDaDao target = new SiShiDaDao();

        // Implement your own logic
        InvocationHandler logHandler = new LogInvocationHandler(target);
        
        // Get proxy object
        Payable proxy = (Payable) Proxy.newProxyInstance(new MyClassLoader(), Payable.class, logHandler);
        
        // Call proxy object target method
        proxy.pay();
    }
}

"Is the code I wrote right, master?" Zhaocai looked proud. "Next, can we see the implementation details of the newProxyInstance method?"

The gyro waved his hand, "don't worry! Before understanding the details of newProxyInstance, you need to understand what the automatically generated source code of newProxyInstance should look like. Try to write it, and use the parameters of the client call you just wrote."

Zhaocai thought about it and gave his own code.

public class $Proxy0 implements Payable { 
    
    private InvocationHandler h;
    
    public $Proxy0(InvocationHandler h) {
        this.h = h;
    }

    @Override
    public void pay(){
        
        Method m = Payable.class.getMethod("pay");
        this.h.invoke(m,new Object[]{});
        
    }

}

"Well," nodded the top, "the general idea is right, but there are some small problems."

"Tell me."

"First, Payable should be written as a fully qualified class name designPattern.proxy.dynamicProxy.Payable, so that no matter what interface type is passed in, there will be no problem during compilation."

"Second, when obtaining the Method, you pass in the Method name, which is not enough. Because there may be Method overloading, that is, the Method name is the same but the Method parameters are different. Therefore, it is better to obtain the Method object according to the Method name and Method parameters at the same time."

"Third, the pay() method does not catch exceptions, because all methods in $Proxy0 use reflection and need to catch exceptions."

"After paying attention to these three points, can I implement the details of newProxyInstance?" Zhaocai couldn't wait to ask.

"Yes, you are now fully capable of achieving it, but you need to add a billion details!"

"Billion points???"

Gyro said: "because the method pay() declared in the Payable interface is very simple, and there are neither return values nor method parameters, you need to consider the return values and method parameters in the implementation details. However, the details are not important to you, because you understand the principle and have mastered the essence of dynamic agent. Let me show you the code directly!"

The code may cause discomfort and can be skipped directly or accessed github Get the complete code and run it yourself. The effect is better

/**
 * @author Cicada bathes in the wind
 * @description Dynamic agent v3
 * @date 2022/1/14
 */
public class Proxy {

    //Define line breaks
    private static final String ln = "\r\n";

    public static Object newProxyInstance(ClassLoader classLoader, Class intfce, InvocationHandler h) {

        try {

            /** 1.Generate source code**/
            String src = generateSrc(intfce);

            /** 2.Write the source code to disk and generate java file**/
            File file = createJavaFile(src);

            /** 3.To be generated java file compiled into class file**/
            compile(file);

            /** 4.The class loader will Load class file into JVM**/
            Class proxyClass = classLoader.loadClass("$Proxy0");
            Constructor proxyConstructor = proxyClass.getConstructor(InvocationHandler.class);
            file.delete();

            return proxyConstructor.newInstance(h);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;

    }

    private static String generateSrc(Class intfce) {

        //Get the package name of the interface
        String packageName = intfce.getPackage().getName() + "." + intfce.getSimpleName();

        StringBuilder sb = new StringBuilder();
        sb.append("package designPattern.proxy.dynamicProxy.v3;").append(ln)
                .append("import ").append(packageName).append(";").append(ln)
                .append("import java.lang.reflect.*;").append(ln)
                .append("public class $Proxy0 implements ").append(intfce.getName()).append(" { ").append(ln)
                .append("    private InvocationHandler h;").append(ln)
                .append("    public $Proxy0(InvocationHandler h) {").append(ln)
                .append("        this.h = h;").append(ln)
                .append("    }").append(ln).append(ln)

                .append(generateMethodsSrc(intfce))

                .append("}").append(ln).append(ln);

        System.out.println(sb.toString());
        return sb.toString();
    }

    private static StringBuilder generateMethodsSrc(Class intfce) {
        StringBuilder sb = new StringBuilder();

        for (Method m : intfce.getMethods()) {
            sb.append("    @Override").append(ln);

            Class<?>[] params = m.getParameterTypes();
            StringBuilder paramNames = new StringBuilder();
            StringBuilder paramValues = new StringBuilder();
            StringBuilder paramClasses = new StringBuilder();

            for (int i = 0; i < params.length; i++) {
                Class clazz = params[i];
                String type = clazz.getName();
                String paramName = toLowerFirstCase(clazz.getSimpleName()) + i;
                paramNames.append(type + " " + paramName);
                paramValues.append(paramName);
                paramClasses.append(clazz.getName() + ".class");
                if (i < params.length - 1) {
                    paramNames.append(",");
                    paramValues.append(",");
                    paramClasses.append(",");
                }
            }

            sb.append("    public ").append(m.getReturnType().getName()).append(" ").append(m.getName())
                    .append("(").append(paramNames).append("){").append(ln);
            sb.append("        try{").append(ln);
            sb.append("            Method m = ").append(intfce.getName()).append(".class.getMethod(").append("\"" + m.getName() + "\",").append("new Class[]{").append(paramClasses.toString()).append("});").append(ln);
            sb.append(hasReturnValue(m.getReturnType()) ? "            return " : "            ").append(getReturnCode("this.h.invoke(m,new Object[]{" + paramValues + "})", m.getReturnType())).append(";").append(ln);

            sb.append(getReturnEmptyCode(m.getReturnType()));
            sb.append("        }catch(Throwable e){}").append(ln);
            sb.append("    }").append(ln).append(ln);

        }

        return sb;
    }

    private static Map<Class, Class> mappings = new HashMap<Class, Class>();

    static {
        mappings.put(int.class, Integer.class);
    }

    private static String getReturnEmptyCode(Class<?> returnClass) {
        if (mappings.containsKey(returnClass)) {
            return "return 0;";
        } else if (returnClass == void.class) {
            return "";
        } else {
            return "return null;";
        }
    }

    private static boolean hasReturnValue(Class<?> clazz) {
        return clazz != void.class;
    }

    private static String getReturnCode(String code, Class<?> returnClass) {
        if (mappings.containsKey(returnClass)) {
            return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() + "Value()";
        }
        return code;
    }

    private static String toLowerFirstCase(String src) {
        char[] chars = src.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    private static File createJavaFile(String src) throws Exception {
        String filePath = Proxy.class.getResource("").getPath();
        File file = new File(filePath + "$Proxy0.java");
        FileWriter fw = new FileWriter(file);
        fw.write(src);
        fw.flush();
        fw.close();
        return file;
    }

    private static void compile(File file) throws IOException {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
        Iterable iterable = manager.getJavaFileObjects(file);
        JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
        task.call();
        manager.close();
    }
}

Note: Although the code deals with the problem of method return values and parameters, there are still many details that are not perfect. For example, it will rewrite all methods in the interface, including static and private methods, which is obviously wrong

You just need to understand the spirit. The details here are not important to us

"Now, we can finally add our own logic before and after implementing any method of any object of any interface!" Zhaocai shouted excitedly.

Gyro smiled: "Congratulations, so far, you have completely mastered the most difficult design pattern - dynamic Proxy. Now you will find that the Proxy class and InvocationHandler interface we have worked hard to design no longer need to be changed."

"Yes, then we can encapsulate this function and use dynamic agents in our project." Zhaocai is a little excited.

"As like as two peas, we have to admit that our JDK functions are not perfect. Fortunately, we have encapsulated dynamic agents for us. In fact, all of the work we do step by step is simulating the dynamic agents provided by JDK, including the names of interfaces and methods, which are exactly the same as those of JDK. But on some parameters, we are in dynamic generation with JDK. It's a little different. "

"What parameters are different?" Zhaocai asked.

"The newProxyInstance method we designed is slightly different from that of JDK. The second parameter of JDK is an array, but it doesn't matter. You just need to know this."

// We designed it
Object newProxyInstance(ClassLoader classLoader,
                        Class intfce,
                        InvocationHandler h)


// Provided by JDK
Object newProxyInstance(ClassLoader loader, 
                        Class<?>[] interfaces,
                        InvocationHandler h)

Gyro continued: "there is another important parameter, but we haven't given it in the current version. Even many programs don't know the meaning of this parameter in JDK."

This can completely stimulate the curiosity of Zhaocai, "what is this parameter?"

Instead of directly answering Zhaocai, Luo Ming asked, "Zhaocai, what are the advantages and disadvantages of our current dynamic agent?"

I don't know how to get rich, but since Shifu asked, he always had to answer, "The advantage is that users don't need to pay attention to the implementation details of newProxyInstance. They just need to implement the InvocationHandler interface, add their own logic to the invoke method, and then create their own proxy object according to the steps. The disadvantage is that they can only obtain the proxy object at the end and define the logic in the invoke method The management object has no operation permission. "

Gyroscope nodded approvingly, "that's the point! Although most users don't use proxy objects directly in invoke, JDK provides this parameter for the sake of functional perfection. Next, we will modify our code a little, which is very simple."

v4.0 -- the simulation of JDK dynamic agent is finally completed

Gyro explained: "the problem is that we need to transfer the generated proxy object to the invoke method. Obviously, we should do something in the newProxyInstance method. Make a change when automatically generating code and transfer this object to the invoke method."

@Override
public void pay(){
    try{
        Method m = designPattern.proxy.dynamicProxy.Payable.class.getMethod("pay",new Class[]{});
        this.h.invoke(this, m, new Object[]{});
    }catch(Throwable e){}
}

"In this case, the declaration of the invoke method needs to be changed to invoke(Object proxy, Method m, Object[] args)
, right? " Zhaocai added.

"Yes, so that when overriding the invoke method, the user can get the proxy object proxy and perform a series of operations on the proxy object. So far, we have completed the simulation of JDK dynamic proxy."

Postscript

Zhaocai asked curiously, "master, like us, JDK obtains the source code of the proxy object by splicing strings, and then compiles it?"

The gyro laughed, "If this is true, JDK is too low. JDK officially provides the specification of class bytecode. As long as you know this specification, you can directly write bytecode files according to this specification, so as to skip the process of generating. java and then dynamically compiling into. Class. JDK dynamic agent generates bytecode during runtime and directly writes class bytecode files, which is more efficient than Higher. "

"Master, from the beginning, you stipulated that you must use interfaces to use dynamic agents. Is it also related to the implementation of JDK? Isn't there a way to use interfaces to implement dynamic agents?" Zhaocai once again raised the question.

Gyro loves and hates his disciples. "You're really sharp. In addition to JDK dynamics, there's CGLib dynamic agent. The former is implemented through interface and the latter through inheritance, but don't want me to continue to tell you about CGLib. After talking about JDK dynamic agent, I'll lose half my life. Next time!"

PS: cicada Mufeng really doesn't want the top to answer. After all, he has written this article for nearly two weeks

It is strongly recommended to download the source code and run it yourself. For the source code of all versions of dynamic agent, see github


Topics: Dynamic Proxy