JDK dynamic proxy underlying implementation case

Said in front of the words: I am in the learning JDK dynamic proxy to the case of the JDK dynamic proxy way to learn the underlying implementation, summarizes the individual implementation steps, since I am limited, if inappropriate, Wang said.

The underlying dynamic proxy underlying JDK similar to our handwriting HelloWorld process, but JDK generated Java files loaded at runtime and compile
.java dynamically generated proxy classes -> compiled into .class files dynamic -> .class file will be loaded into the JVM in
-> use this class to create objects

First, the case needs

First described cases demand, there are now Flyable interfaces, abstract method fly (time) method, Bird Flyable class implements the interface. Now we need to generate a proxy object, the object method Flyable Bird interface agent, minutes and seconds when the recorded date of each flight of birds.

Second, prepare for work

1. Write a Flyable Interface

public interface Flyable {

    void fly(long time);

}
复制代码

Nothing specific things, when a method to fly, fly long parameters

2. Write a Bird implements the interface

public class Bird implements Flyable {
    
    @Override
    public void fly(long time) {
        try {
            System.out.println("我是小鸟,我开始飞了");
            Thread.sleep(time);
            System.out.println("我是小鸟,我飞了" + time + "毫秒");
        } catch (InterruptedException e) {
        }
    }
}
复制代码

Birds fly implement interfaces

Third, start implementing agency

1. Write an Executive Interface

//执行方法
public interface InvocationHandler {
	//第一个参数,代理对象,第二个参数,所执行的方法反射对象,第三个参数及以后,传入的参数
    Object invoke(Object proxy, Method method, Object... args);

}
复制代码

This interface role: we can customize the proxy object method function

All methods are dynamic agents of execution is performed inside this method, so this interface is particularly important

2. Write obtain the proxy object class

== == highlight

Used to obtain the proxy object class

public class MyProxy {

    private static final String CLASS_BASE_PATH = "C:\\Users\\bai\\Desktop\\Java生成代码";
    private static final String PACKAGE_NAME = "myproxy";

    //获取代理对象
    public static <T> T newProxyInstance(Class<T> clazz, InvocationHandler invocationHandler) {
        String proxyClassName = clazz.getSimpleName() + "$MyProxy";
		
        try {
            //一、 生成java文件
            generateProxyJavaFile(clazz, proxyClassName);

            //二、 编译
            compileJavaFile();

            //三、 加载class文件到jvm中
            ClassUtil.loadClass(new File(CLASS_BASE_PATH));

            //四、 创建对象并返回
            Class proxyClass = MyProxy.class.getClassLoader().loadClass(PACKAGE_NAME + "." + proxyClassName);
            return (T) proxyClass.getConstructor(InvocationHandler.class).newInstance(invocationHandler);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    //生成代理类的java文件
    private static void generateProxyJavaFile(Class clazz, String proxyClassName) throws IOException {
		//...下面讲
    }
    
    //把java文件编译成.class文件
    private static void compileJavaFile() throws FileNotFoundException{
        //...下面讲
    }

}
复制代码

Four steps, and write a java helloWord similar

1) Create a Java file

Here is the Java code generated Java files, must rely on an open source toolkit, Javapoet, maven repository there.

Java file and the handwriting is similar to the first class, attributes, constructors, methods, and the like.

Written by Javapoet rule, is not difficult, it can usually handwritten, it can be achieved with

The method of construction where some trouble, java file may be generated with reference to look at the code

	//生成代理类的java文件
    private static void generateProxyJavaFile(Class clazz, String proxyClassName) throws IOException {

        //构造一个类,实现传入接口,addSuperinterface功能是添加一个实现接口
        TypeSpec.Builder classBuilder = TypeSpec.classBuilder(proxyClassName).addSuperinterface(clazz);

        //构建一个属性,用于保存执行对象
        FieldSpec fieldSpec = FieldSpec.builder(InvocationHandler.class, "handler", Modifier.PRIVATE).build();

        //添加到类中
        classBuilder.addField(fieldSpec);

        //构建一个构造器,初始化执行对象
        MethodSpec constructor = MethodSpec.constructorBuilder()
            	//添加权限修饰符
                .addModifiers(Modifier.PUBLIC)
            	//添加参数
                .addParameter(InvocationHandler.class, "handler")
            	//方法体内容
                .addStatement("this.handler = handler")
                .build();

        //把构造器添加到类中
        classBuilder.addMethod(constructor);
        
		//获取传入接口的所有公有方法(自己的,外部可以访问的方法,不包括继承的方法)
        Method[] methods = clazz.getDeclaredMethods();

        for (Method method : methods) {
            MethodSpec.Builder methodSpec = MethodSpec.methodBuilder(method.getName())
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(Override.class)
                    .returns(method.getReturnType());
            //生成handler.invoke()执行语句(实际执行方法),添加到方法体
            StringBuilder invokeString = new StringBuilder("\tthis.handler.invoke(this, " + clazz.getName() + ".class.getMethod(\"" + method.getName() + "\",");
            //存储执行方法参数列表
            StringBuilder paramNames = new StringBuilder();
            //这部分如果看不太懂,可以对照生成后的java文件
            for (Parameter parameter : method.getParameters()) {
                //添加外部方法参数列表
                methodSpec.addParameter(parameter.getType(), parameter.getName());
                //添加实际执行方法中
                invokeString.append(parameter.getType() + ".class, ");
                //存到执行方法参数列表中
                paramNames.append(parameter.getName() + ",");
            }
            //把最后一个逗号替换为)
            int lastCommaIndex = invokeString.lastIndexOf(",");
            invokeString.replace(lastCommaIndex, invokeString.length(), "), ");
            lastCommaIndex = paramNames.lastIndexOf(",");
            paramNames.replace(lastCommaIndex, lastCommaIndex + 1, ")");
            //把属性名追加到最后一个参数列表
            invokeString.append(paramNames);
            //添加方法体,执行InvocationHandler的invoke方法,并抓取异常
            methodSpec.addCode("try{\n");
            methodSpec.addStatement(invokeString.toString());
            methodSpec.addCode("} catch (NoSuchMethodException e) {\n");
            methodSpec.addCode("\te.printStackTrace();\n");
            methodSpec.addCode("}\n");

            //添加到类中
            classBuilder.addMethod(methodSpec.build());
        }

        //生成java文件,第一个参数是包名
//        String path = MyProxy.class.getResource("/").toString();
        JavaFile javaFile = JavaFile.builder(PACKAGE_NAME, classBuilder.build()).build();

        //把java文件写到执行路径下(默认会把包生成文件夹)
        javaFile.writeTo(new File(CLASS_BASE_PATH));
    }
复制代码

After generating Java files

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();
    }
  }
}

复制代码

2) compiled into .class files

Java has provided similar Javac compilation tools, process is not complicated, can be flexibly set of parameters jvm compile, or null to use the current environment parameters

//把java文件编译成.class文件
private static void compileJavaFile() throws FileNotFoundException {
    //1.获取javac编译器
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    //2.通过javac编译器获取一个编译java文件管理器
    StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);

    //3.获取java文件对象   
    //-调用一个工具类,从指定路径下,递归获取所有指定后缀的文件
    Set<File> javaFiles = FileUtil.getFilesForSuffix(new File(CLASS_BASE_PATH), ".java");
    //-这里就一个java文件,就直接使用了
    Iterator<File> iterator = javaFiles.iterator();
    Iterable<? extends JavaFileObject> it = manager.getJavaFileObjects(iterator.next().getAbsoluteFile());

    //4.编译
    JavaCompiler.CompilationTask task = compiler.getTask(null, manager,
            null, null, null, it);
    task.call();
}
复制代码

3) Load .class file

Load class files into the jvm

public class ClassUtil {

    //加载class文件
    public static <T> void loadClass(File classFolder) throws Exception {
		//使用url类加载器,把文件夹路径添加进去,就可以直接加载文件夹下的所有class文件
        //相当于把这个路径设置为源码路径之一
        //获取URLClassLoader的addURL方法
        Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
        boolean accessible = method.isAccessible();
        try {
            //如果方法没有权限访问,则设置可访问权限
            if (accessible == false) {
                method.setAccessible(true);
            }
            // 设置类加载器
            URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
            // 将当前类路径加入到类加载器中
            method.invoke(classLoader, classFolder.toURI().toURL());
        } finally {
            //把方法的权限设置回去
            method.setAccessible(accessible);
        }
		//获取文件夹中所有的.class文件
        Set<File> files = FileUtil.getFilesForSuffix(classFolder, ".class");
        for (File file : files) {
            // 把文件名称转化为,和java.lang.String类似的全类名
            String className = file.getAbsolutePath();
            className = className.substring(classFolder.getAbsolutePath().length() + 1, className.length() - 6);
            className = className.replace(File.separatorChar, '.');
            // 加载Class类
            Class.forName(className);
        }

    }
}
复制代码

III. Testing proxy object

After a series of efforts, finally completed the preparation of dynamic proxy, enter the test phase

1. Invoke method to prepare the body

public class MyInvocationHandler implements InvocationHandler {

    private Bird bird;

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

    @Override   //第一个参数为代理对象,第二个参数为方法对象,后面的参数为方法参数
    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 + "小鸟起飞");
        try {
            Object invoke = method.invoke(bird, args);
            return invoke;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

复制代码

2. Test proxy object

public class MyProxyTest {

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

}
复制代码

Console Print

2019-07-25 20:44:59
我是小鸟,我开始飞了
我是小鸟,我飞了1000毫秒
复制代码

Guess you like

Origin juejin.im/post/5d397ddde51d4577583ddda8