Said in the previous words: WHEN I learn JDK dynamic proxy, I learned the underlying implementation of JDK dynamic proxy in the way of cases, summed up the personal implementation steps, due to my limited level, if there is improper, please point out. Java -> dynamically compile as a.class file -> load the.class file into the JVM -> create objects using the class

I. Case requirements

The Flyable interface is now available. The abstract method is fly(time), and the Bird class realizes the Flyable interface. A proxy object needs to be generated that represents the methods of the Flyable interface in the Bird object and records the date and time of each Bird flight.

Second, preparation

1. Write a Flyable interface

public interface Flyable {

    void fly(long time);

}
Copy the code

Nothing concrete, just a method of flying, parameters for how long to fly

Write a Bird implementation interface

public class Bird implements Flyable {
    
    @Override
    public void fly(long time) {
        try {
            System.out.println("I'm a bird. I'm flying.");
            Thread.sleep(time);
            System.out.println("I'm a bird. I fly." + time + "毫秒");
        } catch (InterruptedException e) {
        }
    }
}
Copy the code

The bird implements a flying interface

Start implementing the proxy

1. Write an execution interface

// Execute method
public interface InvocationHandler {
	// The first argument, the proxy object, the second argument, the reflection object of the method executed, the third argument, and later, the arguments passed in
    Object invoke(Object proxy, Method method, Object... args);

}
Copy the code

What this interface does is: we can customize the method functionality of the proxy object

This interface is especially important because all methods executed by the dynamic proxy actually execute this one method inside

2. Write the class that gets the proxy object

== The highlight ==

The class used to get the proxy object

public class MyProxy {

    private static final String CLASS_BASE_PATH = "C:\ Users\ bai\ Desktop\ Java generated code";
    private static final String PACKAGE_NAME = "myproxy";

    // Get the proxy object
    public static <T> T newProxyInstance(Class<T> clazz, InvocationHandler invocationHandler) {
        String proxyClassName = clazz.getSimpleName() + "$MyProxy";
		
        try {
            // Create a Java file
            generateProxyJavaFile(clazz, proxyClassName);

            // Compile
            compileJavaFile();

            // Add the class file directory to the classpath
            ClassUtil.addClassPath(new File(CLASS_BASE_PATH));

            // load class and create object
            Class proxyClass = Class.forName(PACKAGE_NAME + "." + proxyClassName);
            return (T) proxyClass.getConstructor(InvocationHandler.class).newInstance(invocationHandler);
        } catch (Exception e) {
            e.printStackTrace();
            return null; }}// Generate a Java file for the proxy class
    private static void generateProxyJavaFile(Class clazz, String proxyClassName) throws IOException {
		/ /... Here is
    }
    
    // Compile Java files into.class files
    private static void compileJavaFile(a) throws FileNotFoundException{
        / /... Here is}}Copy the code

The four-step process is similar to writing a Java helloWord

1) Create a Java file

Here is a Java file generated using Java code, with the help of an open source toolkit, Javapoet, available in the Maven repository.

Similar to handwritten Java files, class headers, properties, constructors, methods, etc.

According to Javapoet rules to write, is not difficult, usually can be handwritten, with it can be achieved

There are some problems in the construction method, you can refer to the generated Java file to see the code

	// Generate a Java file for the proxy class
    private static void generateProxyJavaFile(Class clazz, String proxyClassName) throws IOException {

        // Construct a class that implements the incoming interface. AddSuperinterface adds an implementation interface
        TypeSpec.Builder classBuilder = TypeSpec.classBuilder(proxyClassName).addSuperinterface(clazz);

        // Build a property to hold the execution object
        FieldSpec fieldSpec = FieldSpec.builder(InvocationHandler.class, "handler", Modifier.PRIVATE).build();

        // Add to class
        classBuilder.addField(fieldSpec);

        // Build a constructor that initializes the execution object
        MethodSpec constructor = MethodSpec.constructorBuilder()
            	// Add permission modifiers
                .addModifiers(Modifier.PUBLIC)
            	// Add parameters
                .addParameter(InvocationHandler.class, "handler")
            	// Method body content
                .addStatement("this.handler = handler")
                .build();

        // Add the constructor to the class
        classBuilder.addMethod(constructor);
        
		// Get all public methods of the incoming interface (own, externally accessible methods, excluding inherited methods)
        Method[] methods = clazz.getDeclaredMethods();

        for (Method method : methods) {
            MethodSpec.Builder methodSpec = MethodSpec.methodBuilder(method.getName())
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(Override.class)
                    .returns(method.getReturnType());
            // Generate handler.invoke() to execute the statement (which actually executes the method) and add it to the method body
            StringBuilder invokeString = new StringBuilder("\tthis.handler.invoke(this, " + clazz.getName() + ".class.getMethod(\"" + method.getName() + "\",");
            // Store a list of execution method arguments
            StringBuilder paramNames = new StringBuilder();
            // If you don't understand this part, you can compare the generated Java file
            for (Parameter parameter : method.getParameters()) {
                // Add an external method argument list
                methodSpec.addParameter(parameter.getType(), parameter.getName());
                // Add to the actual execution method
                invokeString.append(parameter.getType() + ".class, ");
                // Save to the execution method parameter list
                paramNames.append(parameter.getName() + ",");
            }
            // Replace the last comma with)
            int lastCommaIndex = invokeString.lastIndexOf(",");
            invokeString.replace(lastCommaIndex, invokeString.length(), "),");
            lastCommaIndex = paramNames.lastIndexOf(",");
            paramNames.replace(lastCommaIndex, lastCommaIndex + 1.")");
            // Append the attribute name to the last parameter list
            invokeString.append(paramNames);
            // Add a method body, execute the invoke method of InvocationHandler, and grab an exception
            methodSpec.addCode("try{\n");
            methodSpec.addStatement(invokeString.toString());
            methodSpec.addCode("} catch (NoSuchMethodException e) {\n");
            methodSpec.addCode("\te.printStackTrace(); \n");
            methodSpec.addCode("}\n");

            // Add to class
            classBuilder.addMethod(methodSpec.build());
        }

        // Generate a Java file with the package name as the first argument
// String path = MyProxy.class.getResource("/").toString();
        JavaFile javaFile = JavaFile.builder(PACKAGE_NAME, classBuilder.build()).build();

        // Write the Java file to the execution path (default package folder)
        javaFile.writeTo(new File(CLASS_BASE_PATH));
    }
Copy the code

Generated Java file

package myproxy;

import java.lang.Override;

class Flyable$MyProxy implements Flyable {
  private InvocationHandler handler;

  public Flyable$MyProxy(InvocationHandler handler) {
    this.handler = handler;
  }

  @Override
  public void fly(long arg0) {
    try{
    	this.handler.invoke(this, myproxy.Flyable.class.getMethod("fly".long.class), arg0);
    } catch(NoSuchMethodException e) { e.printStackTrace(); }}}Copy the code

2) Compile as a.class file

Java provides a tool similar to Javac compilation. The process is not complicated, and you can flexibly set the parameters of the JVM at compile time, or if null, use the parameters of the current environment

// Compile Java files into.class files
private static void compileJavaFile(a) throws FileNotFoundException {
    1. Obtain the Javac compiler
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    //2. Get a compiled Java file manager from the Javac compiler
    StandardJavaFileManager manager = compiler.getStandardFileManager(null.null.null);

    //3. Get the Java file object
    //- Calls a utility class to recursively retrieve all files with the specified suffix from the specified path
    Set<File> javaFiles = FileUtil.getFilesForSuffix(new File(CLASS_BASE_PATH), ".java");
    //- This is a Java file
    Iterator<File> iterator = javaFiles.iterator();
    Iterable<? extends JavaFileObject> it = manager.getJavaFileObjects(iterator.next().getAbsoluteFile());

    / / 4. Compilation
    JavaCompiler.CompilationTask task = compiler.getTask(null, manager,
            null.null.null, it);
    task.call();
}
Copy the code

3) Load the. Class file

Load the class file into the JVM

public class ClassUtil {

    // Add a folder to the classpath
   public static <T> void addClassPath(File classFolder) throws Exception {
        // Get the addURL method of the URLClassLoader
        Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
        boolean accessible = method.isAccessible();
        try {
            // If the method does not have permission to access, set the permission to access
            if (accessible == false) {
                method.setAccessible(true);
            }
            // Set the class loader
            URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
            // Add the current classpath to the classloader
            method.invoke(classLoader, classFolder.toURI().toURL());
        } finally {
            // Set the method's permissions backmethod.setAccessible(accessible); }}}Copy the code

Test the proxy object

After a series of efforts, finally completed the preparation of dynamic proxy, into the test stage

1. Write the Invoke method body

public class MyInvocationHandler implements InvocationHandler {

    private Bird bird;

    public MyInvocationHandler(Bird bird) {
        this.bird = bird;
    }

    @Override   // The first argument is the proxy object, the second argument is the method object, and the following arguments are the method parameters
    public Object invoke(Object proxy, Method method, Object... args) {
        String dateString = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis());
        System.out.println(dateString + "Bird Takes off");
        try {
            Object invoke = method.invoke(bird, args);
            return invoke;
        } catch (Exception e) {
            e.printStackTrace();
            return null; }}}Copy the code

2. Test the proxy object

public class MyProxyTest {

    @Test
    public void testProxy(a) {
        Flyable flyable = MyProxy.newProxyInstance(Flyable.class, new MyInvocationHandler(new Bird()));
        flyable.fly(1000); }}Copy the code

Console printing

I am a bird, I began to fly I am a bird, I flew 1000 millisecondsCopy the code