你對(duì)Spring AOP了解有多深?

網(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è)地址直接查閱:

https://github.com/spring-projects/spring-framework/blob/master/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java

到此上面的問(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é)觀

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容