靜態代理、動態代理,以及動態代理的呼叫說明
提前說說
專案中涉及到的程式碼我都會上傳到碼雲(gitee)或者github上,提供給大家下載參考,文中就以最簡單的方式說明執行過程。原始碼的地址在文末哦!
代理模式
代理模式分為靜態代理和動態代理兩種方式,靜態代理是在開發的時候就寫好代理的過程,並且代理類要和目標類實現同一個介面。而動態代理是代理類通過實現InvocationHandler介面完成,在執行期間動態的構建代理物件,在動態代理的實現過程中還有另一個更為重要的類Proxy,準確的來說,Proxy負責生成代理物件,而InvocationHandler是根據生成的代理物件執行增強內容和目標方法或類。
靜態代理
要點:
1、代理類需要和目標類需要實現同一個介面
2、在代理類中會內建目標類物件
程式碼分析
建立一個介面Transformers(變形金剛)
public interface Transformers {
void trans2man();//變形->人
void trans2car();//變形->車
}
Transformers的實現類TransformersImpl,可以理解為擎天柱
擎天柱實現了變形金剛介面,擁有兩個功能分別是變形成人、變形成車。
public class TransformersImpl implements Transformers {
@Override
public void trans2man() {
System.out.println("---->transform to man");
}
@Override
public void trans2car() {
System.out.println("---->transform to car");
}
}
代理類TransformersProxy,和TransformersImpl一樣都要實現Transformers介面
public class TransformersProxy implements Transformers {
public Transformers transformers;
public void init(Object transformers) { //初始化
this.transformers = (Transformers) transformers;
}
@Override
public void trans2man() {
System.out.println("---->transform to man before");
transformers.trans2man();
}
@Override
public void trans2car() {
System.out.println("---->transform to car before");
transformers.trans2car();
}
}
執行測試方法,測試代理過程
//靜態代理方式
@Test
public void staticProxyTest() {
System.out.println("=========static proxy test start=========");
TransformersProxy proxy = new TransformersProxy();
proxy.init(new TransformersImpl());
proxy.trans2man();
System.out.println("~~~~~~~~~華麗分隔線~~~~~~~~~~");
proxy.trans2car();
System.out.println("=========static proxy test end=========");
}
執行結果:
=========static proxy test start=========
---->transform to man before
---->transform to man
~~~~~~~~~華麗分隔線~~~~~~~~~~
---->transform to car before
---->transform to car
=========static proxy test end=========
先執行代理類中的邏輯,再執行目標方法,這樣就完成了代理的過程。如果現在又有一個變形金剛大黃蜂實現了Transformers類,它的增強內容和擎天柱是相同,這個時候代理類中的增強程式碼就可複用,只要在初始化代理物件的時候,傳入大黃蜂實現類物件即可。
缺點:
隨著專案的迭代升級,代理的目標增多,代理的增強內容變多(可能不同的實現類需要增強的內容不同),代理類也會越來越龐大,對整個維護過程也會變得複雜。
動態代理
動態代理用的很是廣泛,如面試必問、專案必用的AOP,心中一陣絞痛,想當年因為這問題也在面試中被虐的體無完膚。具體的AOP原始碼拋開暫時不看,我們所知道的就是它的重要一個實現機制就是動態代理,那就從最基礎的瞭解動態代理的實現。
程式碼分析
建立變形金剛介面TransformersDynamic
public interface TransformersDynamic {
void trans2man();//變形->人
void trans2car();//變形->車
}
建立擎天柱TransformersDynamicImpl,實現變形金剛介面
public class TransformersDynamicImpl implements TransformersDynamic {
@Override
public void trans2man() {
System.out.println("---->transform to man");
}
@Override
public void trans2car() {
System.out.println("---->transform to car");
}
}
建立代理類TransformersDynamicProxy,實現InvocationHandler介面
public class TransformersDynamicProxy implements InvocationHandler {
private Object proxyObject;
public TransformersDynamicProxy(Object proxyObject){
this.proxyObject = proxyObject;
}
/**
* 獲取代理物件
*/
public Object newProxyInstance() {
return Proxy.newProxyInstance(proxyObject.getClass().getClassLoader(),
proxyObject.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("---->dynamic invoke before");
method.invoke(proxyObject, args); //執行目標方法
System.out.println("---->dynamic invoke after");
return null;
}
}
說明:實現InvocationHandler介面後實現invoke介面,這個介面中就定義了這一個介面,原始碼:
/**
* @author Peter Jones
* @see Proxy
* @since 1.3
*/
public interface InvocationHandler {
/**
* 此處省略100000行註釋……
* @see UndeclaredThrowableException
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
proxy:指我們所代理的真實物件
method:指的是我們所要呼叫真實物件的某個方法Method物件
args:指呼叫真實物件某個方法時接受的引數
在newProxyInstance方法內,通過Proxy類生成代理物件,第二個引數Interfaces,是獲取當前目標類實現的所有介面。
執行測試方法,測試代理過程
//動態代理方式
@Test
public void dynamicProxyTest() {
System.out.println("=========dynamic proxy test start=========");
TransformersDynamicProxy dynamicProxy = new TransformersDynamicProxy(new TransformersDynamicImpl());
TransformersDynamic proxy = (TransformersDynamic) dynamicProxy.newProxyInstance();
System.out.println("----->" + proxy.getClass());
proxy.trans2man();
System.out.println("~~~~~~~~~華麗分隔線~~~~~~~~~~");
proxy.trans2car();
System.out.println("=========dynamic proxy test end=========");
}
執行結果:
=========dynamic proxy test start=========
----->class com.sun.proxy.$Proxy11
---->dynamic invoke before
---->transform to man
---->dynamic invoke after
~~~~~~~~~華麗分隔線~~~~~~~~~~
---->dynamic invoke before
---->transform to car
---->dynamic invoke after
=========dynamic proxy test end=========
列印的proxy可以看出,這個物件並不是在建立時傳入的TransformersDynamicImpl物件,而是通過Proxy生成的動態代理物件。
到這裡靜態代理和動態代理的最基本原理已經說完了。但是這裡還是需要說點其他的。
動態代理的坑
上面動態代理可以看出來,trans2man和trans2car都會目標方法,在執行的時候都會執行before和after,但是下面這個演示,你將看到不一樣的結果。
程式碼的改動
1、將dynamicProxyTest方法改成如下內容:
//動態代理方式
@Test
public void dynamicProxyTest() {
System.out.println("=========dynamic proxy test start=========");
TransformersDynamicProxy dynamicProxy = new TransformersDynamicProxy(new TransformersDynamicImpl());
TransformersDynamic proxy = (TransformersDynamic) dynamicProxy.newProxyInstance();
System.out.println("----->" + proxy.getClass());
proxy.trans2man();
System.out.println("=========dynamic proxy test end=========");
}
2、在TransformersDynamicImpl類的trans2man方法中呼叫trans2car方法:
@Override
public void trans2man() {
System.out.println("---->transform to man");
trans2car();
}
執行結果:
=========dynamic proxy test start=========
----->class com.sun.proxy.$Proxy11
---->dynamic invoke before
---->transform to man
---->transform to car
---->dynamic invoke after
=========dynamic proxy test end=========
從結果分析可以看出來,在輸出transform to car前後少了一對before和after,也就意味著這個時候trans2car沒有被增強,為什麼呢,trans2car是被增強的啊。
這裡需要理解的是,在trans2man中呼叫trans2car方法前面還隱含著一個呼叫物件,補全就是this.trans2car(),也就是當前物件呼叫的trans2car方法,並不是代理物件呼叫,那就肯定沒有增強邏輯的執行了。代理被繞過,沒有生效。在這裡有這個問題,那麼對於Spring AOP的動態代理有沒有問題呢?
原始碼提供
具體的程式碼是在com.minuor.staticProxy包(靜態代理示例)、com.minuor.dynamicProxy包(動態代理示例),測試類在test下com.minuor.MinuorJunitService。
gitHub:https://github.com/minuor/proxy
碼雲:https://gitee.com/minuor/proxy