您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關(guān)如何手動(dòng)模擬一個(gè)JDK動(dòng)態(tài)代理,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
為哪些方法代理?
實(shí)現(xiàn)自己動(dòng)態(tài)代理,首先需要關(guān)注的點(diǎn)就是,代理對(duì)象需要為哪些方法代理? 原生JDK的動(dòng)態(tài)代理的實(shí)現(xiàn)是往上抽象出一層接口,讓目標(biāo)對(duì)象和代理對(duì)象都實(shí)現(xiàn)這個(gè)接口,怎么把接口的信息告訴jdk原生的動(dòng)態(tài)代理呢? 如下代碼所示,Proxy.newProxyInstance()方法的第二個(gè)參數(shù)將接口的信息傳遞了進(jìn)去第一個(gè)參數(shù)的傳遞進(jìn)去一個(gè)類加載器,在jdk的底層用它對(duì)比對(duì)象是否是同一個(gè),標(biāo)準(zhǔn)就是相同對(duì)象的類加載器是同一個(gè)
ServiceInterface) Proxy.newProxyInstance(service.getClass().getClassLoader() , new Class[]{ServiceInterface.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("前置通知"); method.invoke(finalService,args); System.out.println("后置通知"); return proxy; } });
我們也效仿它的做法. 代碼如下:
public class Test { public static void main(String[] args) { IndexDao indexDao = new IndexDao(); Dao dao =(Dao) ProxyUtil.newInstance(Dao.class,new MyInvocationHandlerImpl(indexDao)); assert dao != null; System.out.println(dao.say("changwu")); } }
拿到了接口的Class對(duì)象后,通過反射就得知了接口中有哪些方法描述對(duì)象Method,獲取到的所有的方法,這些方法就是我們需要增強(qiáng)的方法
如何將增強(qiáng)的邏輯動(dòng)態(tài)的傳遞進(jìn)來呢?
JDK的做法是通過InvocationHandler的第三個(gè)參數(shù)完成,他是個(gè)接口,里面只有一個(gè)抽象方法如下: 可以看到它里面有三個(gè)入?yún)?分別是 代理對(duì)象,被代理對(duì)象的方法,被代理對(duì)象的方法的參數(shù)
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
當(dāng)我們使用jdk的動(dòng)態(tài)代理時(shí),就是通過這個(gè)重寫這個(gè)鉤子函數(shù),將邏輯動(dòng)態(tài)的傳遞進(jìn)去,并且可以選擇在適當(dāng)?shù)牡胤阶屇繕?biāo)方法執(zhí)行
InvocationHandler接口必須存在必要性1:
為什么不傳遞進(jìn)去Method,而是傳遞進(jìn)去InvocationHandler對(duì)象呢? 很顯然,我們的初衷是借助ProxyUtil工具類完成對(duì)代理對(duì)象的拼串封裝,然后讓這個(gè)代理對(duì)象去執(zhí)行method.invoke(), 然而事與愿違,傳遞進(jìn)來的Method對(duì)象的確可以被ProxyUtil使用,調(diào)用method.invoke(), 但是我們的代理對(duì)象不能使用它,因?yàn)榇韺?duì)象在這個(gè)ProxyUtil還以一堆等待拼接字符串, ProxyUtil的作用只能是往代理對(duì)象上疊加字符串,卻不能直接傳遞給它一個(gè)對(duì)象,所以只能傳遞一個(gè)對(duì)象進(jìn)來,然后通過反射獲取到這個(gè)對(duì)象的實(shí)例,繼而有可能實(shí)現(xiàn)method.invoke()
InvocationHandler接口必須存在必要性2:
通過這個(gè)接口的規(guī)范,我們可以直接得知回調(diào)方法的名字就是invoke()所以說,在拼接字符串完成對(duì)代理對(duì)象的拼接時(shí),可以直接寫死它
思路
我們需要通過上面的ProxyUtil.newInstance(Dao.class,new MyInvocationHandlerImpl(indexDao))方法完成如下幾件事
根據(jù)入?yún)⑽恢玫男畔?提取我們需要的信息,如包名,方法名,等等
根據(jù)我們提取的信息通過字符串的拼接完成一個(gè)全新的java的拼接
這個(gè)java類就是我們的代理對(duì)象
拼接好的java類是一個(gè)String字符串,我們將它寫入磁盤取名XXX.java
通過ProxyUtil使用類加載器,將XXX.java讀取JVM中,形成Class對(duì)象
通過Class對(duì)象反射出我們需要的代理對(duì)象
ProxyUtil的實(shí)現(xiàn)如下:
public static Object newInstance(Class targetInf, MyInvocationHandler invocationHandler) { Method methods[] = targetInf.getDeclaredMethods(); String line = "\n"; String tab = "\t"; String infName = targetInf.getSimpleName(); String content = ""; String packageContent = "package com.myproxy;" + line; // 導(dǎo)包,全部導(dǎo)入接口層面,換成具體的實(shí)現(xiàn)類就會(huì)報(bào)錯(cuò) // String importContent = "import " + targetInf.getName() + ";" + line + "import com.changwu.代理技術(shù).模擬jdk實(shí)現(xiàn)動(dòng)態(tài)代理.MyInvocationHandler;" + line + "import java.lang.reflect.Method;" + line + "import java.lang.Exception;" + line; String clazzFirstLineContent = "public class $Proxy implements " + infName +"{"+ line; String filedContent = tab + "private MyInvocationHandler handler;"+ line; String constructorContent = tab + "public $Proxy (MyInvocationHandler handler){" + line + tab + tab + "this.handler =handler;" + line + tab + "}" + line; String methodContent = ""; // 遍歷它的全部方法,接口出現(xiàn)的全部方法進(jìn)行增強(qiáng) for (Method method : methods) { String returnTypeName = method.getReturnType().getSimpleName(); method.getReturnType().getSimpleName()); String methodName = method.getName(); Class<?>[] parameterTypes = method.getParameterTypes(); // 參數(shù)的.class String paramsClass = ""; for (Class<?> parameterType : parameterTypes) { paramsClass+= parameterType.getName()+","; } String[] split = paramsClass.split(","); //方法參數(shù)的類型數(shù)組 Sting.class String.class String argsContent = ""; String paramsContent = ""; int flag = 0; for (Class arg : parameterTypes) { // 獲取方法名 String temp = arg.getSimpleName(); argsContent += temp + " p" + flag + ","; paramsContent += "p" + flag + ","; flag++; } // 去掉方法參數(shù)中最后面多出來的, if (argsContent.length() > 0) { argsContent = argsContent.substring(0, argsContent.lastIndexOf(",") - 1); paramsContent = paramsContent.substring(0, paramsContent.lastIndexOf(",") - 1); } methodContent += tab + "public " + returnTypeName + " " + methodName + "(" + argsContent + ") {" + line + tab + tab+"Method method = null;"+line + tab + tab+"String [] args0 = null;"+line + tab + tab+"Class<?> [] args1= null;"+line // invoke入?yún)⑹荕ethod對(duì)象,而不是上面的字符串,所以的得通過反射創(chuàng)建出Method對(duì)象 + tab + tab+"try{"+line // 反射得到參數(shù)的類型數(shù)組 + tab + tab + tab + "args0 = \""+paramsClass+"\".split(\",\");"+line + tab + tab + tab + "args1 = new Class[args0.length];"+line + tab + tab + tab + "for (int i=0;i<args0.length;i++) {"+line + tab + tab + tab + " args1[i]=Class.forName(args0[i]);"+line + tab + tab + tab + "}"+line // 反射目標(biāo)方法 + tab + tab + tab + "method = Class.forName(\""+targetInf.getName()+"\").getDeclaredMethod(\""+methodName+"\",args1);"+line + tab + tab+"}catch (Exception e){"+line + tab + tab+ tab+"e.printStackTrace();"+line + tab + tab+"}"+line + tab + tab + "return ("+returnTypeName+") this.handler.invoke(method,\"暫時(shí)不知道的方法\");" + line; // methodContent+= tab + "}"+line; } content = packageContent + importContent + clazzFirstLineContent + filedContent + constructorContent + methodContent + "}"; File file = new File("d:\\com\\myproxy\\$Proxy.java"); try { if (!file.exists()) { file.createNewFile(); } FileWriter fw = new FileWriter(file); fw.write(content); fw.flush(); fw.close(); // 將生成的.java的文件編譯成 .class文件 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null); Iterable units = fileMgr.getJavaFileObjects(file); JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units); t.call(); fileMgr.close(); // 使用類加載器將.class文件加載進(jìn)jvm // 因?yàn)楫a(chǎn)生的.class不在我們的工程當(dāng)中 URL[] urls = new URL[]{new URL("file:D:\\\\")}; URLClassLoader urlClassLoader = new URLClassLoader(urls); Class clazz = urlClassLoader.loadClass("com.myproxy.$Proxy"); return clazz.getConstructor(MyInvocationHandler.class).newInstance(invocationHandler); } catch (Exception e) { e.printStackTrace(); } return null; } }
運(yùn)行的效果:
package com.myproxy; import com.changwu.myproxy.pro.Dao; import com.changwu.myproxy.pro.MyInvocationHandler; import java.lang.reflect.Method; import java.lang.Exception; public class $Proxy implements Dao{ private MyInvocationHandler handler; public $Proxy (MyInvocationHandler handler){ this.handler =handler; } public String say(String p) { Method method = null; String [] args0 = null; Class<?> [] args1= null; try{ args0 = "java.lang.String,".split(","); args1 = new Class[args0.length]; for (int i=0;i<args0.length;i++) { args1[i]=Class.forName(args0[i]); } method = Class.forName("com.changwu.myproxy.pro.Dao").getDeclaredMethod("say",args1); }catch (Exception e){ e.printStackTrace(); } return (String) this.handler.invoke(method,"暫時(shí)不知道的方法"); } }
解讀
通過newInstance()用戶獲取到的代理對(duì)象就像上面的代理一樣,這個(gè)過程是在java代碼運(yùn)行時(shí)生成的,但是直接看他的結(jié)果和靜態(tài)代理差不錯(cuò),這時(shí)用戶再去調(diào)用代理對(duì)象的say(), 實(shí)際上就是在執(zhí)行用戶傳遞進(jìn)去的InvocationHandeler里面的invoke方法, 但是亮點(diǎn)是我們把目標(biāo)方法的描述對(duì)象Method同時(shí)給他傳遞進(jìn)去了,讓用戶可以執(zhí)行目標(biāo)方法+增強(qiáng)的邏輯
當(dāng)通過反射區(qū)執(zhí)行Method對(duì)象的invoke()方法時(shí),指定的哪個(gè)對(duì)象的當(dāng)前方法呢? 這個(gè)參數(shù)其實(shí)是我們手動(dòng)傳遞進(jìn)去的代理對(duì)象代碼如下
public class MyInvocationHandlerImpl implements MyInvocationHandler { private Object obj; public MyInvocationHandlerImpl(Object obj) { this.obj = obj; } @Override public Object invoke(Method method, Object[] args) { System.out.println("前置通知"); try { method.invoke(obj,args); } catch (Exception e) { e.printStackTrace(); } System.out.println("后置通知"); return null; } }
看完上述內(nèi)容,你們對(duì)如何手動(dòng)模擬一個(gè)JDK動(dòng)態(tài)代理有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。