Java中的動(dòng)態(tài)代理和靜態(tài)代理詳細(xì)解析
1.什么是代理?
生活中的代理是很常見(jiàn)的,比如代購(gòu)、律師、中介等,他們都有一個(gè)共性就是幫助被代理人處理一些前前后后的事情。而被代理人只需要專注做自己要做的那部分事情就可以了。當(dāng)然這里的被代理人不可能是只有一個(gè),假如只有一個(gè)那根本養(yǎng)不活這群代理。
Java中的代理也是類似的,代理可以幫助被代理者完成一些前期的準(zhǔn)備工作和后期的善后工作,但是核心的業(yè)務(wù)邏輯仍然是由被代理者完成。就好比中介找房,你想要什么房中介都可以幫你找,但是錢(qián)還是得你出,房還是得你親自看。
2.為什么要使用代理?
從生活的角度上來(lái)說(shuō),租房為什么需要中介呢?我們直接找房東不好嗎,其實(shí)中介他起到的作用是匯集房源,假如我們直接找房東,先不說(shuō)房東好不好找,房子肯定是有數(shù)的,一個(gè)房東頂多也就幾套房,而一個(gè)中介可能頂上好幾個(gè)房東的房源了。說(shuō)直白點(diǎn),租房是我們的目的,在不改變自己的目的情況下,快速的找到房源,那就是通過(guò)中介!
從代碼角度上來(lái)說(shuō),在不修改源碼的基礎(chǔ)上對(duì)方法進(jìn)行加強(qiáng)。當(dāng)然不是所有代碼涉及到增強(qiáng)就需要使用代理,而是很多方法都涉及到了增強(qiáng),才會(huì)統(tǒng)一使用一個(gè)代理,舉例:我們要給所有的接口添加操作日志,這時(shí)候不可能說(shuō)在每個(gè)接口上添加操作日志,這樣會(huì)導(dǎo)致重復(fù)代碼一大堆,所以這時(shí)候我們從中間抽出來(lái)一個(gè)代理,被代理類只需要專注于自己的核心代碼即可,日志記錄交給代理類就可以了,優(yōu)點(diǎn):使得代碼更加簡(jiǎn)潔,分工明確。
3靜態(tài)代理
代理模式分為動(dòng)態(tài)代理和靜態(tài)代理。兩者的差別還是很大的,不過(guò)思想都是一樣的,起到一個(gè)服務(wù)中介的作用。
所謂靜態(tài)代理也就是在程序運(yùn)行前就已經(jīng)存在代理類的字節(jié)碼文件,而動(dòng)態(tài)代理是通過(guò)某種方式來(lái)生成的代理字節(jié)碼文件。
3.1.靜態(tài)代理的示例
以下是通過(guò)一個(gè)買車示例來(lái)演示靜態(tài)代理的思想,實(shí)際開(kāi)發(fā)當(dāng)中我們可能不會(huì)這么寫(xiě),但是更多的是需要理解他的思想!
(1)定義接口,接口當(dāng)中有一個(gè)抽象方法
/**
* 買車接口
* @author guo
*
*/
public interface StaticMy {
void lawsuit();
}
(2)定義被代理類,實(shí)現(xiàn)StaticMy接口
package com.gzl.static1;
/**
* 被代理方 我
* @author guo
*
*/
public class StaticMyImpl implements StaticMy{
public void lawsuit() {
System.out.println("我要買車");
}
}
(3)代理類同樣也需要實(shí)現(xiàn)StaticMy接口,將被代理類通過(guò)構(gòu)造器的方式傳入代理類,由代理類對(duì)被代理類進(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)測(cè)試
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);
// 由代理類來(lái)執(zhí)行
shop2.lawsuit();
}
}
執(zhí)行結(jié)果

4.動(dòng)態(tài)代理
動(dòng)態(tài)代理就是,在程序運(yùn)行期,創(chuàng)建目標(biāo)對(duì)象的代理對(duì)象,并對(duì)目標(biāo)對(duì)象中的方法進(jìn)行功能性增強(qiáng)的一種技術(shù)。在生成代理對(duì)象的過(guò)程中,目標(biāo)對(duì)象不變,代理對(duì)象中的方法是目標(biāo)對(duì)象方法的增強(qiáng)方法??梢岳斫鉃檫\(yùn)行期間,對(duì)象中方法的動(dòng)態(tài)攔截,在攔截方法的前后執(zhí)行功能操作。
靜態(tài)代理是直接在代碼中聲明好的代理對(duì)象,而動(dòng)態(tài)代理中的代理對(duì)象,并不是事先在Java代碼中定義好的。而是在運(yùn)行期間,根據(jù)我們?cè)趧?dòng)態(tài)代理對(duì)象中的“指示”,動(dòng)態(tài)生成的。也就是說(shuō),你想獲取哪個(gè)對(duì)象的代理,動(dòng)態(tài)代理就會(huì)為你動(dòng)態(tài)的生成這個(gè)對(duì)象的代理對(duì)象。動(dòng)態(tài)代理一般有兩種實(shí)現(xiàn)方式,cglib和jdk。
4.1.cglib和jdk動(dòng)態(tài)代理的區(qū)別
- JDK代理使用的是反射機(jī)制生成一個(gè)實(shí)現(xiàn)代理接口的匿名類,在調(diào)用具體方法前調(diào)用InvokeHandler來(lái)處理。
- CGLIB代理使用字節(jié)碼處理框架ASM,對(duì)代理對(duì)象類的class文件加載進(jìn)來(lái),通過(guò)修改字節(jié)碼生成子類。
- JDK創(chuàng)建代理對(duì)象效率較高,執(zhí)行效率較低;
- CGLIB創(chuàng)建代理對(duì)象效率較低,執(zhí)行效率高。
- JDK動(dòng)態(tài)代理機(jī)制是委托機(jī)制,只能對(duì)實(shí)現(xiàn)接口的類生成代理,通過(guò)反射動(dòng)態(tài)實(shí)現(xiàn)接口類;
- CGLIB則使用的繼承機(jī)制,針對(duì)類實(shí)現(xiàn)代理,被代理類和代理類是繼承關(guān)系,所以代理類是可以賦值給被代理類的,因?yàn)槭抢^承機(jī)制,不能代理final修飾的類。
JDK代理是不需要依賴第三方的庫(kù),只要JDK環(huán)境就可以進(jìn)行代理,需要滿足以下要求:
- 實(shí)現(xiàn)InvocationHandler接口,重寫(xiě)invoke()
- 使用Proxy.newProxyInstance()產(chǎn)生代理對(duì)象
- 被代理的對(duì)象必須要實(shí)現(xiàn)接口
CGLib 必須依賴于CGLib的類庫(kù),需要滿足以下要求:
- 實(shí)現(xiàn)MethodInterceptor接口,重寫(xiě)intercept()
- 使用Enhancer對(duì)象.create()產(chǎn)生代理對(duì)象
4.2.cglib動(dòng)態(tài)代理示例
jdk動(dòng)態(tài)代理只能為接口創(chuàng)建代理,使用上有局限性。實(shí)際的場(chǎng)景中我們的類不一定有接口,此時(shí)如果我們想為普通的類也實(shí)現(xiàn)代理功能,我們就需要用到cglib來(lái)實(shí)現(xiàn)了。
cglib是一個(gè)強(qiáng)大、高性能的字節(jié)碼生成庫(kù),它用于在運(yùn)行時(shí)擴(kuò)展Java類和實(shí)現(xiàn)接口;本質(zhì)上它是通過(guò)動(dòng)態(tài)的生成一個(gè)子類去覆蓋所要代理的類(非final修飾的類和方法)。Enhancer可能是CGLIB中最常用的一個(gè)類,和jdk中的Proxy不同的是,Enhancer既能夠代理普通的class,也能夠代理接口。Enhancer創(chuàng)建一個(gè)被代理對(duì)象的子類并且攔截所有的方法調(diào)用(包括從Object中繼承的toString和hashCode方法)。Enhancer不能夠攔截final方法,例如Object.getClass()方法,這是由于Java final方法語(yǔ)義決定的。基于同樣的道理,Enhancer也不能對(duì)final類進(jìn)行代理操作。
CGLIB底層使用了ASM(一個(gè)短小精悍的字節(jié)碼操作框架)來(lái)操作字節(jié)碼生成新的類。除了CGLIB庫(kù)外,腳本語(yǔ)言(如Groovy和BeanShell)也使用ASM生成字節(jié)碼。ASM使用類似SAX的解析器來(lái)實(shí)現(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)建一個(gè)類,寫(xiě)入兩個(gè)方法
package com.itheima.cglib;
/**
* 一個(gè)生產(chǎn)者
*/
public class Producer {
/**
* 銷售
* @param money
*/
public void saleProduct(float money){
System.out.println("銷售產(chǎn)品,并拿到錢(qián):"+money);
}
/**
* 售后
* @param money
*/
public void afterService(float money){
System.out.println("提供售后服務(wù),并拿到錢(qián):"+money);
}
}
(3)動(dòng)態(tài)代理類
在源碼當(dāng)中看到MethodInterceptor就是使用的cglib動(dòng)態(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;
/**
* 模擬一個(gè)消費(fèi)者
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
/**
* 動(dòng)態(tài)代理:
* 特點(diǎn):字節(jié)碼隨用隨創(chuàng)建,隨用隨加載
* 作用:不修改源碼的基礎(chǔ)上對(duì)方法增強(qiáng)
* 分類:
* 基于接口的動(dòng)態(tài)代理
* 基于子類的動(dòng)態(tài)代理
* 基于子類的動(dòng)態(tài)代理:
* 涉及的類:Enhancer
* 提供者:第三方cglib庫(kù)
* 如何創(chuàng)建代理對(duì)象:
* 使用Enhancer類中的create方法
* 創(chuàng)建代理對(duì)象的要求:
* 被代理類不能是最終類
* create方法的參數(shù):
* Class:字節(jié)碼
* 它是用于指定被代理對(duì)象的字節(jié)碼。
*
* Callback:用于提供增強(qiáng)的代碼
* 它是讓我們寫(xiě)如何代理。我們一般都是些一個(gè)該接口的實(shí)現(xiàn)類,通常情況下都是匿名內(nèi)部類,但不是必須的。
* 此接口的實(shí)現(xiàn)類都是誰(shuí)用誰(shuí)寫(xiě)。
* 我們一般寫(xiě)的都是該接口的子接口實(shí)現(xiàn)類:MethodInterceptor
*/
Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 執(zhí)行producer的任何方法都會(huì)經(jīng)過(guò)該方法
* @param proxy
* @param method
* @param args
* 以上三個(gè)參數(shù)和基于接口的動(dòng)態(tài)代理中invoke方法的參數(shù)是一樣的
* @param methodProxy :當(dāng)前執(zhí)行方法的代理對(duì)象
* @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動(dòng)態(tài)代理示例
(1)創(chuàng)建一個(gè)接口
package com.gzl.proxy;
/**
* 對(duì)生產(chǎn)廠家要求的接口
*/
public interface IProducer {
/**
* 銷售
* @param money
*/
public void saleProduct(float money);
/**
* 售后
* @param money
*/
public void afterService(float money);
}
(2)實(shí)現(xiàn)類
package com.gzl.proxy;
/**
* 一個(gè)生產(chǎn)者
*/
public class Producer implements IProducer{
/**
* 銷售
* @param money
*/
public void saleProduct(float money){
System.out.println("銷售產(chǎn)品,并拿到錢(qián):"+money);
}
/**
* 售后
* @param money
*/
public void afterService(float money){
System.out.println("提供售后服務(wù),并拿到錢(qián):"+money);
}
}
(3)代理類
package com.gzl.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 模擬一個(gè)消費(fèi)者
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
/**
* 動(dòng)態(tài)代理:
* 特點(diǎn):字節(jié)碼隨用隨創(chuàng)建,隨用隨加載
* 作用:不修改源碼的基礎(chǔ)上對(duì)方法增強(qiáng)
* 分類:
* 基于接口的動(dòng)態(tài)代理
* 基于子類的動(dòng)態(tài)代理
* 基于接口的動(dòng)態(tài)代理:
* 涉及的類:Proxy
* 提供者:JDK官方
* 如何創(chuàng)建代理對(duì)象:
* 使用Proxy類中的newProxyInstance方法
* 創(chuàng)建代理對(duì)象的要求:
* 被代理類最少實(shí)現(xiàn)一個(gè)接口,如果沒(méi)有則不能使用
* newProxyInstance方法的參數(shù):
* ClassLoader:類加載器
* 它是用于加載代理對(duì)象字節(jié)碼的。和被代理對(duì)象使用相同的類加載器。固定寫(xiě)法。
* Class[]:字節(jié)碼數(shù)組
* 它是用于讓代理對(duì)象和被代理對(duì)象有相同方法。固定寫(xiě)法。
* InvocationHandler:用于提供增強(qiáng)的代碼
* 它是讓我們寫(xiě)如何代理。我們一般都是些一個(gè)該接口的實(shí)現(xiàn)類,通常情況下都是匿名內(nèi)部類,但不是必須的。
* 此接口的實(shí)現(xiàn)類都是誰(shuí)用誰(shuí)寫(xiě)。
*/
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 作用:執(zhí)行被代理對(duì)象的任何接口方法都會(huì)經(jīng)過(guò)該方法
* 方法參數(shù)的含義
* @param proxy 代理對(duì)象的引用
* @param method 當(dāng)前執(zhí)行的方法
* @param args 當(dāng)前執(zhí)行方法所需的參數(shù)
* @return 和被代理對(duì)象方法有相同的返回值
* @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中的動(dòng)態(tài)代理和靜態(tài)代理詳細(xì)解析的文章就介紹到這了,更多相關(guān)Java動(dòng)態(tài)代理和靜態(tài)代理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 淺析Java中靜態(tài)代理和動(dòng)態(tài)代理的應(yīng)用與區(qū)別
- 一文搞懂Java常見(jiàn)的三種代理模式(靜態(tài)代理、動(dòng)態(tài)代理和cglib代理)
- Java代理模式之靜態(tài)代理與動(dòng)態(tài)代理的區(qū)別及優(yōu)缺點(diǎn)
- 深入理解Java動(dòng)態(tài)代理與靜態(tài)代理
- Java的動(dòng)態(tài)代理和靜態(tài)代理詳解
- Java?靜態(tài)代理與動(dòng)態(tài)代理解析
- 代理模式:JAVA靜態(tài)代理和動(dòng)態(tài)代理的實(shí)例和實(shí)現(xiàn)詳解
相關(guān)文章
Java使用Sftp和Ftp實(shí)現(xiàn)對(duì)文件的上傳和下載
這篇文章主要介紹了Java使用Sftp和Ftp實(shí)現(xiàn)對(duì)文件的上傳和下載,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
簡(jiǎn)單捋捋@RequestParam 和 @RequestBody的使用
這篇文章主要介紹了簡(jiǎn)單捋捋@RequestParam 和 @RequestBody的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
在SpringBoot下讀取自定義properties配置文件的方法
這篇文章主要介紹了在SpringBoot下讀取自定義properties配置文件的方法,文中涉及到了Spring-boot中讀取config配置文件的兩種方式,需要的朋友可以參考下2017-12-12
Java 數(shù)組元素倒序的三種方式(小結(jié))
這篇文章主要介紹了Java 數(shù)組元素倒序的三種方式(小結(jié)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
ShardingSphere-Proxy5搭建使用過(guò)程分析
ShardingSphere-Proxy是跨語(yǔ)言的數(shù)據(jù)庫(kù)代理服務(wù)端,主要用來(lái)處理:分表、分庫(kù)、讀寫(xiě)分離 等,這篇文章主要介紹了ShardingSphere-Proxy5搭建使用過(guò)程,需要的朋友可以參考下2022-10-10
SpringCloud Ribbon負(fù)載均衡代碼實(shí)例
這篇文章主要介紹了SpringCloud Ribbon負(fù)載均衡代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
Java虛擬機(jī)類加載器之雙親委派機(jī)制模型案例
這篇文章主要介紹了Java虛擬機(jī)類加載器之雙親委派機(jī)制模型案例,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
SpringBoot中的五種對(duì)靜態(tài)資源的映射規(guī)則的實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot中的五種對(duì)靜態(tài)資源的映射規(guī)則的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
圖解Spring Security 中用戶是如何實(shí)現(xiàn)登錄的
這篇文章主要介紹了圖解Spring Security 中用戶是如何實(shí)現(xiàn)登錄的,文中通過(guò)示例代碼和圖片介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07

