說在前面的話:本人在學習JDK動態代理時,以案例的方式學習了JDK動態代理底層實現,總結了我的的實現步驟,因爲本人水平有限,若有不當之處,望指出。java
JDK動態代理底層的底層相似於咱們手寫HelloWorld的過程,不過JDK是在運行期生成Java文件並編譯加載
動態生成代理類的.java -> 動態編譯爲.class文件 -> 將.class文件加載到JVM中 -> 使用該類建立對象編程
(想自學習編程的小夥伴請搜索圈T社區,更多行業相關資訊更有行業相關免費視頻教程。徹底免費哦!)app
首先描述案例需求,如今有Flyable接口,抽象方法爲fly(time)方法,Bird類實現Flyable接口。
現須要生成代理對象,代理該Bird對象中Flyable接口的方法,記錄鳥兒每次飛行的日期時分秒。jvm
#### 1.寫一個Flyable接口 public interface Flyable { void fly(long time); }
沒什麼 具體的東西,就時一個飛的方法,參數爲飛多久maven
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) { } } }
鳥實現會飛的接口ide
//執行方法 public interface InvocationHandler { //第一個參數,代理對象,第二個參數,所執行的方法反射對象,第三個參數及之後,傳入的參數 Object invoke(Object proxy, Method method, Object... args); }
此接口做用:咱們能夠定製代理對象的方法功能工具
動態代理執行的全部方法其實都是在裏面執行了這一個方法,因此此接口尤其重要學習
==重頭戲==測試
用來獲取代理對象的類ui
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 { //1、 生成java文件 generateProxyJavaFile(clazz, proxyClassName); //2、 編譯 compileJavaFile(); //3、 加載class文件到jvm中 ClassUtil.loadClass(new File(CLASS_BASE_PATH)); //4、 建立對象並返回 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毫秒