java 中動(dòng)態(tài)代理機(jī)制的實(shí)例講解
java 中動(dòng)態(tài)代理機(jī)制的實(shí)例講解
在學(xué)習(xí)Spring的時(shí)候,我們知道Spring主要有兩大思想,一個(gè)是IoC,另一個(gè)就是AOP,對于IoC,依賴注入就不用多說了,而對于Spring的核心AOP來說,我們不但要知道怎么通過AOP來滿足的我們的功能,我們更需要學(xué)習(xí)的是其底層是怎么樣的一個(gè)原理,而AOP的原理就是java的動(dòng)態(tài)代理機(jī)制,所以本篇隨筆就是對java的動(dòng)態(tài)機(jī)制進(jìn)行一個(gè)回顧。
在java的動(dòng)態(tài)代理機(jī)制中,有兩個(gè)重要的類或接口,一個(gè)是 InvocationHandler(Interface)、另一個(gè)則是 Proxy(Class),這一個(gè)類和接口是實(shí)現(xiàn)我們動(dòng)態(tài)代理所必須用到的。首先我們先來看看java的API幫助文檔是怎么樣對這兩個(gè)類進(jìn)行描述的:
InvocationHandler:
InvocationHandler is the interface implemented by the invocation handler of a proxy instance. Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.
每一個(gè)動(dòng)態(tài)代理類都必須要實(shí)現(xiàn)InvocationHandler這個(gè)接口,并且每個(gè)代理類的實(shí)例都關(guān)聯(lián)到了一個(gè)handler,當(dāng)我們通過代理對象調(diào)用一個(gè)方法的時(shí)候,這個(gè)方法的調(diào)用就會(huì)被轉(zhuǎn)發(fā)為由InvocationHandler這個(gè)接口的 invoke 方法來進(jìn)行調(diào)用。我們來看看InvocationHandler這個(gè)接口的唯一一個(gè)方法 invoke 方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
我們看到這個(gè)方法一共接受三個(gè)參數(shù),那么這三個(gè)參數(shù)分別代表什么呢?
- proxy: 指代我們所代理的那個(gè)真實(shí)對象
- method: 指代的是我們所要調(diào)用真實(shí)對象的某個(gè)方法的Method對象
- args: 指代的是調(diào)用真實(shí)對象某個(gè)方法時(shí)接受的參數(shù)
如果不是很明白,等下通過一個(gè)實(shí)例會(huì)對這幾個(gè)參數(shù)進(jìn)行更深的講解。
接下來我們來看看Proxy這個(gè)類:
Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.
Proxy這個(gè)類的作用就是用來動(dòng)態(tài)創(chuàng)建一個(gè)代理對象的類,它提供了許多的方法,但是我們用的最多的就是 newProxyInstance 這個(gè)方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.
這個(gè)方法的作用就是得到一個(gè)動(dòng)態(tài)的代理對象,其接收三個(gè)參數(shù),我們來看看這三個(gè)參數(shù)所代表的含義:
- loader:一個(gè)ClassLoader對象,定義了由哪個(gè)ClassLoader對象來對生成的代理對象進(jìn)行加載
- interfaces:一個(gè)Interface對象的數(shù)組,表示的是我將要給我需要代理的對象提供一組什么接口,如果我提供了一組接口給它,那么這個(gè)代理對象就宣稱實(shí)現(xiàn)了該接口(多態(tài)),這樣我就能調(diào)用這組接口中的方法了
- h:一個(gè)InvocationHandler對象,表示的是當(dāng)我這個(gè)動(dòng)態(tài)代理對象在調(diào)用方法的時(shí)候,會(huì)關(guān)聯(lián)到哪一個(gè)InvocationHandler對象上
好了,在介紹完這兩個(gè)接口(類)以后,我們來通過一個(gè)實(shí)例來看看我們的動(dòng)態(tài)代理模式是什么樣的:
首先我們定義了一個(gè)Subject類型的接口,為其聲明了兩個(gè)方法:
public interface Subject { public void rent(); public void hello(String str); }
接著,定義了一個(gè)類來實(shí)現(xiàn)這個(gè)接口,這個(gè)類就是我們的真實(shí)對象,RealSubject類:
public class RealSubject implements Subject { @Override public void rent() { System.out.println("I want to rent my house"); } @Override public void hello(String str) { System.out.println("hello: " + str); } }
下一步,我們就要定義一個(gè)動(dòng)態(tài)代理類了,前面說個(gè),每一個(gè)動(dòng)態(tài)代理類都必須要實(shí)現(xiàn) InvocationHandler 這個(gè)接口,因此我們這個(gè)動(dòng)態(tài)代理類也不例外:
public class DynamicProxy implements InvocationHandler { // 這個(gè)就是我們要代理的真實(shí)對象 private Object subject; // 構(gòu)造方法,給我們要代理的真實(shí)對象賦初值 public DynamicProxy(Object subject) { this.subject = subject; } @Override public Object invoke(Object object, Method method, Object[] args) throws Throwable { // 在代理真實(shí)對象前我們可以添加一些自己的操作 System.out.println("before rent house"); System.out.println("Method:" + method); // 當(dāng)代理對象調(diào)用真實(shí)對象的方法時(shí),其會(huì)自動(dòng)的跳轉(zhuǎn)到代理對象關(guān)聯(lián)的handler對象的invoke方法來進(jìn)行調(diào)用 method.invoke(subject, args); // 在代理真實(shí)對象后我們也可以添加一些自己的操作 System.out.println("after rent house"); return null; } }
最后,來看看我們的Client類:
/** * Java學(xué)習(xí)交流QQ群:589809992 我們一起學(xué)Java! */ public class Client { public static void main(String[] args) { // 我們要代理的真實(shí)對象 Subject realSubject = new RealSubject(); // 我們要代理哪個(gè)真實(shí)對象,就將該對象傳進(jìn)去,最后是通過該真實(shí)對象來調(diào)用其方法的 InvocationHandler handler = new DynamicProxy(realSubject); /* * 通過Proxy的newProxyInstance方法來創(chuàng)建我們的代理對象,我們來看看其三個(gè)參數(shù) * 第一個(gè)參數(shù) handler.getClass().getClassLoader() ,我們這里使用handler這個(gè)類的ClassLoader對象來加載我們的代理對象 * 第二個(gè)參數(shù)realSubject.getClass().getInterfaces(),我們這里為代理對象提供的接口是真實(shí)對象所實(shí)行的接口,表示我要代理的是該真實(shí)對象,這樣我就能調(diào)用這組接口中的方法了 * 第三個(gè)參數(shù)handler, 我們這里將這個(gè)代理對象關(guān)聯(lián)到了上方的 InvocationHandler 這個(gè)對象上 */ Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject .getClass().getInterfaces(), handler); System.out.println(subject.getClass().getName()); subject.rent(); subject.hello("world"); } }
我們先來看看控制臺的輸出:
$Proxy0 before rent house Method:public abstract void com.xiaoluo.dynamicproxy.Subject.rent() I want to rent my house after rent house before rent house Method:public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String) hello: world after rent house
我們首先來看看 $Proxy0 這東西,我們看到,這個(gè)東西是由 System.out.println(subject.getClass().getName()); 這條語句打印出來的,那么為什么我們返回的這個(gè)代理對象的類名是這樣的呢?
Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject .getClass().getInterfaces(), handler);
可能我以為返回的這個(gè)代理對象會(huì)是Subject類型的對象,或者是InvocationHandler的對象,結(jié)果卻不是,首先我們解釋一下為什么我們這里可以將其轉(zhuǎn)化為Subject類型的對象?原因就是在newProxyInstance這個(gè)方法的第二個(gè)參數(shù)上,我們給這個(gè)代理對象提供了一組什么接口,那么我這個(gè)代理對象就會(huì)實(shí)現(xiàn)了這組接口,這個(gè)時(shí)候我們當(dāng)然可以將這個(gè)代理對象強(qiáng)制類型轉(zhuǎn)化為這組接口中的任意一個(gè),因?yàn)檫@里的接口是Subject類型,所以就可以將其轉(zhuǎn)化為Subject類型了。
同時(shí)我們一定要記住,通過 Proxy.newProxyInstance 創(chuàng)建的代理對象是在jvm運(yùn)行時(shí)動(dòng)態(tài)生成的一個(gè)對象,它并不是我們的InvocationHandler類型,也不是我們定義的那組接口的類型,而是在運(yùn)行是動(dòng)態(tài)生成的一個(gè)對象,并且命名方式都是這樣的形式,以$開頭,proxy為中,最后一個(gè)數(shù)字表示對象的標(biāo)號。
接著我們來看看這兩句
subject.rent(); subject.hello(“world”);
這里是通過代理對象來調(diào)用實(shí)現(xiàn)的那種接口中的方法,這個(gè)時(shí)候程序就會(huì)跳轉(zhuǎn)到由這個(gè)代理對象關(guān)聯(lián)到的 handler 中的invoke方法去執(zhí)行,而我們的這個(gè) handler 對象又接受了一個(gè) RealSubject類型的參數(shù),表示我要代理的就是這個(gè)真實(shí)對象,所以此時(shí)就會(huì)調(diào)用 handler 中的invoke方法去執(zhí)行:
/** * Java學(xué)習(xí)交流QQ群:589809992 我們一起學(xué)Java! */ public Object invoke(Object object, Method method, Object[] args) throws Throwable { // 在代理真實(shí)對象前我們可以添加一些自己的操作 System.out.println("before rent house"); System.out.println("Method:" + method); // 當(dāng)代理對象調(diào)用真實(shí)對象的方法時(shí),其會(huì)自動(dòng)的跳轉(zhuǎn)到代理對象關(guān)聯(lián)的handler對象的invoke方法來進(jìn)行調(diào)用 method.invoke(subject, args); // 在代理真實(shí)對象后我們也可以添加一些自己的操作 System.out.println("after rent house"); return null; }
我們看到,在真正通過代理對象來調(diào)用真實(shí)對象的方法的時(shí)候,我們可以在該方法前后添加自己的一些操作,同時(shí)我們看到我們的這個(gè) method 對象是這樣的:
public abstract void com.xiaoluo.dynamicproxy.Subject.rent() public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)
正好就是我們的Subject接口中的兩個(gè)方法,這也就證明了當(dāng)我通過代理對象來調(diào)用方法的時(shí)候,起實(shí)際就是委托由其關(guān)聯(lián)到的 handler 對象的invoke方法中來調(diào)用,并不是自己來真實(shí)調(diào)用,而是通過代理的方式來調(diào)用的。
這就是我們的java動(dòng)態(tài)代理機(jī)制。
本篇隨筆詳細(xì)的講解了java中的動(dòng)態(tài)代理機(jī)制,這個(gè)知識點(diǎn)非常非常的重要,包括我們Spring的AOP其就是通過動(dòng)態(tài)代理的機(jī)制實(shí)現(xiàn)的,所以我們必須要好好的理解動(dòng)態(tài)代理的機(jī)制。
如有疑問請留言或者到本站社區(qū)交流討論,感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
相關(guān)文章
SpringBoot中的ApplicationListener事件監(jiān)聽器使用詳解
這篇文章主要介紹了SpringBoot中的ApplicationListener事件監(jiān)聽器使用詳解,ApplicationListener是應(yīng)用程序的事件監(jiān)聽器,繼承自java.util.EventListener標(biāo)準(zhǔn)接口,采用觀察者設(shè)計(jì)模式,需要的朋友可以參考下2023-11-11Java設(shè)計(jì)模式之橋模式(Bridge模式)介紹
這篇文章主要介紹了Java設(shè)計(jì)模式之橋模式(Bridge模式)介紹,本文講解了為什么使用橋模式、如何實(shí)現(xiàn)橋模式、Bridge模式在EJB中的應(yīng)用等內(nèi)容,需要的朋友可以參考下2015-03-03IDEA集成JProfiler11可視化工具的詳細(xì)流程(安裝、集成、測試)
小編打算在IDEA中集成一下JProfiler11(現(xiàn)在有12版本了)工具,到網(wǎng)上搜都沒有找到合適的,于是自己動(dòng)手寫個(gè),關(guān)于IDEA集成JProfiler11可視化工具(安裝、集成、測試)相關(guān)知識感興趣的朋友一起看看吧2021-06-06Java abstract class 與 interface對比
這篇文章主要介紹了 Java abstract class 與 interface對比的相關(guān)資料,需要的朋友可以參考下2016-12-12SpringCloud的Gateway網(wǎng)關(guān)詳解
這篇文章主要介紹了SpringCloud的Gateway網(wǎng)關(guān)詳解,Gateway 是 Spring Cloud 官方推出的一個(gè)基于 Spring 5、Spring Boot 2 和 Project Reactor 的 API 網(wǎng)關(guān)實(shí)現(xiàn),本文將介紹 Spring Cloud Gateway 的基本概念、核心組件以及如何配置和使用它,需要的朋友可以參考下2023-09-09解決Springboot不能自動(dòng)提交數(shù)據(jù)庫連接問題
在使用SSM框架開發(fā)時(shí),若在同一Service內(nèi)部方法間互相調(diào)用,直接使用this關(guān)鍵字會(huì)導(dǎo)致事務(wù)管理失效,從而引發(fā)如數(shù)據(jù)庫連接不足等問題,原因是通過this調(diào)用不會(huì)經(jīng)過Spring的代理,因此不會(huì)自動(dòng)進(jìn)行事務(wù)處理2024-09-09Java實(shí)戰(zhàn)之超市收銀管理系統(tǒng)的實(shí)現(xiàn)
這篇文章主要介紹了如何利用Java實(shí)現(xiàn)超市收銀管理系統(tǒng),文中采用的技術(shù)有:Spring、SpringMVC、MyBatis、ThymeLeaf等,需要的可以參考一下2022-03-03