網上寫Spring AOP的文章很多,寫得好的也不少。為什麼我還要寫?因為我蜜汁自信我能寫得更易懂,更有深度。

作為技術面試官,每當看到應聘者簡歷寫著「熟悉/精通Spring」的時候,我都會按照下面的套路來看看應聘者的掌握程度。

1、Spring AOP是什麼東西?

這個概念性的問題應聘者都能答出來(答不出來還有臉混java圈么~)差異無非體現在表達能力。表達不好也沒啥關係,因為Java圈創造的名詞概念實在太多了,只要能表達出主要應用場景即可。當然,如果應聘者能進一步表明自己在工作中用AOP實現過一個什麼功能,那就更好了。

官方一點的說法:AOP(Aspect Oriented Programming),面向切面編程,廣泛應用於處理一些具有橫切性質的系統級服務,如事務管理、緩存、許可權校驗、日誌記錄等等。

通俗點的說法:AOP,字面解釋就是面向切面編程,它能夠在不侵入業務代碼的情況下(也就是說不修改任何業務類的代碼),在指定的業務類的方法前或者方法後或者方法拋出異常時,執行一些通用邏輯。

有關AOP的用法,不是本文的關注點,不過大家可以在網上很容易找到,有興趣的童鞋可以找來看看。

2、Sping AOP是怎麼實現的?

這個問題除了小部分應聘者完全不了解外,大部分人都能說到是通過動態代理來實現的,然後能夠進一步說出動態代理有兩種實現方式:JDK Proxy和CGLib 並且能說清兩者的區別的大概有三分之一

Spring AOP是通過動態代理來實現的,我們通過咬文嚼字來解釋一下:動態代理=動態+代理。

什麼是動態?就是在程序運行時生成的,而不是編譯時。編譯時就生成的稱為靜態代理。很多人可能會把「靜態代理」單純理解為:需要為每一個目標類手動編寫一個代理類。其實不太對,AspectJ框架其實也可以實現AOP的事情,它與Spring AOP不同之處是:AspectJ框架可以在編譯時就生成目標類的「代理類」,在這裡加了個冒號,是因為實際上它並沒有生成一個新的類,而是把代理邏輯直接編譯到目標類裡面了(具體介紹大家可以看看文末貼的參考文章)。

什麼是代理?就是代理模式中的代理,不懂的童鞋可以自己查一下代理模式。

Spring AOP的動態代理有兩種實現方式:JDK Proxy 和CGLib. 它們主要區別:1、前者只能代理介面類,後者則沒有此限制;2、前者實現被代理類實現的介面,後者通過繼承被代理類來生成代理類。據網上資料說是CGLib性能更好一點,我自己沒有驗證。默認情況下,Spring對實現了介面的類使用JDK Proxy方式,否則的話使用CGLib。不過可以通過配置指定Spring AOP都通過CGLib來生成代理類。

3、Spring AOP同一個類內部嵌套調用能生效嗎?

這個問題其實是考察你對動態代理的本質理解。大部分人都答不出來,或者答出來了但是解釋不清楚原因。這個不難理解,因為要掌握上面幾個問題,只需要網上找一篇文章看一遍即可。但是如果沒有獨立思考過或者專門研究過的話,很難一下子想到本題的答案

為了更好的說明問題,我通過一個示例代碼進行描述:

@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;
}
}

問題是:上述代碼中getOrders方法在調用getOrder的時候,@Cacheable是否會生效?也就是說是否會檢查緩存?

答案是:不會。如果你理解動態代理生成的代理類大概是什麼樣子,你就能想到答案。其實,生成的代理類可以簡單示意成如下樣子(真實樣子不是這樣,會複雜很多,在這裡只是為了方便理解):

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(代碼略)
}
}

大家可以看到代理類是持有被代理類的一個實例對象的(orderService)。在代理類中,getOrders方法是直接調用被代理對象的對應方法,其前後並沒有增加任何代碼,而getOrder方法則先檢查了緩存,從緩存裡面找不到才去調用被代理對象的對應方法,然後再將返回值存到緩存中。看到這裡,大家應該能想清楚為什麼調用getOrders方法緩存不起作用了吧。

下面貼一下JDK Proxy生成的代理類(我私下將OrderService改為實現一個介面IOrderService,這樣Spring就使用JDK Proxy來生成代理類了)。說明一下,這裡貼出來的代碼不是全部,我刪掉了一些無關的方法及代碼以方便閱讀:

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());
}
}
}

可以看到這個代理類的每個方法實現都是直接調用一個類型為InvocationHandler的invoke方法。那麼InvocationHandler又是什麼呢?它的源代碼也很容易看懂,因為代碼比較長就不貼在這裡了,感興趣的童鞋可以打開這個地址直接查閱:

github.com/spring-proje

到此上面的問題就解釋完了。如果想在Spring中讓getOrders也能用到緩存,也是有「歪門邪道」的方法實現的,大家可以Google一下,相信能找到答案。

4、既然CGLib是通過繼承生成代理類的,是不是CGLib本身是支持讓getOrders能用到緩存的呢?

這個問題已經跟Spring無關了,我也不會問應聘者。這裡只是單純擴展一下CGLib的知識。

當初我學習Spring AOP的時候,我是有上述疑問的。試想一下,如果生成的代理類如下所示,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(代碼略)
}
}

對比一下之前的OrderServiceProxy,上面的「代理類」是不持有被代理類的對象的。為什麼代理類這三字我加了雙引號,因為這樣實現的話,跟我們理解的代理模式就不太匹配了,我們學習到的代理設計模式的實現方式都是說要持有被代理類對象的。拋開這個回到問題,CGLib到底支不支持生成類似上述的「代理類」,答案是:支持。感興趣大家可以去實踐一下。

References

[1] 李剛- Spring AOP 實現原理與 CGLIB 應用:

Spring AOP 實現原理與 CGLIB 應用?

www.ibm.com
圖標

歡迎微信掃一掃,關注我


推薦閱讀:
相关文章