我們知道,Spring 中 AOP 是一大核心技術(shù),也是面試中經(jīng)常會(huì)被問到的問題,最近我在網(wǎng)上也看到很多面試題,其中和 Spring AOP 相關(guān)的就有不少,這篇文章主要來總結(jié)下相關(guān)的技術(shù)點(diǎn),希望對(duì)大家有用。
- 幾個(gè)常見的問題
針對(duì)這一塊的東西,一般下面幾個(gè)問題面試官問的比較多:
Spring AOP用的是哪種設(shè)計(jì)模式?
談?wù)勀銓?duì)代理模式的理解?
靜態(tài)代理和動(dòng)態(tài)代理有什么區(qū)別?
如何實(shí)現(xiàn)動(dòng)態(tài)代理?
Spring AOP中用的是哪種代理技術(shù)?
如果這些問題都能回答的很流暢的話,說明對(duì)代理這一塊的基本知識(shí)有一定的了解了。因?yàn)槲覀冊(cè)趯?shí)際開發(fā)中,寫業(yè)務(wù)代碼會(huì)更多,所以這一塊的東西,大部分人可能知道個(gè)一二,但是如果讓他們很有條理的表達(dá)出來,可能就不那么容易了。
- 什么是 Spring AOP?
一般面試官問到這個(gè)問題,面試者基本上都會(huì)回答:AOP 就是面向切面編程。其實(shí)這真的是句廢話,這么回答真的沒有任何意義。
或許你可以給面試官舉個(gè)例子:歌星都有好多助理,歌星最重要的一件事就是唱歌,其他事他不用關(guān)注,比如唱歌前可能需要和其他人談合作,還要布置場(chǎng)地,唱歌后還要收錢等等,這些統(tǒng)統(tǒng)交給他對(duì)應(yīng)的助理去做。也許哪一天,這個(gè)歌星做慈善,免費(fèi)唱歌了,不收錢了,那么就可以把收錢這個(gè)助力給辭退了。這就是 AOP,每個(gè)人各司其職,靈活組合,達(dá)到一種可配置的、可插拔的程序結(jié)構(gòu)。AOP 的實(shí)現(xiàn)原理就是代理模式。
在程序中也是如此,通過代理,可以詳細(xì)控制訪問某個(gè)或者某類對(duì)象的方法,在調(diào)用這個(gè)方法前做前置處理,調(diào)用這個(gè)方法后做后置處理。
- 什么是代理模式?
代理模式的核心作用就是通過代理,控制對(duì)對(duì)象的訪問。它的設(shè)計(jì)思路是:定義一個(gè)抽象角色,讓代理角色和真實(shí)角色分別去實(shí)現(xiàn)它。
真實(shí)角色:實(shí)現(xiàn)抽象角色,定義真實(shí)角色所要實(shí)現(xiàn)的業(yè)務(wù)邏輯,供代理角色調(diào)用。它只關(guān)注真正的業(yè)務(wù)邏輯,比如歌星唱歌。
代理角色:實(shí)現(xiàn)抽象角色,是真實(shí)角色的代理,通過真實(shí)角色的業(yè)務(wù)邏輯方法來實(shí)現(xiàn)抽象方法,并在前后可以附加自己的操作,比如談合同,布置場(chǎng)地,收錢等等。
這就是代理模式的設(shè)計(jì)思路。代理模式分為靜態(tài)代理和動(dòng)態(tài)代理。靜態(tài)代理是我們自己創(chuàng)建一個(gè)代理類,而動(dòng)態(tài)代理是程序自動(dòng)幫我們生成一個(gè)代理,我們就不用管了。下面我詳細(xì)介紹一下這兩種代理模式。
- 靜態(tài)代理模式
就舉明星唱歌這個(gè)例子,根據(jù)上面提供的設(shè)計(jì)思路,首先我們需要?jiǎng)?chuàng)建明星這個(gè)抽象角色,
/**
明星接口類
@author shengwu ni
@date 2018-12-07
*/
public interface Star {
/**
* 唱歌方法
*/
void sing();
}
靜態(tài)代理需要?jiǎng)?chuàng)建真實(shí)角色和代理角色,分別實(shí)現(xiàn)唱歌這個(gè)接口,真實(shí)角色很簡單,直接實(shí)現(xiàn)即可,因?yàn)檎鎸?shí)角色的主要任務(wù)就是唱歌。
/**
真實(shí)明星類
@author shengwu ni
-
@date 2018-12-08
*/
public class RealStar implements Star {@Override
public void sing() {
System.out.println("明星本人開始唱歌……");
}
}
代理類就需要做點(diǎn)工作了,我們思考一下,代理只是在明星唱歌前后做一些準(zhǔn)備和收尾的事,唱歌這件事還得明星親自上陣,代理做不了。所以代理類里面是肯定要將真實(shí)的對(duì)象傳進(jìn)來。有了思路,我們將代理類寫出來。
/**
明星的靜態(tài)代理類
@author shengwu ni
-
@date 2018-12-08
*/
public class ProxyStar implements Star {/**
- 接收真實(shí)的明星對(duì)象
*/
private Star star;
/**
- 通過構(gòu)造方法傳進(jìn)來真實(shí)的明星對(duì)象
- @param star star
*/
public ProxyStar(Star star) {
this.star = star;
}
@Override
public void sing() {
System.out.println("代理先進(jìn)行談判……");
// 唱歌只能明星自己唱
this.star.sing();
System.out.println("演出完代理去收錢……");
} - 接收真實(shí)的明星對(duì)象
}
這樣的話,邏輯就非常清晰了。在代理類中,可以看到,維護(hù)了一個(gè)Star對(duì)象,通過構(gòu)造方法傳進(jìn)來一個(gè)真實(shí)的Star對(duì)象給其賦值,然后在唱歌這個(gè)方法里,使用真實(shí)對(duì)象來唱歌。所以說面談、收錢都是由代理對(duì)象來實(shí)現(xiàn)的,唱歌是代理對(duì)象讓真實(shí)對(duì)象來做。下面寫個(gè)客戶端測(cè)試下。
/**
測(cè)試客戶端
@author shengwu ni
-
@date 2018-12-08
*/
public class Client {/**
測(cè)試靜態(tài)代理結(jié)果
-
@param args args
*/
public static void main(String[] args) {
Star realStar = new RealStar();
Star proxy = new ProxyStar(realStar);proxy.sing();
}
}
讀者可以自己運(yùn)行下結(jié)果,靜態(tài)代理比較簡單。動(dòng)態(tài)代理比靜態(tài)代理使用的更廣泛,動(dòng)態(tài)代理在本質(zhì)上,代理類不用我們來管,我們完全交給工具去生成代理類即可。動(dòng)態(tài)代理一般有兩種方式:JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理。
- JDK 動(dòng)態(tài)代理
既然動(dòng)態(tài)代理不需要我們?nèi)?chuàng)建代理類,那我們只需要編寫一個(gè)動(dòng)態(tài)處理器就可以了。真正的代理對(duì)象由 JDK 在運(yùn)行時(shí)為我們動(dòng)態(tài)的來創(chuàng)建。
/**
動(dòng)態(tài)代理處理類
@author shengwu ni
-
@date 2018-12-08
*/
public class JdkProxyHandler {/**
- 用來接收真實(shí)明星對(duì)象
*/
private Object realStar;
/**
- 通過構(gòu)造方法傳進(jìn)來真實(shí)的明星對(duì)象
- @param star star
*/
public JdkProxyHandler(Star star) {
super();
this.realStar = star;
}
/**
給真實(shí)對(duì)象生成一個(gè)代理對(duì)象實(shí)例
-
@return Object
*/
public Object getProxyInstance() {
return Proxy.newProxyInstance(realStar.getClass().getClassLoader(),
realStar.getClass().getInterfaces(), (proxy, method, args) -> {System.out.println("代理先進(jìn)行談判……"); // 唱歌需要明星自己來唱 Object object = method.invoke(realStar, args); System.out.println("演出完代理去收錢……"); return object; });
}
} - 用來接收真實(shí)明星對(duì)象
這里說一下 Proxy.newProxyInstance() 方法,該方法接收三個(gè)參數(shù):第一個(gè)參數(shù)指定當(dāng)前目標(biāo)對(duì)象使用的類加載器,獲取加載器的方法是固定的;第二個(gè)參數(shù)指定目標(biāo)對(duì)象實(shí)現(xiàn)的接口的類型;第三個(gè)參數(shù)指定動(dòng)態(tài)處理器,執(zhí)行目標(biāo)對(duì)象的方法時(shí),會(huì)觸發(fā)事件處理器的方法。網(wǎng)上針對(duì)第三個(gè)參數(shù)的寫法都是 new 一個(gè)匿名類來處理,我這直接用的 Java8 里面的 lamda 表達(dá)式來寫的,都一樣。底層原理使用的是反射機(jī)制。接下來寫一個(gè)客戶端程序來測(cè)試下。
/**
測(cè)試客戶端
@author shengwu ni
-
@date 2018-12-08
*/
public class Client {/**
測(cè)試JDK動(dòng)態(tài)代理結(jié)果
-
@param args args
*/
public static void main(String[] args) {
Star realStar = new RealStar();
// 創(chuàng)建一個(gè)代理對(duì)象實(shí)例
Star proxy = (Star) new JdkProxyHandler(realStar).getProxyInstance();proxy.sing();
}
}
可以看出,創(chuàng)建一個(gè)真實(shí)的對(duì)象,送給 JdkProxyHandler 就可以創(chuàng)建一個(gè)代理對(duì)象了。
我們對(duì) JDK 動(dòng)態(tài)代理做一個(gè)簡單的總結(jié):相對(duì)于靜態(tài)代理,JDK 動(dòng)態(tài)代理大大減少了我們的開發(fā)任務(wù),同時(shí)減少了對(duì)業(yè)務(wù)接口的依賴,降低了耦合度。JDK 動(dòng)態(tài)代理是利用反射機(jī)制生成一個(gè)實(shí)現(xiàn)代理接口的匿名類,在調(diào)用具體方法前調(diào)用InvokeHandler 來處理。但是 JDK 動(dòng)態(tài)代理有個(gè)缺憾,或者說特點(diǎn):JDK 實(shí)現(xiàn)動(dòng)態(tài)代理需要實(shí)現(xiàn)類通過接口定義業(yè)務(wù)方法。也就是說它始終無法擺脫僅支持 interface 代理的桎梏,因?yàn)樗脑O(shè)計(jì)就注定了這個(gè)遺憾。
- CGLIB 動(dòng)態(tài)代理
由上面的分析可知,JDK 實(shí)現(xiàn)動(dòng)態(tài)代理需要實(shí)現(xiàn)類通過接口定義業(yè)務(wù)方法,那對(duì)于沒有接口的類,如何實(shí)現(xiàn)動(dòng)態(tài)代理呢,這就需要 CGLIB 了。
CGLIB 采用了非常底層的字節(jié)碼技術(shù),其原理是通過字節(jié)碼技術(shù)為一個(gè)類創(chuàng)建子類,并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用,順勢(shì)織入橫切邏輯。但因?yàn)椴捎玫氖抢^承,所以不能對(duì)final修飾的類進(jìn)行代理。我們來寫一個(gè) CBLIB 代理類。
/**
cglib代理處理類
@author shengwu ni
-
@date 2018-12-08
*/
public class CglibProxyHandler implements MethodInterceptor {/**
- 維護(hù)目標(biāo)對(duì)象
*/
private Object target;
public Object getProxyInstance(final Object target) {
this.target = target;
// Enhancer類是CGLIB中的一個(gè)字節(jié)碼增強(qiáng)器,它可以方便的對(duì)你想要處理的類進(jìn)行擴(kuò)展
Enhancer enhancer = new Enhancer();
// 將被代理的對(duì)象設(shè)置成父類
enhancer.setSuperclass(this.target.getClass());
// 回調(diào)方法,設(shè)置攔截器
enhancer.setCallback(this);
// 動(dòng)態(tài)創(chuàng)建一個(gè)代理類
return enhancer.create();
}@Override
public Object intercept(Object object, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {System.out.println("代理先進(jìn)行談判……"); // 唱歌需要明星自己來唱 Object result = methodProxy.invokeSuper(object, args); System.out.println("演出完代理去收錢……"); return result;
}
} - 維護(hù)目標(biāo)對(duì)象
使用 CGLIB 需要實(shí)現(xiàn) MethodInterceptor 接口,并重寫intercept 方法,在該方法中對(duì)原始要執(zhí)行的方法前后做增強(qiáng)處理。該類的代理對(duì)象可以使用代碼中的字節(jié)碼增強(qiáng)器來獲取。接下來寫個(gè)客戶端測(cè)試程序。
/**
測(cè)試客戶端
@author shengwu ni
-
@date 2018-12-08
*/
public class Client {/**
測(cè)試Cglib動(dòng)態(tài)代理結(jié)果
-
@param args args
*/
public static void main(String[] args) {
Star realStar = new RealStar();
Star proxy = (Star) new CglibProxyHandler().getProxyInstance(realStar);proxy.sing();
}
}
這個(gè)客戶端測(cè)試程序和 JDK 動(dòng)態(tài)代理的邏輯一模一樣,所以也可以看出,代理模式中的動(dòng)態(tài)代理,其實(shí)套路都是相同的,只是使用了不同的技術(shù)而已。
我們也對(duì) CGLIB 動(dòng)態(tài)代理做一下總結(jié):CGLIB 創(chuàng)建的動(dòng)態(tài)代理對(duì)象比 JDK 創(chuàng)建的動(dòng)態(tài)代理對(duì)象的性能更高,但是 CGLIB 創(chuàng)建代理對(duì)象時(shí)所花費(fèi)的時(shí)間卻比 JDK 多得多。所以對(duì)于單例的對(duì)象,因?yàn)闊o需頻繁創(chuàng)建對(duì)象,用 CGLIB 合適,反之使用JDK方式要更為合適一些。同時(shí)由于 CGLIB 由于是采用動(dòng)態(tài)創(chuàng)建子類的方法,對(duì)于final修飾的方法無法進(jìn)行代理。
當(dāng)然了,不管是哪種動(dòng)態(tài)代理技術(shù),在上面的代碼里,要代理的類中可能不止一種方法,有時(shí)候我們需要對(duì)特定的方法進(jìn)行增強(qiáng)處理,所以可以對(duì)傳入的 method 參數(shù)進(jìn)行方法名的判斷,再做相應(yīng)的處理。
- Spring AOP 采用哪種代理?
JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理均是實(shí)現(xiàn) Spring AOP 的基礎(chǔ)。對(duì)于這一塊內(nèi)容,面試官問的比較多,他們往往更想聽聽面試者是怎么回答的,有沒有看過這一塊的源碼等等。
針對(duì)于這一塊內(nèi)容,我們看一下 Spring 5 中對(duì)應(yīng)的源碼是怎么說的。
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
// 判斷目標(biāo)類是否是接口或者目標(biāo)類是否Proxy類型,若是則使用JDK動(dòng)態(tài)代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 配置了使用CGLIB進(jìn)行動(dòng)態(tài)代理或者目標(biāo)類沒有接口,那么使用CGLIB的方式創(chuàng)建代理對(duì)象
return new ObjenesisCglibAopProxy(config);
}
else {
// 上面的三個(gè)方法沒有一個(gè)為true,那使用JDK的提供的代理方式生成代理對(duì)象
return new JdkDynamicAopProxy(config);
}
}
//其他方法略……
}
從上述源碼片段可以看出,是否使用 CGLIB 是在代碼中進(jìn)行判斷的,判斷條件是 config.isOptimize()、config.isProxyTargetClass() 和 hasNoUserSuppliedProxyInterfaces(config)。
其中,config.isOptimize() 與 config.isProxyTargetClass()默認(rèn)返回都是 false,這種情況下判斷結(jié)果就由
hasNoUserSuppliedProxyInterfaces(config)的結(jié)果決定了。
簡單來說,
hasNoUserSuppliedProxyInterfaces(config) 就是在判斷代理的對(duì)象是否有實(shí)現(xiàn)接口,有實(shí)現(xiàn)接口的話直接走 JDK 分支,即使用 JDK 的動(dòng)態(tài)代理。
所以基本上可以總結(jié)出 Spring AOP 中的代理使用邏輯了:如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,默認(rèn)情況下會(huì)采用 JDK 的動(dòng)態(tài)代理實(shí)現(xiàn) AOP;如果目標(biāo)對(duì)象沒有實(shí)現(xiàn)了接口,則采用 CGLIB 庫,Spring 會(huì)自動(dòng)在 JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理之間轉(zhuǎn)換。
當(dāng)然,源碼我也沒讀那么深,暫且就只能寫到這,后面深入了,有新的見解再給大家分享。還記得文章開頭的幾個(gè)問題嗎?相信你讀到這里,心中應(yīng)該已經(jīng)有了答案了。
如果覺得對(duì)自己有幫助,可以轉(zhuǎn)發(fā)給更多的伙伴們。