詳解JAVA動(dòng)態(tài)代理
文檔更新說(shuō)明
2018年09月24日 v1.0 初稿
代理在生活中很常見(jiàn),比如說(shuō)婚介網(wǎng)站,其實(shí)就是找對(duì)象的代理;還有社保代理、人事代理;還有找黃牛搶票,其實(shí)也是一種代理;而這些代理,在JAVA中也是有對(duì)應(yīng)實(shí)現(xiàn)的。
1、為什么要?jiǎng)討B(tài)代理
動(dòng)態(tài)代理的作用其實(shí)就是在不修改原代碼的前提下,對(duì)已有的方法進(jìn)行增強(qiáng)。
關(guān)鍵點(diǎn):
不修改原來(lái)已有的代碼(滿足設(shè)計(jì)模式的要求)
對(duì)已有方法進(jìn)行增強(qiáng)
2、舉個(gè)栗子
我們用一個(gè)很簡(jiǎn)單的例子來(lái)說(shuō)明:Hello類,有一個(gè)introduction方法。
現(xiàn)在我們的需求就是不修改Hello類的introduction方法,在introduction之前先sayHello,在introduction之后再sayGoodBye
3、實(shí)現(xiàn)方式
JAVA中,實(shí)現(xiàn)動(dòng)態(tài)代理有兩種方式,一種是JDK提供的,一種是第三方庫(kù)CgLib提供的。特點(diǎn)如下:
JDK動(dòng)態(tài)代理:被代理的目標(biāo)類需要實(shí)現(xiàn)接口
CgLib方式:可以對(duì)任意類實(shí)現(xiàn)動(dòng)態(tài)代理
3.1、JDK動(dòng)態(tài)代理
JDK動(dòng)態(tài)代理需要實(shí)現(xiàn)接口,然后通過(guò)對(duì)接口方法的增強(qiáng)來(lái)實(shí)現(xiàn)動(dòng)態(tài)代理
所以要使用JDK動(dòng)態(tài)代理的話,我們首先要?jiǎng)?chuàng)建一個(gè)接口,并且被代理的方法要在這個(gè)接口里面
3.1.1、創(chuàng)建一個(gè)接口
我們創(chuàng)建一個(gè)接口如下:
Personal.java
public interface Personal {
/**
* 被代理的方法
*/
void introduction();
}
3.1.2、實(shí)現(xiàn)接口
創(chuàng)建接口實(shí)現(xiàn)類,并且完成introduction方法
PersonalImpl.java
public class PersonalImpl implements Personal {
@Override
public void introduction() {
System.out.println("我是程序員!");
}
}
3.1.3、創(chuàng)建代理類
JDK代理的關(guān)鍵就是這個(gè)代理類了,需要實(shí)現(xiàn)InvocationHandler
在代理類中,所有方法的調(diào)用都好分發(fā)到invoke方法中。我們?cè)?code>invoke方法完成對(duì)方法的增強(qiáng)即可
JDKProxyFactory.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDKProxyFactory<T> implements InvocationHandler {
/**
* 目標(biāo)對(duì)象
*/
private T target;
/**
* 構(gòu)造函數(shù)傳入目標(biāo)對(duì)象
*
* @param target 目標(biāo)對(duì)象
*/
public JDKProxyFactory(T target) {
this.target = target;
}
/**
* 獲取代理對(duì)象
*
* @return 獲取代理
*/
public T getProxy() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 對(duì)方法增強(qiáng)
System.out.println("大家好!");
// 調(diào)用原方法
Object result = method.invoke(target, args);
// 方法增強(qiáng)
System.out.println("再見(jiàn)!");
return result;
}
}
就這樣,JDK動(dòng)態(tài)代理的代碼就完成了,接下來(lái)寫一份測(cè)試代碼
3.1.4、編寫測(cè)試代碼
為了方便測(cè)試,我們編寫一個(gè)test方法
同時(shí)為了查看class文件,還添加了一個(gè)generatorClass方法,這個(gè)方法可以將動(dòng)態(tài)代理生成的.class輸出到文件
ProxyTest.java
import org.junit.Test;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
public class ProxyTest {
@Test
public void testJdkProxy() {
// 生成目標(biāo)對(duì)象
Personal personal = new PersonalImpl();
// 獲取代理對(duì)象
JDKProxyFactory<Personal> proxyFactory = new JDKProxyFactory<>(personal);
Personal proxy = proxyFactory.getProxy();
// 將proxy的class字節(jié)碼輸出到文件
generatorClass(proxy);
// 調(diào)用代理對(duì)象
proxy.introduction();
}
/**
* 將對(duì)象的class字節(jié)碼輸出到文件
*
* @param proxy 代理類
*/
private void generatorClass(Object proxy) {
FileOutputStream out = null;
try {
byte[] generateProxyClass = ProxyGenerator.generateProxyClass(proxy.getClass().getSimpleName(), new Class[]{proxy.getClass()});
out = new FileOutputStream(proxy.getClass().getSimpleName() + ".class");
out.write(generateProxyClass);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
}
}
}
}
}
3.1.5、查看運(yùn)行結(jié)果
可以看到,運(yùn)行test方法之后,控制臺(tái)打印出如下:
大家好!
我是程序員!
再見(jiàn)!
我們?cè)?code>introduction方法前和后都成功增加了功能,讓這個(gè)程序員的自我介紹瞬間變得更加有禮貌了。
3.1.6、探探動(dòng)態(tài)代理的秘密
動(dòng)態(tài)代理的代碼并不多,那么JDK底層是怎么幫我們實(shí)現(xiàn)的呢?
在測(cè)試的時(shí)候我們將動(dòng)態(tài)生成的代理類的class字節(jié)碼輸出到了文件,我們可以反編譯看看。
結(jié)果有點(diǎn)長(zhǎng),就不全部貼出來(lái)了,不過(guò)我們可以看到,里面有一個(gè)introduction方法如下:
/**
* the invocation handler for this proxy instance.
* @serial
*/
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
public final void introduction() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
原來(lái),生成的代理對(duì)象里面,引用了我們的InvocationHandler,然后在將introduction方法里面調(diào)用了InvocationHandler的introduction,而InvocationHandler是由我們編寫的代理類,在這里我們?cè)黾恿?code>sayHello和sayGoodBye操作,然后還調(diào)用了原對(duì)象的introduction方法,就這樣完成了動(dòng)態(tài)代理。
3.2、CgLib動(dòng)態(tài)代理
CgLib動(dòng)態(tài)
3.2.1、創(chuàng)建被代理對(duì)象
由于CgLib不需要實(shí)現(xiàn)接口,所以我們不需要?jiǎng)?chuàng)建接口文件了(當(dāng)然,你要有接口也沒(méi)有問(wèn)題)
直接創(chuàng)建目標(biāo)類,實(shí)現(xiàn)introduction方法
PersonalImpl.java
public class PersonalImpl {
public void introduction() {
System.out.println("我是程序員!");
}
}
3.2.2、創(chuàng)建代理類
同樣,我們也需要?jiǎng)?chuàng)建代理類,并且在這里實(shí)現(xiàn)增強(qiáng)的邏輯,這次我們不是實(shí)現(xiàn)InvocationHandler接口了,而是實(shí)現(xiàn)CgLib提供的接口MethodInterceptor,都是類似的,MethodInterceptor中,全部方法調(diào)用都會(huì)交給intercept處理,我們?cè)?code>intercept添加處理邏輯即可。
CgLibProxyFactory.java
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 CgLibProxyFactory<T> implements MethodInterceptor {
/**
* 獲取代理對(duì)象
*
* @param tClass 被代理的目標(biāo)對(duì)象
* @return 代理對(duì)象
*/
public T getProxyByCgLib(Class<T> tClass) {
// 創(chuàng)建增強(qiáng)器
Enhancer enhancer = new Enhancer();
// 設(shè)置需要增強(qiáng)的類的類對(duì)象
enhancer.setSuperclass(tClass);
// 設(shè)置回調(diào)函數(shù)
enhancer.setCallback(this);
// 獲取增強(qiáng)之后的代理對(duì)象
return (T) enhancer.create();
}
/**
* 代理類方法調(diào)用回調(diào)
*
* @param obj 這是代理對(duì)象,也就是[目標(biāo)對(duì)象]的子類
* @param method [目標(biāo)對(duì)象]的方法
* @param args 參數(shù)
* @param proxy 代理對(duì)象的方法
* @return 返回結(jié)果,返回給調(diào)用者
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("大家好!");
Object result = proxy.invokeSuper(obj, args);
System.out.println("再見(jiàn)!");
return result;
}
}
3.2.3、編寫測(cè)試代碼
在剛才的測(cè)試方法中,我們添加一個(gè)cglib的測(cè)試方法:
@Test
public void testCgLibProxy() {
// 生成被代理的目標(biāo)對(duì)象
PersonalImpl personal = new PersonalImpl();
// 獲取代理類
CgLibProxyFactory<PersonalImpl> proxyFactory = new CgLibProxyFactory<>();
PersonalImpl proxy = proxyFactory.getProxyByCgLib((Class<PersonalImpl>) personal.getClass());
// 將proxy的class字節(jié)碼輸出到文件
generatorClass(proxy);
// 調(diào)用代理對(duì)象
proxy.introduction();
}
3.2.4、查看運(yùn)行結(jié)果
運(yùn)行測(cè)試用例,可以看到跟JDK的實(shí)現(xiàn)一樣的效果
大家好!
我是程序員!
再見(jiàn)!
3.2.5、探探動(dòng)態(tài)代理的秘密
跟JDK的測(cè)試一樣,我們也來(lái)看看生成的class文件
public final void introduction() throws {
try {
super.h.invoke(this, m7, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
可以發(fā)現(xiàn),與JDK的動(dòng)態(tài)代理并沒(méi)有區(qū)別。
4、如何選擇
既然有兩種實(shí)現(xiàn)方式,那么到底應(yīng)該怎么選擇呢?
就兩個(gè)原則:
目標(biāo)類有接口實(shí)現(xiàn)的,JDK和CgLib都可以選擇,你開(kāi)心就好
目標(biāo)類沒(méi)有實(shí)現(xiàn)任何接口,那只能用CgLib了
5、后記
其實(shí)在第一次看到動(dòng)態(tài)代理的時(shí)候,我就想不明白,我們都把目標(biāo)類new出來(lái)了,為什么還要將目標(biāo)類丟給代理類呢?為什么不直接調(diào)用目標(biāo)類對(duì)應(yīng)的方法呢?
后來(lái)才發(fā)現(xiàn),原來(lái)我沒(méi)搞清楚動(dòng)態(tài)代理的使用場(chǎng)景,場(chǎng)景很清晰,就是:
不修改原來(lái)已有的代碼(滿足設(shè)計(jì)模式的要求)
對(duì)已有方法進(jìn)行增強(qiáng)
關(guān)鍵是增強(qiáng),代理類里面我們是可以添加很多處理邏輯的,從而實(shí)現(xiàn)增強(qiáng)效果。就像黃牛搶票比我們厲害些一樣。
以上所述是小編給大家介紹的JAVA動(dòng)態(tài)代理詳解整合,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- 詳解Java JDK動(dòng)態(tài)代理
- 詳解Java Cglib動(dòng)態(tài)代理
- Java簡(jiǎn)單實(shí)現(xiàn)動(dòng)態(tài)代理模式過(guò)程解析
- Java JDK動(dòng)態(tài)代理實(shí)現(xiàn)原理實(shí)例解析
- 詳細(xì)分析java 動(dòng)態(tài)代理
- Java使用JDK與Cglib動(dòng)態(tài)代理技術(shù)統(tǒng)一管理日志記錄
- Java動(dòng)態(tài)代理語(yǔ)法Proxy類原理詳解
- Java代理模式實(shí)例詳解【靜態(tài)代理與動(dòng)態(tài)代理】
- JAVA使用動(dòng)態(tài)代理對(duì)象進(jìn)行敏感字過(guò)濾代碼實(shí)例
- Java動(dòng)態(tài)代理和反射機(jī)制詳解
- Java動(dòng)態(tài)代理的兩種實(shí)現(xiàn)方式詳解【附相關(guān)jar文件下載】
- Java兩種方式實(shí)現(xiàn)動(dòng)態(tài)代理
相關(guān)文章
java 輸入3個(gè)數(shù)a,b,c,按大小順序輸出的實(shí)例講解
今天小編就為大家分享一篇java 輸入3個(gè)數(shù)a,b,c,按大小順序輸出的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-07-07
Java雙向鏈表按照順序添加節(jié)點(diǎn)的方法實(shí)例
這篇文章主要給大家介紹了關(guān)于Java雙向鏈表按照順序添加節(jié)點(diǎn)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02
從零開(kāi)始學(xué)Java之關(guān)系運(yùn)算符
今天帶大家復(fù)習(xí)Java關(guān)系運(yùn)算符,文中對(duì)Java運(yùn)算符相關(guān)知識(shí)作了詳細(xì)總結(jié),對(duì)正在學(xué)習(xí)java基礎(chǔ)的小伙伴們很有幫助,需要的朋友可以參考下2021-08-08
Java爬蟲范例之使用Htmlunit爬取學(xué)校教務(wù)網(wǎng)課程表信息
htmlunit 是一款開(kāi)源的java 頁(yè)面分析工具,讀取頁(yè)面后,可以有效的使用htmlunit分析頁(yè)面上的內(nèi)容。項(xiàng)目可以模擬瀏覽器運(yùn)行,被譽(yù)為java瀏覽器的開(kāi)源實(shí)現(xiàn)。今天我們用這款分析工具來(lái)爬取學(xué)校教務(wù)網(wǎng)課程表信息2021-11-11
Java Synchronized鎖升級(jí)原理及過(guò)程剖析
這篇文章主要為大家詳細(xì)介紹一下Java中Synchronized鎖升級(jí)原理及過(guò)程,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)學(xué)習(xí)2022-08-08
spring-security關(guān)于hasRole的坑及解決
這篇文章主要介紹了spring-security關(guān)于hasRole的坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09
mybatis條件語(yǔ)句中帶數(shù)組參數(shù)的處理
這篇文章主要介紹了mybatis條件語(yǔ)句中帶數(shù)組參數(shù)的處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09

