Java中的動態(tài)代理和靜態(tài)代理詳細(xì)解析
1.什么是代理?
生活中的代理是很常見的,比如代購、律師、中介等,他們都有一個共性就是幫助被代理人處理一些前前后后的事情。而被代理人只需要專注做自己要做的那部分事情就可以了。當(dāng)然這里的被代理人不可能是只有一個,假如只有一個那根本養(yǎng)不活這群代理。
Java中的代理也是類似的,代理可以幫助被代理者完成一些前期的準(zhǔn)備工作和后期的善后工作,但是核心的業(yè)務(wù)邏輯仍然是由被代理者完成。就好比中介找房,你想要什么房中介都可以幫你找,但是錢還是得你出,房還是得你親自看。
2.為什么要使用代理?
從生活的角度上來說,租房為什么需要中介呢?我們直接找房東不好嗎,其實中介他起到的作用是匯集房源,假如我們直接找房東,先不說房東好不好找,房子肯定是有數(shù)的,一個房東頂多也就幾套房,而一個中介可能頂上好幾個房東的房源了。說直白點,租房是我們的目的,在不改變自己的目的情況下,快速的找到房源,那就是通過中介!
從代碼角度上來說,在不修改源碼的基礎(chǔ)上對方法進(jìn)行加強(qiáng)。當(dāng)然不是所有代碼涉及到增強(qiáng)就需要使用代理,而是很多方法都涉及到了增強(qiáng),才會統(tǒng)一使用一個代理,舉例:我們要給所有的接口添加操作日志,這時候不可能說在每個接口上添加操作日志,這樣會導(dǎo)致重復(fù)代碼一大堆,所以這時候我們從中間抽出來一個代理,被代理類只需要專注于自己的核心代碼即可,日志記錄交給代理類就可以了,優(yōu)點:使得代碼更加簡潔,分工明確。
3靜態(tài)代理
代理模式分為動態(tài)代理和靜態(tài)代理。兩者的差別還是很大的,不過思想都是一樣的,起到一個服務(wù)中介的作用。
所謂靜態(tài)代理也就是在程序運行前就已經(jīng)存在代理類的字節(jié)碼文件,而動態(tài)代理是通過某種方式來生成的代理字節(jié)碼文件。
3.1.靜態(tài)代理的示例
以下是通過一個買車示例來演示靜態(tài)代理的思想,實際開發(fā)當(dāng)中我們可能不會這么寫,但是更多的是需要理解他的思想!
(1)定義接口,接口當(dāng)中有一個抽象方法
/** * 買車接口 * @author guo * */ public interface StaticMy { void lawsuit(); }
(2)定義被代理類,實現(xiàn)StaticMy接口
package com.gzl.static1; /** * 被代理方 我 * @author guo * */ public class StaticMyImpl implements StaticMy{ public void lawsuit() { System.out.println("我要買車"); } }
(3)代理類同樣也需要實現(xiàn)StaticMy接口,將被代理類通過構(gòu)造器的方式傳入代理類,由代理類對被代理類進(jìn)行加強(qiáng)
package com.gzl.static1; public class Shop implements StaticMy{ private StaticMyImpl staticMy; public Shop(StaticMyImpl staticMy) { super(); this.staticMy = staticMy; } public void lawsuit() { System.out.println("廠子進(jìn)車"); this.staticMy.lawsuit(); System.out.println("交車"); } }
(4)測試
package com.gzl.static1; public class Test { public static void main(String[] args) { // 創(chuàng)建被代理類 StaticMyImpl shop = new StaticMyImpl(); // 將被代理類傳入代理類當(dāng)中 Shop shop2 = new Shop(shop); // 由代理類來執(zhí)行 shop2.lawsuit(); } }
執(zhí)行結(jié)果
4.動態(tài)代理
動態(tài)代理就是,在程序運行期,創(chuàng)建目標(biāo)對象的代理對象,并對目標(biāo)對象中的方法進(jìn)行功能性增強(qiáng)的一種技術(shù)。在生成代理對象的過程中,目標(biāo)對象不變,代理對象中的方法是目標(biāo)對象方法的增強(qiáng)方法??梢岳斫鉃檫\行期間,對象中方法的動態(tài)攔截,在攔截方法的前后執(zhí)行功能操作。
靜態(tài)代理是直接在代碼中聲明好的代理對象,而動態(tài)代理中的代理對象,并不是事先在Java代碼中定義好的。而是在運行期間,根據(jù)我們在動態(tài)代理對象中的“指示”,動態(tài)生成的。也就是說,你想獲取哪個對象的代理,動態(tài)代理就會為你動態(tài)的生成這個對象的代理對象。動態(tài)代理一般有兩種實現(xiàn)方式,cglib和jdk。
4.1.cglib和jdk動態(tài)代理的區(qū)別
- JDK代理使用的是反射機(jī)制生成一個實現(xiàn)代理接口的匿名類,在調(diào)用具體方法前調(diào)用InvokeHandler來處理。
- CGLIB代理使用字節(jié)碼處理框架ASM,對代理對象類的class文件加載進(jìn)來,通過修改字節(jié)碼生成子類。
- JDK創(chuàng)建代理對象效率較高,執(zhí)行效率較低;
- CGLIB創(chuàng)建代理對象效率較低,執(zhí)行效率高。
- JDK動態(tài)代理機(jī)制是委托機(jī)制,只能對實現(xiàn)接口的類生成代理,通過反射動態(tài)實現(xiàn)接口類;
- CGLIB則使用的繼承機(jī)制,針對類實現(xiàn)代理,被代理類和代理類是繼承關(guān)系,所以代理類是可以賦值給被代理類的,因為是繼承機(jī)制,不能代理final修飾的類。
JDK代理是不需要依賴第三方的庫,只要JDK環(huán)境就可以進(jìn)行代理,需要滿足以下要求:
- 實現(xiàn)InvocationHandler接口,重寫invoke()
- 使用Proxy.newProxyInstance()產(chǎn)生代理對象
- 被代理的對象必須要實現(xiàn)接口
CGLib 必須依賴于CGLib的類庫,需要滿足以下要求:
- 實現(xiàn)MethodInterceptor接口,重寫intercept()
- 使用Enhancer對象.create()產(chǎn)生代理對象
4.2.cglib動態(tài)代理示例
jdk動態(tài)代理只能為接口創(chuàng)建代理,使用上有局限性。實際的場景中我們的類不一定有接口,此時如果我們想為普通的類也實現(xiàn)代理功能,我們就需要用到cglib來實現(xiàn)了。
cglib是一個強(qiáng)大、高性能的字節(jié)碼生成庫,它用于在運行時擴(kuò)展Java類和實現(xiàn)接口;本質(zhì)上它是通過動態(tài)的生成一個子類去覆蓋所要代理的類(非final修飾的類和方法)。Enhancer可能是CGLIB中最常用的一個類,和jdk中的Proxy不同的是,Enhancer既能夠代理普通的class,也能夠代理接口。Enhancer創(chuàng)建一個被代理對象的子類并且攔截所有的方法調(diào)用(包括從Object中繼承的toString和hashCode方法)。Enhancer不能夠攔截final方法,例如Object.getClass()方法,這是由于Java final方法語義決定的?;谕瑯拥牡览恚珽nhancer也不能對final類進(jìn)行代理操作。
CGLIB底層使用了ASM(一個短小精悍的字節(jié)碼操作框架)來操作字節(jié)碼生成新的類。除了CGLIB庫外,腳本語言(如Groovy和BeanShell)也使用ASM生成字節(jié)碼。ASM使用類似SAX的解析器來實現(xiàn)高性能。
(1)cglib并不是java當(dāng)中自帶的,所以使用的話需要引入jar包
<dependencies> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.1_3</version> </dependency> </dependencies>
(2)創(chuàng)建一個類,寫入兩個方法
package com.itheima.cglib; /** * 一個生產(chǎn)者 */ public class Producer { /** * 銷售 * @param money */ public void saleProduct(float money){ System.out.println("銷售產(chǎn)品,并拿到錢:"+money); } /** * 售后 * @param money */ public void afterService(float money){ System.out.println("提供售后服務(wù),并拿到錢:"+money); } }
(3)動態(tài)代理類
在源碼當(dāng)中看到MethodInterceptor就是使用的cglib動態(tài)代理
package com.gzl.cglib; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * 模擬一個消費者 */ public class Client { public static void main(String[] args) { final Producer producer = new Producer(); /** * 動態(tài)代理: * 特點:字節(jié)碼隨用隨創(chuàng)建,隨用隨加載 * 作用:不修改源碼的基礎(chǔ)上對方法增強(qiáng) * 分類: * 基于接口的動態(tài)代理 * 基于子類的動態(tài)代理 * 基于子類的動態(tài)代理: * 涉及的類:Enhancer * 提供者:第三方cglib庫 * 如何創(chuàng)建代理對象: * 使用Enhancer類中的create方法 * 創(chuàng)建代理對象的要求: * 被代理類不能是最終類 * create方法的參數(shù): * Class:字節(jié)碼 * 它是用于指定被代理對象的字節(jié)碼。 * * Callback:用于提供增強(qiáng)的代碼 * 它是讓我們寫如何代理。我們一般都是些一個該接口的實現(xiàn)類,通常情況下都是匿名內(nèi)部類,但不是必須的。 * 此接口的實現(xiàn)類都是誰用誰寫。 * 我們一般寫的都是該接口的子接口實現(xiàn)類:MethodInterceptor */ Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() { /** * 執(zhí)行producer的任何方法都會經(jīng)過該方法 * @param proxy * @param method * @param args * 以上三個參數(shù)和基于接口的動態(tài)代理中invoke方法的參數(shù)是一樣的 * @param methodProxy :當(dāng)前執(zhí)行方法的代理對象 * @return * @throws Throwable */ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //提供增強(qiáng)的代碼 Object returnValue = null; //1.獲取方法執(zhí)行的參數(shù) Float money = (Float)args[0]; //2.判斷當(dāng)前方法是不是銷售 if("saleProduct".equals(method.getName())) { returnValue = method.invoke(producer, money*0.8f); } return returnValue; } }); cglibProducer.saleProduct(12000f); } }
執(zhí)行結(jié)果
4.3.JDK動態(tài)代理示例
(1)創(chuàng)建一個接口
package com.gzl.proxy; /** * 對生產(chǎn)廠家要求的接口 */ public interface IProducer { /** * 銷售 * @param money */ public void saleProduct(float money); /** * 售后 * @param money */ public void afterService(float money); }
(2)實現(xiàn)類
package com.gzl.proxy; /** * 一個生產(chǎn)者 */ public class Producer implements IProducer{ /** * 銷售 * @param money */ public void saleProduct(float money){ System.out.println("銷售產(chǎn)品,并拿到錢:"+money); } /** * 售后 * @param money */ public void afterService(float money){ System.out.println("提供售后服務(wù),并拿到錢:"+money); } }
(3)代理類
package com.gzl.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 模擬一個消費者 */ public class Client { public static void main(String[] args) { final Producer producer = new Producer(); /** * 動態(tài)代理: * 特點:字節(jié)碼隨用隨創(chuàng)建,隨用隨加載 * 作用:不修改源碼的基礎(chǔ)上對方法增強(qiáng) * 分類: * 基于接口的動態(tài)代理 * 基于子類的動態(tài)代理 * 基于接口的動態(tài)代理: * 涉及的類:Proxy * 提供者:JDK官方 * 如何創(chuàng)建代理對象: * 使用Proxy類中的newProxyInstance方法 * 創(chuàng)建代理對象的要求: * 被代理類最少實現(xiàn)一個接口,如果沒有則不能使用 * newProxyInstance方法的參數(shù): * ClassLoader:類加載器 * 它是用于加載代理對象字節(jié)碼的。和被代理對象使用相同的類加載器。固定寫法。 * Class[]:字節(jié)碼數(shù)組 * 它是用于讓代理對象和被代理對象有相同方法。固定寫法。 * InvocationHandler:用于提供增強(qiáng)的代碼 * 它是讓我們寫如何代理。我們一般都是些一個該接口的實現(xiàn)類,通常情況下都是匿名內(nèi)部類,但不是必須的。 * 此接口的實現(xiàn)類都是誰用誰寫。 */ IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() { /** * 作用:執(zhí)行被代理對象的任何接口方法都會經(jīng)過該方法 * 方法參數(shù)的含義 * @param proxy 代理對象的引用 * @param method 當(dāng)前執(zhí)行的方法 * @param args 當(dāng)前執(zhí)行方法所需的參數(shù) * @return 和被代理對象方法有相同的返回值 * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //提供增強(qiáng)的代碼 Object returnValue = null; //1.獲取方法執(zhí)行的參數(shù) Float money = (Float)args[0]; //2.判斷當(dāng)前方法是不是銷售 if("saleProduct".equals(method.getName())) { returnValue = method.invoke(producer, money*0.8f); } return returnValue; } }); proxyProducer.saleProduct(12000f); } }
執(zhí)行結(jié)果
到此這篇關(guān)于Java中的動態(tài)代理和靜態(tài)代理詳細(xì)解析的文章就介紹到這了,更多相關(guān)Java動態(tài)代理和靜態(tài)代理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java使用Sftp和Ftp實現(xiàn)對文件的上傳和下載
這篇文章主要介紹了Java使用Sftp和Ftp實現(xiàn)對文件的上傳和下載,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03簡單捋捋@RequestParam 和 @RequestBody的使用
這篇文章主要介紹了簡單捋捋@RequestParam 和 @RequestBody的使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12在SpringBoot下讀取自定義properties配置文件的方法
這篇文章主要介紹了在SpringBoot下讀取自定義properties配置文件的方法,文中涉及到了Spring-boot中讀取config配置文件的兩種方式,需要的朋友可以參考下2017-12-12Java 數(shù)組元素倒序的三種方式(小結(jié))
這篇文章主要介紹了Java 數(shù)組元素倒序的三種方式(小結(jié)),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09SpringCloud Ribbon負(fù)載均衡代碼實例
這篇文章主要介紹了SpringCloud Ribbon負(fù)載均衡代碼實例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-03-03Java虛擬機(jī)類加載器之雙親委派機(jī)制模型案例
這篇文章主要介紹了Java虛擬機(jī)類加載器之雙親委派機(jī)制模型案例,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08SpringBoot中的五種對靜態(tài)資源的映射規(guī)則的實現(xiàn)
這篇文章主要介紹了SpringBoot中的五種對靜態(tài)資源的映射規(guī)則的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12圖解Spring Security 中用戶是如何實現(xiàn)登錄的
這篇文章主要介紹了圖解Spring Security 中用戶是如何實現(xiàn)登錄的,文中通過示例代碼和圖片介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07