一剖淀、概念
1、定義
代理模式給某一個對象提供一個代理對象,并由代理對象控制對原對象的引用却嗡。同時代理對象可以調(diào)用被代理對象的方法舶沛,并對其進行增強〈凹郏可以總結為代理對象 = 增強代碼 + 目標對象(原對象)
2如庭、舉例
疫情期間很多公司破產(chǎn),導致很多勞動者都失業(yè)了撼港,小文也是其中之一坪它。公司拖欠小文的工資一直未能下發(fā)。小文希望通過勞動仲裁來要回屬于自己的工資帝牡,所以和公司打官司往毡。基本的仲裁步驟小文都懂否灾,大概有:準備勞動仲裁申請書卖擅、收集證據(jù)、提交申請墨技、開庭答辯等等惩阶,但是小文是第一次進行勞動仲裁,對這些具體的操作沒什么經(jīng)驗扣汪,同時自己在這期間又有了新的工作断楷,很難抽出時間去進行勞動仲裁。這時小文就掏錢找了一位律師崭别,這位律師就相當于代理對象冬筒。律師替小文準備仲裁申請、提交申請茅主、開庭后幫小文進行辯論闡述等等舞痰,律師不僅僅是按著仲裁需要的步驟做了,而且每一步都做的比小文更好诀姚,這就是對被代理對象方法的增強
3响牛、場景
面向切面編程中就用到了代理模式,具體的內(nèi)容后面有機會再詳細介紹吧
4赫段、分類:
靜態(tài)代理: 在編譯時就已經(jīng)實現(xiàn)呀打,編譯完成后代理類是一個實際的class文件。
動態(tài)代理: 在運行時動態(tài)生成的糯笙,即編譯完成后沒有實際的class文件贬丛,而是在運行時動態(tài)生成類字節(jié)碼,并加載到JVM中给涕。
二豺憔、靜態(tài)代理
1额获、代碼
我們用靜態(tài)代理來實現(xiàn)上面例子
定義接口:無論是誰進行勞動仲裁,都需要按著這個流程執(zhí)行
public interface ArbitrationStep {
/**
* 準備仲裁申請
*/
void prepareApp();
/**
* 收集證據(jù)
*/
void collectEvi();
/**
* 開庭答辯
*/
void debate();
}
定義被代理類
public class XiaoWen implements ArbitrationStep {
@Override
public void prepareApp() {
System.out.println("準備仲裁申請焕阿!");
}
@Override
public void collectEvi() {
System.out.println("收集證據(jù)咪啡!");
}
@Override
public void debate() {
System.out.println("開庭答辯环础!");
}
}
定義代理類
可以看到代理類中不僅執(zhí)行了被代理的方法茂契,同時還對其方法進行了一定的增強
public class LawyerProxy implements ArbitrationStep {
private XiaoWen xiaoWen;
public LawyerProxy(XiaoWen xiaoWen) {
this.xiaoWen = xiaoWen;
}
@Override
public void prepareApp() {
System.out.println("律師聽取小文的想法");
xiaoWen.prepareApp();
System.out.println("律師對申請進行修改");
}
@Override
public void collectEvi() {
xiaoWen.collectEvi();
System.out.println("律師對證據(jù)進行整理");
}
@Override
public void debate() {
xiaoWen.debate();
System.out.println("律師更詳細的闡述");
}
}
測試類
public class Tribunal {
public static void main(String[] args) {
XiaoWen xiaoWen = new XiaoWen();
System.out.println("小文委托律師幫忙進行勞動仲裁");
LawyerProxy proxy = new LawyerProxy(xiaoWen);
System.out.println("-------第一步-------");
proxy.prepareApp();
System.out.println("-------第二步-------");
proxy.collectEvi();
System.out.println("-------第三步-------");
proxy.debate();
System.out.println();
System.out.println("仲裁結束");
}
}
運行結果
小文委托律師幫忙進行勞動仲裁
-------第一步-------
律師聽取小文的想法
準備仲裁申請!
律師對申請進行修改
-------第二步-------
收集證據(jù)缸匪!
律師對證據(jù)進行整理
-------第三步-------
開庭答辯褒纲!
律師更詳細的闡述
仲裁結束
2准夷、總結
通過上面的代碼可以總結出靜態(tài)代理的步驟
- 定義接口
-- 被代理類和代理類都需要實現(xiàn)該接口 - 定義被代理類
-- 實現(xiàn)上面定義的接口 - 定義代理類
-- 實現(xiàn)上面定義的接口
-- 創(chuàng)建被代理對象
-- 調(diào)用方法時需要調(diào)用被代理對象的方法,同時自己可以對方法進行增加
圖解
3莺掠、存在的問題
如果一個類需要被代理衫嵌,就得去創(chuàng)建一個代理類。如果被代理的類過多彻秆,這樣就需要手動創(chuàng)建很多代理類楔绞。為了解決這個問題,便有了動態(tài)代理
三唇兑、基于Java反射的動態(tài)代理
1酒朵、反射與類加載
在這之前我們需要先了解Java反射與類加載
同時還可以參考知乎回答——Java 動態(tài)代理作用是什么?
2扎附、使用反射實現(xiàn)動態(tài)代理
接著上面的勞動仲裁的例子蔫耽,不過這次我們用動態(tài)代理去實現(xiàn)
但是需要用到Proxy類的兩個靜態(tài)方法
- 獲得動態(tài)代理類Class對象
-- public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
-- 參數(shù)分別是類加載器和接口的Class對象;
-- 這個方法留夜, 會從你傳入的接口Class中匙铡,“拷貝”類結構信息到一個新的Class對象中,但新的Class對象帶有構造器碍粥,是可以創(chuàng)建對象的 - 獲得動態(tài)代理
-- public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
-- 封裝了得到代理類Class對象鳖眼、構造函數(shù)等細節(jié),直接返回了代理對象
接口和被代理類不變
public interface ArbitrationStep {
/**
* 準備仲裁申請
*/
void prepareApp();
/**
* 收集證據(jù)
*/
void collectEvi();
/**
* 開庭答辯
*/
void debate();
}
public class XiaoWen implements ArbitrationStep {
@Override
public void prepareApp() {
System.out.println("準備仲裁申請嚼摩!");
}
@Override
public void collectEvi() {
System.out.println("收集證據(jù)钦讳!");
}
@Override
public void debate() {
System.out.println("開庭答辯!");
}
}
通過反射獲得代理對象:
public class Tribunal2 {
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
// 獲取代理類的Class對象
Class<?> proxyClazz = Proxy.getProxyClass(ArbitrationStep.class.getClassLoader(), ArbitrationStep.class);
// 獲得構造函數(shù)
Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);
// 創(chuàng)建代理對象
ArbitrationStep lawyerProxy = (ArbitrationStep) constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 創(chuàng)建被代理對象低斋,用于調(diào)用方法
XiaoWen xiaoWen = new XiaoWen();
// 通過invoke方法調(diào)用了被代理對象的方法
Object invoke = method.invoke(xiaoWen, args);
// 每個步驟都讓律師幫忙處理
System.out.println("律師幫忙處理!");
return invoke;
}
});
// 通過代理對象調(diào)用方法
System.out.println("-------第一步-------");
lawyerProxy.prepareApp();
System.out.println("-------第二步-------");
lawyerProxy.collectEvi();
System.out.println("-------第三步-------");
lawyerProxy.debate();
System.out.println();
System.out.println("仲裁結束");
}
}
運行結果
-------第一步-------
準備仲裁申請匪凡!
律師幫忙處理膊畴!
-------第二步-------
收集證據(jù)!
律師幫忙處理病游!
-------第三步-------
開庭答辯唇跨!
律師幫忙處理稠通!
仲裁結束
稍微封裝一下獲得代理對象的方法:
public class Tribunal3 {
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
XiaoWen xiaoWen = new XiaoWen();
// 獲得代理對象
ArbitrationStep lawyerProxy = (ArbitrationStep) getProxy(xiaoWen);
// 通過代理對象調(diào)用方法
System.out.println("-------第一步-------");
lawyerProxy.prepareApp();
System.out.println("-------第二步-------");
lawyerProxy.collectEvi();
System.out.println("-------第三步-------");
lawyerProxy.debate();
System.out.println();
System.out.println("仲裁結束");
}
// 該方法返回一個代理對象
public static Object getProxy(Object target) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// target為被代理對象,得到其代理類的Class對象
Class<?> proxyClazz = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());
// 獲得構造函數(shù)
Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);
// 獲得代理對象
Object targetProxy = constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = method.invoke(target, args);
System.out.println("代理類增強方法");
return invoke;
}
});
return targetProxy;
}
}
自己編寫了一個getProxy方法买猖,傳入被代理的對象改橘,返回一個代理對象。
通過newProxyInstance()獲得代理對象
上面的getProxy方法返回代理對象的過程是我們自己寫的玉控,通過Proxy類的newProxyInstance()可以直接返回代理對象
public static Object getProxyByProxyMethod(Object target) {
// 直接返回代理對象
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = method.invoke(target, args);
System.out.println("代理對象增強方法");
return invoke;
}
});
}
看看newProxyInstance幫我們做了些什么工作
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
// 下面這一段其實就是getProxyClass方法的內(nèi)部實現(xiàn)
// --------------------------------------------------------------
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
// 獲得接口加上構造函數(shù)后的Class對象
Class<?> cl = getProxyClass0(loader, intfs);
// ----------------------------------------------------------------
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// 獲得構造函數(shù)
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 返回代理對象
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
我們自己獲取代理對象的步驟是
- 通過getProxyClass獲得代理對象的Class對象(接口+對應的構造函數(shù))
- 通過Class對象調(diào)用得到構造方法
- 構造方法去創(chuàng)建實例飞主,構造方法傳入InvocationHandler實例,需要實現(xiàn)其invoke方法
newProxyInstance幫我們獲取代理的步驟和上面類似高诺,只不過Class對象是直接通過getProxyClass0(loader, intfs)來獲取的碌识。而我們自己封裝的代碼中,使用的是getProxyClass方法虱而。但是該方法最終還是調(diào)用的getProxyClass0(loader, intfs)來獲取的Class對象
getProxyClass方法
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
{
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
return getProxyClass0(loader, intfs);
}
3筏餐、如何實現(xiàn)方法增強
上面的例子確實將被代理對象的方法增強了,但這是如何實現(xiàn)的呢
InvocationHandler
我們在獲得代理類構造器的時候牡拇,傳入了InvocationHandler
Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);
InvocationHandler是一個接口魁瞪,內(nèi)部只有一個方法invoke
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
在通過constructor獲取代理對象時,newInstance方法需要傳入一個invocationHandler的實例惠呼,這個實例重寫了invoke方法导俘,就是通過它代理對象增強了被代理對象的方法
Object targetProxy = constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 調(diào)用被代理對象的對應方法
// target 被代理對象
// args 方法需要的參數(shù)
Object invoke = method.invoke(target, args);
System.out.println("代理類增強方法");
return invoke;
}
});
return targetProxy;
}
圖解**
總結
調(diào)用代理對象的方法時,實際上調(diào)用的是InvocationHandler的invoke方法罢杉,這個方法內(nèi)部不僅調(diào)用了被代理對象的方法趟畏,還可以增加其他功能
4、為什么必須實現(xiàn)接口
通過
lawyerProxy.getClass().getSuperclass()
可以獲得代理對象的父類滩租,可以看到其父類為:
class java.lang.reflect.Proxy
也就是無論是什么代理類赋秀,都會繼承自Proxy,而Java又是單繼承的律想,所以想要被代理對象與代理對象產(chǎn)生聯(lián)系猎莲,就只能通過接口來實現(xiàn)了
四、基于CGLib的動態(tài)代理
Java動態(tài)代理是面向接口的代理模式技即,如果沒有接口著洼,但是想要去實現(xiàn)動態(tài)代理,就需要用到CGLib來進行動態(tài)代理了
CGLIB原理
cglib是一個java字節(jié)碼的生成工具而叼,它動態(tài)生成一個被代理類的子類身笤,子類重寫被代理的類的所有不是final的方法。在子類中采用方法攔截的技術攔截所有父類方法的調(diào)用葵陵,順勢織入橫切邏輯液荸。
示例
被代理類:
public class HelloServiceImpl {
public void sayHello(){
System.out.println("Hello Zhanghao");
}
public void sayBey(){
System.out.println("Bye Zhanghao");
}
}
實現(xiàn)MethodInterceptor接口生成方法攔截器:
public class HelloMethodInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("Before: " + method.getName());
Object object = methodProxy.invokeSuper(o, objects);
System.out.println("After: " + method.getName());
return object;
}
}
生成代理類對象并打印在代理類對象調(diào)用方法之后的執(zhí)行結果:
public class Client {
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/zhanghao/Documents/toy/spring-framework-source-study/");
Enhancer enhancer = new Enhancer();
//繼承被代理類
enhancer.setSuperclass(HelloServiceImpl.class);
//設置回調(diào)
enhancer.setCallback(new HelloMethodInterceptor());
//設置代理類對象
HelloServiceImpl helloService = (HelloServiceImpl) enhancer.create();
//在調(diào)用代理類中方法時會被我們實現(xiàn)的方法攔截器進行攔截
helloService.sayBey();
}
}
result:
Before: sayBey
Bye Zhanghao
After: sayBey
構建代理類過程
我們可以從上面的代碼示例中看到,代理類是由enhancer.create()創(chuàng)建的脱篙。Enhancer是CGLIB的字節(jié)碼增強器娇钱,可以很方便的對類進行拓展伤柄。
創(chuàng)建代理類的過程:
- 生成代理類的二進制字節(jié)碼文件;
- 加載二進制字節(jié)碼文搂,生成Class對象适刀;
- 通過反射機制獲得實例構造,并創(chuàng)建代理類對象煤蹭。
enhancer.create()實現(xiàn):
/**
* Generate a new class if necessary and uses the specified
* callbacks (if any) to create a new object instance.
* Uses the no-arg constructor of the superclass.
* @return a new instance
*/
public Object create() {
classOnly = false;
argumentTypes = null;
return createHelper();
}
private Object createHelper() {
validate();
if (superclass != null) {
//設置生成類的名稱
setNamePrefix(superclass.getName());
} else if (interfaces != null) {
//設置生成類的名稱
setNamePrefix(interfaces[ReflectUtils.findPackageProtected(interfaces)].getName());
}
//生成代理類對象(在KEY_FACTORY.newInstance(...)->生成代理類的二進制字節(jié)碼文件以及加載二進制字節(jié)碼)
return super.create(KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
ReflectUtils.getNames(interfaces),
filter,
callbackTypes,
useFactory,
interceptDuringConstruction,
serialVersionUID));
}
cglic一共會自動生成三個字節(jié)碼文件笔喉。其中一個類HelloServiceImpl
EnhancerByCGLIB
d855d4dc 繼承了被代理類 HelloServiceImpl。這個類就是加強的代理類疯兼,其中會生成兩個方法CGLIB sayHello1和sayHello()然遏。
其中sayHello():
public final void sayHello() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if(this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if(var10000 != null) {
var10000.intercept(this, CGLIB$sayHello$1$Method, CGLIB$emptyArgs, CGLIB$sayHello$1$Proxy);
} else {
super.sayHello();
}
}
當代理對象的執(zhí)行sayHello方法時,會首先判斷一下是否存在實現(xiàn)了MethodInterceptor接口的CGLIB$CALLBACK_0;吧彪,如果存在待侵,則將調(diào)用MethodInterceptor中的intercept方法。
與JDK代理對比
JDK代理要求被代理的類必須實現(xiàn)接口姨裸,有很強的局限性秧倾。而CGLIB動態(tài)代理則沒有此類強制性要求。簡單的說傀缩,CGLIB會讓生成的代理類繼承被代理類那先,并在代理類中對代理方法進行強化處理(前置處理、后置處理等)赡艰。但是如果被代理類被final修飾售淡,那么它不可被繼承,即不可被代理慷垮;同樣揖闸,如果被代理類中存在final修飾的方法,那么該方法也不可被代理料身。