你對Spring AOP瞭解有多深?

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

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

一、Spring AOP是什麼東西?

這個概念性的問題應聘者都能答出來(答不出來還有臉混java圈麼~)差別無非體如今表達能力。表達很差也沒啥關係,由於Java圈創造的名詞概念實在太多了,只要能表達出主要應用場景便可。固然,若是應聘者能進一步代表本身在工做中用AOP實現過一個什麼功能,那就更好了。

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

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

有關AOP的用法,不是本文的關注點,不過你們能夠在網上很容易找到,有興趣的童鞋能夠找來看看。github

二、Sping AOP是怎麼實現的?

這個問題除了小部分應聘者徹底不瞭解外,大部分人都能說到是經過動態代理來實現的,而後可以進一步說出動態代理有兩種實現方式:JDK Proxy和CGLib 而且能說清二者的區別的大概有三分之一

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

什麼是動態?就是在程序運行時生成的,而不是編譯時。編譯時就生成的稱爲靜態代理。不少人可能會把「靜態代理」單純理解爲:須要爲每個目標類手動編寫一個代理類。其實不太對,AspectJ框架其實也能夠實現AOP的事情,它與Spring AOP不一樣之處是:AspectJ框架能夠在編譯時就生成目標類的「代理類」,在這裏加了個冒號,是由於實際上它並無生成一個新的類,而是把代理邏輯直接編譯到目標類裏面了(具體介紹你們能夠看看文末貼的參考文章)。
什麼是代理?就是代理模式中的代理,不懂的童鞋能夠本身查一下代理模式。spring

Spring AOP的動態代理有兩種實現方式:JDK Proxy 和CGLib. 它們主要區別:一、前者只能代理接口類,後者則沒有此限制;二、前者實現被代理類實現的接口,後者經過繼承被代理類來生成代理類。據網上資料說是CGLib性能更好一點,我本身沒有驗證。默認狀況下,Spring對實現了接口的類使用JDK Proxy方式,不然的話使用CGLib。不過能夠經過配置指定Spring AOP都經過CGLib來生成代理類。編程

三、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是否會生效?也就是說是否會檢查緩存?緩存

答案是:不會。若是你理解動態代理生成的代理類大概是什麼樣子,你就能想到答案。其實,生成的代理類能夠簡單示意成以下樣子(真實樣子不是這樣,會複雜不少,在這裏只是爲了方便理解):bash

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-proj…

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

四、既然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 應用:

www.ibm.com/developerwo…

喜歡的童鞋,歡迎關注公衆號:字節觀

相關文章
相關標籤/搜索