網(wǎng)上寫Spring AOP的文章很多,寫得好的也不少。為什么我還要寫?因?yàn)槲颐壑孕盼夷軐懙酶锥?,更有深度?/p>
作為技術(shù)面試官,每當(dāng)看到應(yīng)聘者簡(jiǎn)歷寫著“熟悉/精通Spring”的時(shí)候,我都會(huì)按照下面的套路來(lái)看看應(yīng)聘者的掌握程度。
1、Spring AOP是什么東西?
這個(gè)概念性的問(wèn)題應(yīng)聘者都能答出來(lái)(答不出來(lái)還有臉混java圈么~)差異無(wú)非體現(xiàn)在表達(dá)能力。表達(dá)不好也沒(méi)啥關(guān)系,因?yàn)镴ava圈創(chuàng)造的名詞概念實(shí)在太多了,只要能表達(dá)出主要應(yīng)用場(chǎng)景即可。當(dāng)然,如果應(yīng)聘者能進(jìn)一步表明自己在工作中用AOP實(shí)現(xiàn)過(guò)一個(gè)什么功能,那就更好了。
官方一點(diǎn)的說(shuō)法:AOP(Aspect Oriented Programming),面向切面編程,廣泛應(yīng)用于處理一些具有橫切性質(zhì)的系統(tǒng)級(jí)服務(wù),如事務(wù)管理、緩存、權(quán)限校驗(yàn)、日志記錄等等。
通俗點(diǎn)的說(shuō)法:AOP,字面解釋就是面向切面編程,它能夠在不侵入業(yè)務(wù)代碼的情況下(也就是說(shuō)不修改任何業(yè)務(wù)類的代碼),在指定的業(yè)務(wù)類的方法前或者方法后或者方法拋出異常時(shí),執(zhí)行一些通用邏輯。
有關(guān)AOP的用法,不是本文的關(guān)注點(diǎn),不過(guò)大家可以在網(wǎng)上很容易找到,有興趣的童鞋可以找來(lái)看看。
2、Sping AOP是怎么實(shí)現(xiàn)的?
這個(gè)問(wèn)題除了小部分應(yīng)聘者完全不了解外,大部分人都能說(shuō)到是通過(guò)動(dòng)態(tài)代理來(lái)實(shí)現(xiàn)的,然后能夠進(jìn)一步說(shuō)出動(dòng)態(tài)代理有兩種實(shí)現(xiàn)方式:JDK Proxy和CGLib 并且能說(shuō)清兩者的區(qū)別的大概有三分之一
Spring AOP是通過(guò)動(dòng)態(tài)代理來(lái)實(shí)現(xiàn)的,我們通過(guò)咬文嚼字來(lái)解釋一下:動(dòng)態(tài)代理=動(dòng)態(tài)+代理。
什么是動(dòng)態(tài)?就是在程序運(yùn)行時(shí)生成的,而不是編譯時(shí)。編譯時(shí)就生成的稱為靜態(tài)代理。很多人可能會(huì)把“靜態(tài)代理”單純理解為:需要為每一個(gè)目標(biāo)類手動(dòng)編寫一個(gè)代理類。其實(shí)不太對(duì),AspectJ框架其實(shí)也可以實(shí)現(xiàn)AOP的事情,它與Spring AOP不同之處是:AspectJ框架可以在編譯時(shí)就生成目標(biāo)類的“代理類”,在這里加了個(gè)冒號(hào),是因?yàn)閷?shí)際上它并沒(méi)有生成一個(gè)新的類,而是把代理邏輯直接編譯到目標(biāo)類里面了(具體介紹大家可以看看文末貼的參考文章)。
什么是代理?就是代理模式中的代理,不懂的童鞋可以自己查一下代理模式。
Spring AOP的動(dòng)態(tài)代理有兩種實(shí)現(xiàn)方式:JDK Proxy 和CGLib. 它們主要區(qū)別:1、前者只能代理接口類,后者則沒(méi)有此限制;2、前者實(shí)現(xiàn)被代理類實(shí)現(xiàn)的接口,后者通過(guò)繼承被代理類來(lái)生成代理類。據(jù)網(wǎng)上資料說(shuō)是CGLib性能更好一點(diǎn),我自己沒(méi)有驗(yàn)證。默認(rèn)情況下,Spring對(duì)實(shí)現(xiàn)了接口的類使用JDK Proxy方式,否則的話使用CGLib。不過(guò)可以通過(guò)配置指定Spring AOP都通過(guò)CGLib來(lái)生成代理類。
3、Spring AOP同一個(gè)類內(nèi)部嵌套調(diào)用能生效嗎?
這個(gè)問(wèn)題其實(shí)是考察你對(duì)動(dòng)態(tài)代理的本質(zhì)理解。大部分人都答不出來(lái),或者答出來(lái)了但是解釋不清楚原因。這個(gè)不難理解,因?yàn)橐莆丈厦鎺讉€(gè)問(wèn)題,只需要網(wǎng)上找一篇文章看一遍即可。但是如果沒(méi)有獨(dú)立思考過(guò)或者專門研究過(guò)的話,很難一下子想到本題的答案
為了更好的說(shuō)明問(wèn)題,我通過(guò)一個(gè)示例代碼進(jìn)行描述:
@Service
public class OrderService {
@Cacheable
public Order getOrder(Integer orderId){
Order order=null;
//get order from db (代碼略)
return order;
}
public List<Order> getOrders(List<Integer> orderIds){
List<Order> resultList=new ArrayList<Order>(orderIds.size());
for (Integer orderId : orderIds) {
resultList.add(this.getOrder(orderId));
}
return resultList;
}
}
問(wèn)題是:上述代碼中g(shù)etOrders方法在調(diào)用getOrder的時(shí)候,@Cacheable是否會(huì)生效?也就是說(shuō)是否會(huì)檢查緩存?
答案是:不會(huì)。如果你理解動(dòng)態(tài)代理生成的代理類大概是什么樣子,你就能想到答案。其實(shí),生成的代理類可以簡(jiǎn)單示意成如下樣子(真實(shí)樣子不是這樣,會(huì)復(fù)雜很多,在這里只是為了方便理解):
public class OrderServiceProxy extends OrderService {
private OrderService orderService;
public OrderServiceProxy(OrderService orderService) {
this.orderService = orderService;
}
@Override
public Order getOrder(Integer orderId) {
Order order = getFromCache(orderId);
//miss cache
if (order == null) {
order = this.orderService.getOrder(orderId);
putIntoCache(orderId, order);
}
return order;
}
@Override
public List<Order> getOrders(List<Integer> orderIds) {
return this.orderService.getOrders(orderIds);
}
private Order getFromCache(Integer orderId) {
//get from cache(代碼略)
return null;
}
private void putIntoCache(Integer key, Order value) {
//save to cache(代碼略)
}
}
大家可以看到代理類是持有被代理類的一個(gè)實(shí)例對(duì)象的(orderService)。在代理類中,getOrders方法是直接調(diào)用被代理對(duì)象的對(duì)應(yīng)方法,其前后并沒(méi)有增加任何代碼,而getOrder方法則先檢查了緩存,從緩存里面找不到才去調(diào)用被代理對(duì)象的對(duì)應(yīng)方法,然后再將返回值存到緩存中??吹竭@里,大家應(yīng)該能想清楚為什么調(diào)用getOrders方法緩存不起作用了吧。
下面貼一下JDK Proxy生成的代理類(我私下將OrderService改為實(shí)現(xiàn)一個(gè)接口IOrderService,這樣Spring就使用JDK Proxy來(lái)生成代理類了)。說(shuō)明一下,這里貼出來(lái)的代碼不是全部,我刪掉了一些無(wú)關(guān)的方法及代碼以方便閱讀:
package com.sun.proxy;
import com.demo.aop.IOrderService;
import com.demo.aop.Order;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public final class $Proxy53 extends Proxy implements IOrderService, SpringProxy, Advised, DecoratingProxy {
private static Method m1;
private static Method m4;
public $Proxy53(InvocationHandler var1) throws {
super(var1);
}
public final Order getOrder(Integer var1) throws {
try {
return (Order)super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final List getOrders(List var1) throws {
try {
return (List)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m4 = Class.forName("com.demo.aop.IOrderService").getMethod("getOrder", new Class[]{Class.forName("java.lang.Integer")});
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
可以看到這個(gè)代理類的每個(gè)方法實(shí)現(xiàn)都是直接調(diào)用一個(gè)類型為InvocationHandler的invoke方法。那么InvocationHandler又是什么呢?它的源代碼也很容易看懂,因?yàn)榇a比較長(zhǎng)就不貼在這里了,感興趣的童鞋可以打開這個(gè)地址直接查閱:
到此上面的問(wèn)題就解釋完了。如果想在Spring中讓getOrders也能用到緩存,也是有“歪門邪道”的方法實(shí)現(xiàn)的,大家可以Google一下,相信能找到答案。
4、既然CGLib是通過(guò)繼承生成代理類的,是不是CGLib本身是支持讓getOrders能用到緩存的呢?
這個(gè)問(wèn)題已經(jīng)跟Spring無(wú)關(guān)了,我也不會(huì)問(wèn)應(yīng)聘者。這里只是單純擴(kuò)展一下CGLib的知識(shí)。
當(dāng)初我學(xué)習(xí)Spring AOP的時(shí)候,我是有上述疑問(wèn)的。試想一下,如果生成的代理類如下所示,getOrders不就可以用上緩存了么?
public class OrderServiceProxy2 extends OrderService {
@Override
public Order getOrder(Integer orderId) {
Order order = getFromCache(orderId);
//miss cache
if (order == null) {
order = super.getOrder(orderId);
putIntoCache(orderId, order);
}
return order;
}
@Override
public List<Order> getOrders(List<Integer> orderIds) {
return super.getOrders(orderIds);
}
private Order getFromCache(Integer orderId) {
//get from cache(代碼略)
return null;
}
private void putIntoCache(Integer key, Order value) {
//save to cache(代碼略)
}
}
對(duì)比一下之前的OrderServiceProxy,上面的“代理類”是不持有被代理類的對(duì)象的。為什么代理類這三字我加了雙引號(hào),因?yàn)檫@樣實(shí)現(xiàn)的話,跟我們理解的代理模式就不太匹配了,我們學(xué)習(xí)到的代理設(shè)計(jì)模式的實(shí)現(xiàn)方式都是說(shuō)要持有被代理類對(duì)象的。拋開這個(gè)回到問(wèn)題,CGLib到底支不支持生成類似上述的“代理類”,答案是:支持。感興趣大家可以去實(shí)踐一下。
References
[1] 李剛- Spring AOP 實(shí)現(xiàn)原理與 CGLIB 應(yīng)用:
https://www.ibm.com/developerworks/cn/java/j-lo-springaopcglib/index.html
喜歡的童鞋,歡迎關(guān)注公眾號(hào):字節(jié)觀