JDK動(dòng)態(tài)代理,代理接口沒有實(shí)現(xiàn)類,實(shí)現(xiàn)動(dòng)態(tài)代理方式
JDK動(dòng)態(tài)代理,代理接口沒有實(shí)現(xiàn)類,實(shí)現(xiàn)動(dòng)態(tài)代理
JDK代理,代理的是接口,那么筆者想一想,既然代理的是接口,那如果沒有實(shí)現(xiàn)類怎么辦,能不能代理。答案是可以的,Mybatis就是這樣的。
Mybatis使用JDK動(dòng)態(tài)代理來實(shí)現(xiàn)Mapper接口,事先保存好Mapper接口,和接口聲明的方法,返回值,參數(shù)類型,然后代理類的方法調(diào)用的時(shí)候使用MapperMethod這個(gè)事先放入方法緩存里的對(duì)象來真實(shí)調(diào)用功能。
筆者極度簡(jiǎn)化了一下代碼:
被代理的接口:
public interface Subject2 { String selectById(); }
這個(gè)接口可以看成是Mapper接口
代理對(duì)象:
public class SubjectProxy2<T> implements InvocationHandler { private Class<T> proxyInterface; //這里可以維護(hù)一個(gè)緩存,存這個(gè)接口的方法抽象的對(duì)象 SubjectProxy2(Class<T> proxyInterface){ this.proxyInterface = proxyInterface; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("selectById")){ //String result = (String) method.invoke(proxyInterface,args); //這里可以得到方法抽象對(duì)象來調(diào)用真的的查詢方法 System.out.println("selectById調(diào)用成功"); } return null; } public T getProxy(){ return (T) Proxy.newProxyInstance(proxyInterface.getClassLoader(),new Class[]{proxyInterface},this); } }
這個(gè)代理類使用了泛型,說明這個(gè)代理類可以代理所有的mapper接口。
那么接下來測(cè)試一下:
public class ProxyTest2 { public static void main(String[] args) { SubjectProxy2<Subject2> subjectProxy2 = new SubjectProxy2(Subject2.class); Subject2 subject2 = subjectProxy2.getProxy(); subject2.selectById(); } }
結(jié)果不言而喻。肯定會(huì)有相應(yīng)的輸出
沒有看mybatis源碼的時(shí)候,我以為動(dòng)態(tài)代理一定要要有實(shí)現(xiàn)類才能代理,但是看了優(yōu)秀的頂級(jí)大牛的源碼之后,我才發(fā)現(xiàn),原來還可以這樣。
jdk動(dòng)態(tài)代理為什么要接口
jdk的動(dòng)態(tài)代理為什么用接口,內(nèi)部是什么原理呢?看了幾篇文章貌似都沒講的清楚明白,因此來解釋一下。
先通過一個(gè)簡(jiǎn)單例子實(shí)現(xiàn)功能:
//接口 public interface SayService { void say(String name); } //實(shí)現(xiàn)類 public class SayServiceImpl implements SayService{ @Override public void say(String name) { System.out.println(name); } }
然后再自定義一個(gè)增強(qiáng)類, 實(shí)現(xiàn)InvocationHandler接口:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class WavingInvocationHandler implements InvocationHandler{ private Object target; public void setTarget(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("方法執(zhí)行之前!"); Object obj = method.invoke(target, args); System.out.println("方法執(zhí)行之后!"); return obj; } }
編寫測(cè)試方法:
import sun.misc.ProxyGenerator; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) { //接口 SayService sayService = new SayServiceImpl(); //織入類 WavingInvocationHandler handler = new WavingInvocationHandler(); handler.setTarget(sayService); //代理類--增強(qiáng)的對(duì)象 SayService s = (SayService) Proxy.newProxyInstance( sayService.getClass().getClassLoader(), sayService.getClass().getInterfaces(), handler); s.say("say()");//執(zhí)行代理對(duì)象完成業(yè)務(wù) /** 方法執(zhí)行之前! say() 方法執(zhí)行之后! */ //將jdk中生成代理類輸出到本地.Class文件,之后可以通過反編譯軟件打開查看 createProxyClassFile("test12345",sayService.getClass().getInterfaces()); } private static void createProxyClassFile(String name,Class<?> [] interfaces){ byte[] data = ProxyGenerator.generateProxyClass(name,interfaces);//該方法為jdk中生成代理類的核心方法 FileOutputStream out =null; try { out = new FileOutputStream(name+".class"); System.out.println((new File(name)).getAbsolutePath()); out.write(data); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { if(null!=out) try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } }
好奇心強(qiáng)的小伙伴一定看過newProxyInstance方法看過了,
里面的getProxyClass方法創(chuàng)建代理類:
/* * Look up or generate the designated proxy class. */ Class<?> cl = getProxyClass0(loader, intfs);
具體是里面的哪個(gè)方法生成的,小伙伴們不用找半天了,慢慢debug后會(huì)發(fā)現(xiàn),就是上面提到的
ProxyGenerator.generateProxyClass()
通過該方法生成的代理類如下:
通過反編譯查看源碼,一看便知接口的作用
JDK的動(dòng)態(tài)代理是靠多態(tài)和反射來實(shí)現(xiàn)的,它生成的代理類需要實(shí)現(xiàn)你傳入的接口,并通過反射來得到接口的方法對(duì)象(下文中的m3),并將此方法對(duì)象傳參給增強(qiáng)類(上文中的WavingInvocationHandler類)的invoke方法去執(zhí)行,從而實(shí)現(xiàn)了代理功能,故接口是jdk動(dòng)態(tài)代理的核心實(shí)現(xiàn)方式,沒有它就無法通過反射找到方法,所以這也是必須有接口的原因。不知道大家明白否
public final class test12345 extends Proxy implements SayService { //1、該類實(shí)現(xiàn)你傳入的接口并實(shí)現(xiàn)方法 // 2、通過構(gòu)造方法傳入你定義的增強(qiáng)類對(duì)象 // 3、通過反射該接口,得到接口里的Method對(duì)象并傳參給增強(qiáng)類,然后執(zhí)行Invoke實(shí)現(xiàn)功能 private static Method m1; private static Method m2; private static Method m3; private static Method m0; public test12345(InvocationHandler paramInvocationHandler) { super(paramInvocationHandler);//2.此處的super指向Proxy中的構(gòu)造方法并賦值(下文的h就是此處的增強(qiáng)類對(duì)象), } //略去無關(guān)的hashcode和equals方法 public final void say(String paramString) { try { this.h.invoke(this, m3, new Object[]{paramString});//3.此處的h就是InvocationHandler對(duì)象,invoke就是你增強(qiáng)類里的方法,傳入接口的方法和參數(shù)并執(zhí)行你增強(qiáng)類里的invoke方法,即完成了整個(gè)操作! } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } static {//1.靜態(tài)代碼塊給屬性賦值,初始化接口中的方法對(duì)象(主要是下面的m3) try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")}); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m3 = Class.forName("com.chenrui.core.jdk.SayService").getMethod("say", new Class[]{Class.forName("java.lang.String")}); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } }
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
JSch教程使用sftp協(xié)議實(shí)現(xiàn)服務(wù)器文件載操作
這篇文章主要為大家介紹了JSch如何使用sftp協(xié)議實(shí)現(xiàn)服務(wù)器文件上傳下載操作,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03使用Thrift實(shí)現(xiàn)跨語言RPC的調(diào)用
Thrift最大的優(yōu)勢(shì)就是可以實(shí)現(xiàn)跨語言RPC調(diào)用,尤其在一些大廠,微服務(wù)各模塊之間使用不同的語言是很常見的,本文就將使用java作為服務(wù)端,用python作為客戶端,實(shí)現(xiàn)不同語言之間的RPC調(diào)用,需要的可以參考下2023-10-10SpringBoot 異步線程間數(shù)據(jù)傳遞的實(shí)現(xiàn)
本文主要介紹了SpringBoot 異步線程間數(shù)據(jù)傳遞的實(shí)現(xiàn),包括異步線程的基本概念、數(shù)據(jù)傳遞的方式、具體實(shí)現(xiàn)方式等,具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03Spring Cloud Gateway組件的三種使用方式實(shí)例詳解
Spring Cloud Gateway是 Spring 官方基于 Spring5.0 、 SpringBoot2.0 和 Project Reactor 等技術(shù)開發(fā)的網(wǎng)關(guān)旨在為微服務(wù)框架提供一種簡(jiǎn)單而有效的統(tǒng)一的API 路由管理方式,統(tǒng)一訪問接口,這篇文章主要介紹了Spring Cloud Gateway組件的三種使用方式,需要的朋友可以參考下2024-01-01解析springboot集成AOP實(shí)現(xiàn)日志輸出的方法
如果這需要在每一個(gè)controller層去寫的話代碼過于重復(fù),于是就使用AOP定義切面 對(duì)其接口調(diào)用前后進(jìn)行攔截日志輸出。接下來通過本文給大家介紹springboot集成AOP實(shí)現(xiàn)日志輸出,需要的朋友可以參考下2021-11-11MyBatis-Plus實(shí)現(xiàn)邏輯刪除的示例代碼
本文主要介紹了MyBatis-Plus實(shí)現(xiàn)邏輯刪除的示例代碼,就是通過邏輯判斷的手段表示該條數(shù)據(jù)已刪除,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05SpringBoot使用阿里OSS實(shí)現(xiàn)文件云存儲(chǔ)的方法
這篇文章主要介紹了SpringBoot使用阿里OSS實(shí)現(xiàn)文件云存儲(chǔ),本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10