说在前面的话:本人在学习JDK动态代理时,以案例的方式学习了JDK动态代理底层实现,总结了个人的实现步骤,由于本人水平有限,如有不当之处,望指出。
JDK动态代理底层的底层类似于我们手写HelloWorld的过程,不过JDK是在运行期生成Java文件并编译加载
动态生成代理类的.java -> 动态编译为.class文件 -> 将.class文件加载到JVM中
-> 使用该类创建对象
首先描述案例需求,现在有Flyable接口,抽象方法为fly(time)方法,Bird类实现Flyable接口。 现需要生成代理对象,代理该Bird对象中Flyable接口的方法,记录鸟儿每次飞行的日期时分秒。
public interface Flyable { void fly(long time); } 复制代码
没什么 具体的东西,就时一个飞的方法,参数为飞多久
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) { } } } 复制代码
鸟实现会飞的接口
//执行方法 public interface InvocationHandler { //第一个参数,代理对象,第二个参数,所执行的方法反射对象,第三个参数及以后,传入的参数 Object invoke(Object proxy, Method method, Object... args); } 复制代码
此接口作用:我们可以定制代理对象的方法功能
动态代理执行的所有方法其实都是在里面执行了这一个方法,所以此接口尤为重要
==重头戏==
用来获取代理对象的类
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{ //...下面讲 } } 复制代码
分四步走,和写一个java的helloWord类似
这里是使用Java代码生成Java文件,要借助一个开源工具包,Javapoet,maven仓库中有。
和手写java文件类似,类头,属性,构造器,方法等。
按Javapoet规则写,并不难,平时可以手写的,用它都可以实现
构造方法的地方有些麻烦,可以参照生成后的java文件看代码
//生成代理类的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)); } 复制代码
生成后的Java文件
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(); } } } 复制代码
Java提供的有类似Javac编译功能的工具,过程并不复杂,可以灵活设置编译期jvm的参数,如果为null则用当前环境的参数
//把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(); } 复制代码
加载class文件到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); } } } 复制代码
经过一系列努力,终于完成了动态代理的编写,进入到测试阶段
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; } } } 复制代码
public class MyProxyTest { @Test public void testProxy() { Flyable flyable = MyProxy.newProxyInstance(Flyable.class, new MyInvocationHandler(new Bird())); flyable.fly(1000); } } 复制代码
控制台打印
2019-07-25 20:44:59 我是小鸟,我开始飞了 我是小鸟,我飞了1000毫秒 复制代码