深圳金融投資網(wǎng)站建設(shè)bt最佳磁力搜索引擎
手寫實現(xiàn)一個動態(tài)代理框架
- 什么是代理模式
- 什么是動態(tài)代理
- 動態(tài)代理中的編譯、類加載與對象實例化
- 手寫實現(xiàn)一個動態(tài)代理框架
- 實現(xiàn)細節(jié)
- DynamicProxyHandler
- Proxy
- 生成代碼
- 寫入代碼到磁盤文件
- 調(diào)用編譯器進行編譯
- 調(diào)用類加載器進行類加載
- 反射實例化
- 刪除前面生成的java文件和class文件
- Coder
- 來玩一把
- JDK動態(tài)代理和我們的區(qū)別
最近寫了一個動態(tài)代理框架,在這里分享一下,順便分析一下動態(tài)代理的原理,當(dāng)做復(fù)習(xí)。
什么是代理模式
首先我們來了解一下代理模式,代理模式是二十三種設(shè)計模式中的其中一種,用于對目標對象進行代理增強。
代理類通常和目標類實現(xiàn)同一個接口,這樣我們就可以用一個接口類型變量去引用一個代理類對象,然后又可以當(dāng)成目標對象去使用。
public interface Handler {void handle();
}
public class Proxy implements Handler {private Handler target;public Proxy(Handler target) {this.target = target;}...
}
public class Target implements Handler {...
}
public class Main {public static void main(String[] args) {Target target = new Target();Handler handler = new Proxy(target);...}
}
當(dāng)我們對目標對象做了代理之后,就可以在調(diào)用目標對象方法的前后添加一些增強的處理邏輯。
public class Proxy implements Handler {private Handler target;public Proxy(Handler target) {this.target = target;}@Overridepublic void handle() {pre(); // 前置增強邏輯target.handle();post(); // 后置增強邏輯}
}public class Target implements Handler {@Overridepublic void handle() {...}
}
比如我們要在目標方法的前后添加日志打印,那么我們可以通過代理模式實現(xiàn)。
public class Proxy implements Handler {private Handler target;@Overridepublic void handle() {log.info(...);target.handle();log info(...);}}
這樣做的好處有兩點:
- 我們可以為目標類添加額外的處理邏輯,而不需要改動目標類原有的代碼。
- 我們可以更換目標對象,但是代理類中的增強處理邏輯不用變,更換后的目標對象依然可以復(fù)用原先的增強邏輯。
什么是動態(tài)代理
動態(tài)代理是對普通代理模式的優(yōu)化,普通代理模式有一個缺點,那就是我們需要手動編寫代理邏輯。
比如我們的代理邏輯就是在目標方法的前后添加日志打印。如果只有幾個接口需要代理,那么我們可以手動編寫這幾個接口的代理實現(xiàn)類,那是沒有問題的。
public interface HandlerA {void method1();void method2();void method3();}
public interface HandlerB {void method1();void method2();
}
public interface HandlerC {void method1();void method2();void method3();void method4();
}public class ProxyA implements HandlerA {private HandlerA target;@Overridepublic void method1() {log.info(...);target.method1();log info(...);}@Overridepublic void method2() {log.info(...);target.method2();log info(...);}@Overridepublic void method3() {log.info(...);target.method3();log info(...);}}public class ProxyB implements HandlerB {private HandlerB target;@Overridepublic void method1() {log.info(...);target.method1();log info(...);}@Overridepublic void method2() {log.info(...);target.method2();log info(...);}}
public class ProxyC implements HandlerC {...
}
但是我們發(fā)現(xiàn),雖然我們編寫的代理類實現(xiàn)了不同的接口,重寫了不同的接口方法,但是前后添加的增強邏輯其實是一模一樣的。而且如果接口比較多的話,我們一個個手寫代理實現(xiàn)類也挺麻煩的。
于是就有了動態(tài)代理,動態(tài)代理的主要作用就是動態(tài)生成代理實現(xiàn)類,不需要我們手動編寫。我們只需要定義好增強邏輯,以及需要代理的接口和接口方法,我們就可以通過動態(tài)代理框架生成代理類并實例化代理對象。
比如JDK的動態(tài)代理,我們定義好我們的接口比如Handler,然后實現(xiàn)JDK動態(tài)代理提供的接口InvocationHandler在里面編寫增強邏輯,就可以作為輸入?yún)?shù)調(diào)用JDK動態(tài)代理提供的工具類Proxy生成代理實現(xiàn)類并反射實例化代理對象。
這樣就不需要我們自己編寫那么多的代理實現(xiàn)類了,省掉了一大波繁瑣的工作,一片歲月靜好。
動態(tài)代理中的編譯、類加載與對象實例化
我們自己手動編寫代理類時,從代理類代碼的編寫到代理對象的創(chuàng)建,整個流程是這樣子:
代碼編寫和編譯器編譯是靜態(tài)的,因為是在JVM啟動之前提前做好的。
動態(tài)代理相當(dāng)于是就是在JVM運行的時候,由動態(tài)代理框架根據(jù)我們給定的接口和代理邏輯動態(tài)編寫代理實現(xiàn)類,然后調(diào)用編譯器進行動態(tài)編譯,通過類加載器加載到內(nèi)存中,然后反射實例化。
在動態(tài)代理的情況下,代碼是在JVM運行時動態(tài)生成的,比如用StringBuilder拼接(也可以是別的方式生成),然后通過調(diào)用JDK提供的API進行運行時編譯,類加載器加載也是通過調(diào)用JDK提供的API進行動態(tài)的加載。也就是說從代碼生成、編譯、類加載、到反射實例化的這一切,都是在JVM已經(jīng)運行的情況下,通過Java代碼動態(tài)實現(xiàn)的,因此稱為動態(tài)代理。
手寫實現(xiàn)一個動態(tài)代理框架
那么,我們就來實現(xiàn)一個我們自己的動態(tài)代理框架,非常簡單,三個類:Coder、DynamicProxyHandler、Proxy
- Coder:代理類代碼的生成器類,接收一個Class<?>類型的參數(shù)interfaceClass,表示代理類要實現(xiàn)interfaceClass這個接口,Coder根據(jù)這個接口生成代理類的代碼
- DynamicProxyHandler:留給用戶實現(xiàn)的處理器接口,用戶需要實現(xiàn)DynamicProxyHandler接口并重新invoke以聲明動態(tài)代理的增強邏輯
- Proxy:生成代理對象的工具類,調(diào)用Coder生成代理類的代碼, 通過編譯器動態(tài)編譯,然后通過類加載器動態(tài)加載編譯出來的代理類class,最后通過反射創(chuàng)建代理對象返回給用戶
實現(xiàn)細節(jié)
DynamicProxyHandler
DynamicProxyHandler就是預(yù)留給用戶實現(xiàn)的用于編寫增強邏輯的接口,用戶需要實現(xiàn)DynamicProxyHandler接口,并重寫invoke方法,在invoke方法中編寫增加邏輯。
/*** @author huangjunyi* @date 2023/11/28 19:00* @desc*/
public interface DynamicProxyHandler {Object invoke(Object proxy, Method method, Object[] args);}
invoke方法接收三個參數(shù),proxy是代理對象本身,method是當(dāng)前正在調(diào)用的方法的Method對象,args是當(dāng)前方法接收的參數(shù)。
Proxy
Proxy中的代碼稍稍有點復(fù)雜,不用馬上全部看完,先有個大概印象,下面一步步分析里面干了些啥。
/*** @author huangjunyi* @date 2023/11/28 19:00* @desc*/
public class Proxy {private static boolean enablePrintCode = false;public static void printCode() {enablePrintCode = true;}public static <T> T newInstance(ClassLoader classLoader, Class<?> interfaceClass, DynamicProxyHandler handler) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {String code = Coder.generateCode(interfaceClass);if (enablePrintCode) {System.out.println(code);}String path = classLoader.getResource("").getPath() + interfaceClass.getPackage().toString().substring("package ".length()).replaceAll("\\.", "/") + "/" + interfaceClass.getSimpleName() + "$$.java";path = path.replace('\\', '/');File file = new File(path);if (file.getParentFile().exists()) {file.getParentFile().mkdir();}FileWriter fileWriter = new FileWriter(file);fileWriter.write(code);fileWriter.close();JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();int result = compiler.run(null, null, null, path);String parentPath = path.substring(0, path.lastIndexOf("/"));URL[] urls = new URL[] {new URL("file:/" + parentPath + "/")};URLClassLoader loader = new URLClassLoader(urls);Class c = loader.loadClass(interfaceClass.getPackage().toString().substring("package ".length()) + "." + interfaceClass.getSimpleName() + "$$");Constructor constructor = c.getConstructor(DynamicProxyHandler.class);Object o = constructor.newInstance(handler);new File(path.substring(0, path.lastIndexOf(".") + 1) + "class").delete();file.delete();return (T) o;}}
生成代碼
首先調(diào)用Coder生成代理類的代碼,接收一個接口類型參數(shù)interfaceClass,返回生成的代碼字符串。
String code = Coder.generateCode(interfaceClass);
Proxy里面有個boolean類型的enablePrintCode屬性,把它設(shè)置為true,會打印動態(tài)生成的代理類的代碼。
if (enablePrintCode) {System.out.println(code);}
寫入代碼到磁盤文件
然后把生成的代碼寫入到磁盤文件
String path = classLoader.getResource("").getPath() + interfaceClass.getPackage().toString().substring("package ".length()).replaceAll("\\.", "/") + "/" + interfaceClass.getSimpleName() + "$$.java";path = path.replace('\\', '/');File file = new File(path);if (file.getParentFile().exists()) {file.getParentFile().mkdir();}FileWriter fileWriter = new FileWriter(file);fileWriter.write(code);fileWriter.close();
磁盤文件的路徑必須在類加載器加載類的目錄下,通過 classLoader.getResource(“”).getPath() 取得類加載器加載類的對應(yīng)目錄。
然后把類的包名中的“.”替換成“/”,創(chuàng)建目錄。
最后創(chuàng)建文件,通過FileWriter把代碼寫入到文件中。
調(diào)用編譯器進行編譯
代碼寫到文件后,就要調(diào)用編譯器把這個java文件編譯成class文件。
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();int result = compiler.run(null, null, null, path);
JavaCompiler就是編譯器對象,調(diào)用JavaCompiler的run方法進行編譯,path參數(shù)是需要編譯的java文件的路徑。
調(diào)用類加載器進行類加載
編譯出class文件后,就要調(diào)用類加載器把這個class文件加載到內(nèi)存中,生成一個Class對象。
String parentPath = path.substring(0, path.lastIndexOf("/"));URL[] urls = new URL[] {new URL("file:/" + parentPath + "/")};URLClassLoader loader = new URLClassLoader(urls);Class c = loader.loadClass(interfaceClass.getPackage().toString().substring("package ".length()) + "." + interfaceClass.getSimpleName() + "$$");
String parentPath = path.substring(0, path.lastIndexOf(“/”)); 這一行是取得java文件所在目錄的路徑,編譯出來的class文件與java文件是同處一個目錄下的,這樣我們就可以找到class文件。
然后就是創(chuàng)建URLClassLoader對象,調(diào)用URLClassLoader的loadClass方法進行類加載,獲得代理類的Class對象。
反射實例化
獲得代理類的Class對象后,就要反射進行實例化。
Constructor constructor = c.getConstructor(DynamicProxyHandler.class);Object o = constructor.newInstance(handler);
獲得構(gòu)造器對象constructor,調(diào)用構(gòu)造器的newInstance方法進行實例化。
刪除前面生成的java文件和class文件
最后要把生成的文件刪除掉,以免留下垃圾。
new File(path.substring(0, path.lastIndexOf(".") + 1) + "class").delete();file.delete();
第一行是刪除class文件,第二行是刪除java文件。
Coder
接下來看一下Coder類。這個Coder類不必細看,代碼雖然多,但是都是使用StringBuilder進行字符串拼接,根據(jù)給定的接口拼出一個代理類,沒什么技術(shù)含量。
/*** @author huangjunyi* @date 2023/11/28 19:00* @desc*/
public class Coder {private static Map<String, String> codeCache = new HashMap<>();public static String generateCode(Class<?> interfaceClass) {String interfaceClassName = interfaceClass.getName();if (codeCache.containsKey(interfaceClassName)) {return codeCache.get(interfaceClassName);}StringBuilder codeBuilder = new StringBuilder();// packagePackage aPackage = interfaceClass.getPackage();String packageName = aPackage.getName();codeBuilder.append("package ").append(packageName).append(";").append("\n");codeBuilder.append("\n");// importMethod[] methods = interfaceClass.getDeclaredMethods();Set<String> importTypeSet = new HashSet<>();for (Method method : methods) {Class<?> returnType = method.getReturnType();importTypeSet.add(returnType.getName());Class<?>[] parameterTypes = method.getParameterTypes();for (Class<?> parameterType : parameterTypes) {importTypeSet.add(parameterType.getName());}}for (String importType : importTypeSet) {if ("void".equals(importType) ||"long".equals(importType) ||"int".equals(importType) ||"short".equals(importType) ||"byte".equals(importType) ||"char".equals(importType) ||"float".equals(importType) ||"double".equals(importType) ||"boolean".equals(importType)) {continue;}codeBuilder.append("import ").append(importType).append(";").append("\n");}codeBuilder.append("import java.lang.reflect.Method;");codeBuilder.append("import com.huangjunyi1993.simple.dynamic.proxy.DynamicProxyHandler;");// classcodeBuilder.append("\n\npublic class ").append(interfaceClass.getSimpleName()).append("$$").append(" implements").append(" ").append(interfaceClass.getSimpleName()).append(" {").append("\n\n");// fieldcodeBuilder.append("\tprivate DynamicProxyHandler dynamicProxyHandler;\n\n");// ConstructorcodeBuilder.append("\tpublic ") .append(interfaceClass.getSimpleName()).append("$$").append("(").append("DynamicProxyHandler dynamicProxyHandler) {\n").append("\t\tthis.dynamicProxyHandler = dynamicProxyHandler;").append("\n").append("\t}\n\n");// methodfor (Method method : methods) {Class<?> returnType = method.getReturnType();importTypeSet.add(returnType.getName());codeBuilder.append("\t@Override\n");codeBuilder.append("\t").append("public ").append(returnType.getSimpleName()).append(" ").append(method.getName()).append("(");// method parameterClass<?>[] parameterTypes = method.getParameterTypes();for (int i = 0; i < parameterTypes.length; i++) {Class<?> parameterType = parameterTypes[i];codeBuilder.append(parameterType.getSimpleName()).append(" ").append("var").append(i);if (i < parameterTypes.length - 1) {codeBuilder.append(", ");}}codeBuilder.append(") ").append(" {").append("\n");// method bodycodeBuilder.append("\n\t\tMethod method = null;");codeBuilder.append("\n\t\ttry {");codeBuilder.append("\n\t\t\tmethod = Class.forName(").append("\"").append(interfaceClass.getName()).append("\"").append(")").append(".getDeclaredMethod(").append("\"").append(method.getName()).append("\"");if (parameterTypes.length != 0) {codeBuilder.append(", ");for (int i = 0; i < parameterTypes.length; i++) {Class<?> parameterType = parameterTypes[i];codeBuilder.append(parameterType.getSimpleName()).append(".").append("class");if (i < parameterTypes.length - 1) {codeBuilder.append(", ");}}}codeBuilder.append(");");codeBuilder.append("\n\t\t\tmethod.setAccessible(true);");codeBuilder.append("\n\t\t} catch (ClassNotFoundException|NoSuchMethodException e) {");codeBuilder.append("\n\t\t\tthrow new RuntimeException(\"error when generate code\");");codeBuilder.append("\n\t\t}");if (!"void".equals(returnType.getSimpleName())) {codeBuilder.append("\n\t\treturn (").append(returnType.getSimpleName()).append(") ").append("dynamicProxyHandler.invoke(this, method, new Object[]{");} else {codeBuilder.append("dynamicProxyHandler.invoke(this, method, new Object[]{");}for (int i = 0; i < parameterTypes.length; i++) {codeBuilder.append("var").append(i);if (i < parameterTypes.length - 1) {codeBuilder.append(", ");}}codeBuilder.append("});");codeBuilder.append("\n").append("\t").append("}");codeBuilder.append("\n\n");}codeBuilder.append("}");String code = codeBuilder.toString();codeCache.put(interfaceClassName, code);return code;}}
Coder 有一個Map<String, String>類型的緩存codeCache,用于緩存曾經(jīng)生成過的代碼,如果下一次再調(diào)用Coder的generateCode,傳入的參數(shù)interfaceClass是同一個接口類型,那么直接取緩存中的結(jié)果返回,不再重復(fù)生成。
String interfaceClassName = interfaceClass.getName();if (codeCache.containsKey(interfaceClassName)) {return codeCache.get(interfaceClassName);}
如果緩存中沒有,就要通過StringBuilder動態(tài)拼接代理類的代碼了。
StringBuilder codeBuilder = new StringBuilder();...
這個拼接的邏輯就不用細看了,只是繁瑣的工作,只要知道怎么通過Class對象獲取到類信息和方法信息,就可以根據(jù)這些信息拼出一個代理實現(xiàn)類。
- Package aPackage = interfaceClass.getPackage(); 獲取包路徑。
- Method[] methods = interfaceClass.getDeclaredMethods(); 獲取接口定義的方法。
- Class<?> returnType = method.getReturnType(); 獲取方法返回值類型。
- Class<?>[] parameterTypes = method.getParameterTypes(); 獲取方法參數(shù)類型。
我們看看生成的代理類是怎么樣的。比如我們有一個Hello接口:
/*** @author huangjunyi* @date 2023/11/28 19:58* @desc*/
public interface Hello {String sayHi(int count);}
生成的代理類就是這樣:
package com.huangjunyi1993.simple.dynamic.proxy.test;import java.lang.String;
import java.lang.reflect.Method;import com.huangjunyi1993.simple.dynamic.proxy.DynamicProxyHandler;public class Hello$$ implements Hello {private DynamicProxyHandler dynamicProxyHandler;public Hello$$(DynamicProxyHandler dynamicProxyHandler) {this.dynamicProxyHandler = dynamicProxyHandler;}@Overridepublic String sayHi(int var0) {Method method = null;try {method = Class.forName("com.huangjunyi1993.simple.dynamic.proxy.test.Hello").getDeclaredMethod("sayHi", int.class);method.setAccessible(true);} catch (ClassNotFoundException|NoSuchMethodException e) {throw new RuntimeException("error when generate code");}return (String) dynamicProxyHandler.invoke(this, method, new Object[]{var0});}}
生成的代理類的類名是接口名后加兩個“$”,比如這里的接口是Hello,那么生成的代理類就是Hello$$。
public class Hello$$ implements Hello
生成的代理類重寫了給定接口定義的所有方法,方法里面的邏輯都是反射獲取當(dāng)前方法的Method對象,然后調(diào)用DynamicProxyHandler的invoke方法,把當(dāng)前代理對象this、當(dāng)前方法的Method對象method、當(dāng)前方法參數(shù)作為invoke方法的入?yún)ⅰ?/p>
來玩一把
定義一個Hello接口,就是前面的Hello接口:
/*** @author huangjunyi* @date 2023/11/28 19:58* @desc*/
public interface Hello {String sayHi(int count);}
Hello接口實現(xiàn)類HelloImpl,目標類(被代理類):
/*** @author huangjunyi* @date 2023/11/28 20:00* @desc*/
public class HelloImpl implements Hello {@Overridepublic String sayHi(int count) {String result = "";for (int i = 0; i < count; i++) {result += "hi ";}return result;}
}
實現(xiàn)DynamicProxyHandler接口,編寫增強邏輯:
/*** @author huangjunyi* @date 2023/11/28 20:02* @desc*/
public class HelloHandler implements DynamicProxyHandler {private Hello target;public HelloHandler(Hello target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) {System.out.println("HelloHandler invoke");try {Object invoke = method.invoke(target, args);System.out.println(invoke);return invoke;} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}return null;}
}
測試類:
/*** @author huangjunyi* @date 2023/11/24 10:05* @desc*/
public class A {@Testpublic void test() throws Exception {HelloImpl hello = new HelloImpl();HelloHandler helloHandler = new HelloHandler(hello);// 調(diào)用這個方法,可以打印生成的代理類的代碼// Proxy.printCode();Hello o = Proxy.newInstance(hello.getClass().getClassLoader(), Hello.class, helloHandler);o.sayHi(5);}}
運行測試類,控制臺輸出:
HelloHandler invoke
hi hi hi hi hi
JDK動態(tài)代理和我們的區(qū)別
JDK動態(tài)代理大體流程和我們的思路是相同的,我們看看JDK的Proxy類的newProxyInstance方法:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{final Class<?>[] intfs = interfaces.clone();// 生成代理類的Class對象Class<?> cl = getProxyClass0(loader, intfs);try {// 獲取構(gòu)造器對象final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;cons.setAccessible(true);// 構(gòu)造器反射實例化return cons.newInstance(new Object[]{h});} catch (...) {...}}
里面我省略了許多雜七雜八的代碼,只留下核心流程,可以看到也是生成一個代理類的Class對象,然后反射調(diào)用它的構(gòu)造器生成代理對象。
getProxyClass0(loader, intfs)最終會調(diào)到ProxyClassFactory的apply方法:
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {// 。。。。。。// 生成的不是字符串,而是二進制byte數(shù)組,class字節(jié)碼文件里面的內(nèi)容byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);try {// 調(diào)用native方法直接進行類的初始化,生成Class對象// 沒有寫盤、編譯、類加載的這幾步動作return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);} catch (ClassFormatError e) {...}}
這里就是和我們區(qū)別最大的地方,JDK動態(tài)代理不是通過字符串拼接生成的代理類代碼,而是生成的byte[]類型,里面的內(nèi)容也不是java文件里面的代碼,而是class字節(jié)碼文件里面的內(nèi)容。
然后直接調(diào)用defineClass0方法(defineClass0方法是native方法)進行類的初始化,省略了寫入磁盤(我們也可以設(shè)置要保存生成的class文件,這樣會把class文件寫到磁盤)、編譯、調(diào)用類加載器進行類加載的這幾步。
全文完。