本身實現 aop 和 spring aop

上文說到,咱們能夠在 BeanPostProcessor 中對 bean 的初始化前化作手腳,當時也說了,我徹底能夠生成一個代理類丟回去。java

代理類確定要爲用戶作一些事情,不可能像學設計模式的時候建立個代理類,而後簡單的在前面打印一句話,後面打印一句話,這叫啥事啊,難怪當時聽不懂。最好是這個方法的先後過程能夠自戶本身定義。git

小明說,這還很差辦,cglib 已經有現成的了,jdk 也能夠實現動態代理,看 mybatis 其實也是這麼幹的,否則你想它一個接口怎麼就能找到 xml 的實現呢,能夠參照下 mybatis 的代碼。redis

因此首先學習下 cglib 和 jdk 的動態代理,咱們來模擬下 mybatis 是如何經過接口來實現方法調用的spring

cglibsql

目標接口:數據庫

public interface UserOperator {
    User queryUserByName(String name);
}

代理處理類:json

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class ProxyHandle implements MethodInterceptor{
    // 實現 MethodInterceptor 的代理攔截接口
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("獲取到 sqlId:"+method);
        System.out.println("獲取到執行參數列表:"+args[0]);
        System.out.println("解析 spel 表達式,並獲取到完整的 sql 語句");
        System.out.println("執行 sql ");
        System.out.println("結果集處理,並返回綁定對象");
        return new User("sanri",1);
    }
}

真正調用處:設計模式

Enhancer enhancer = new Enhancer();

enhancer.setSuperclass(UserOperator.class);
enhancer.setCallback(new ProxyHandle());

//能夠把這個類添加進 ioc 容器,這就是真正的代理類
UserOperator userOperator = (UserOperator) enhancer.create();

User sanri = userOperator.queryByName("sanri");
System.out.println(sanri);

jdkmybatis

import java.lang.reflect.InvocationHandler;
public class ProxyHandler implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("獲取到 sqlId:"+method);
        System.out.println("獲取到執行參數列表:"+args[0]);
        System.out.println("解析 spel 表達式,並獲取到完整的 sql 語句");
        System.out.println("執行 sql ");
        System.out.println("結果集處理,並返回綁定對象");
        return new User("sanri",1);
    }
}

真正調用處:maven

UserOperator proxyInstance = (UserOperator)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{UserOperator.class}, new ProxyHandler());
User sanri = proxyInstance.queryByName("sanri");
System.out.println(sanri);

注:jdk 只能支持代理接口,但 cglib 是接口和實體類均可以代理; jdk 是使用實現接口方式,能夠多實現,但 cglib 是繼承方式,也支持接口方式。

代理模式和裝飾模式的區別:

從這也能夠看到代理模式和裝飾模式的區別 ,代理模式的方法簽名通常是不動的,但裝飾模式是爲了方法的加強,通常會使用別的更好的方法來代替原方法。

如何織入

回到正文,這時咱們已經能夠建立一個代理類了,如何把用戶行爲給弄進來呢,哎,又只能 回調 了,咱們把現場信息給用戶,用戶實現個人接口,而後我找到接口的全部實現類進行順序調用,但這時候小明想到了幾個問題

  • 用戶不必定每一個方法都要作代理邏輯,可能只是部分方法須要,咱們應該可以識別出是哪些方法須要作代理邏輯 (Pointcut)
  • 方法加代理邏輯的位置,方法執行前(Before),方法執行後(After),方法返回數據後(AfterReturning),方法出異常後(AfterThrowing),自定義執行(Around)

根據單一職責原則,得寫五個接口,每一個接口要包含 getPointCut() 方法和 handler() 方法,或者繞過單一職責原則,在一個接口中定義 6 個方法,用戶不想實現留空便可。總得來講,用戶只須要提交一份規則給我就行,這個規則你不論是用 json,xml ,或者 註解的方式,只要我可以識別在 這個 pointcut 下,須要有哪些自定義行爲,在另外一個 pointcut 下又有哪些自定義行爲便可。

現拿到用戶行爲了和切點了,還須要建立目標類的代理類,並把行爲給綁定上去,在何時建立代理類呢,確定在把 bean 交給容器的時候悄悄的換掉啊,上文 說到 bean 有一個生命週期是用於作全部 bean 攔截的,而且能夠在初始化前和初始化後進行攔截,沒錯,就是 BeanPostProcessor 咱們能夠在初始化後生成代理類。

這裏須要注意,並非全部類都須要建立代理。咱們能夠這樣檢測,讓 pointcut 提供一個方法用於匹配當前方法是否須要代理,固然這也是 pointcut 的職責,若是當前類有一個方法須要代理,那麼當前類是須要代理的,不然認爲不須要代理,這麼作須要遍歷全部類的全部方法,若是運氣差的話,看上去很耗費性能 ,但 spring 也是這麼幹的。。。。。。優化的方案能夠這麼玩,若是方法須要代理,在類上作一個標識,若是類上存在這個標識,則能夠直接建立代理類。

如今咱們把用戶行爲綁定到代理類,根據上面 jdk 動態代理和 cglib 動態代理的學習,咱們發現,它們都有一個共同的傢伙,那就是方法攔截,用於攔截目標類的當前正在執行的方法,並加強其功能,咱們能夠在建立代理類的時候找到全部的用戶行爲並按照順序和類型依次綁定,能夠用責任鏈模式。

看一下 spring 是怎麼玩的

spring 也是在 BeanPostProcessor 接口的 postProcessAfterInitialization 生命週期進行攔截,具體的類爲 AspectJAwareAdvisorAutoProxyCreator

spring 配置切面有兩種方式,使用註解和使用配置,固然,如今流行註解的方式,更方便,但不論是配置仍是註解,最後都會被解析成 Advisor(InstantiationModelAwarePointcutAdvisorImpl),spring 查找了全部實現 Advisor 的類,源代碼在 BeanFactoryAdvisorRetrievalHelper.findAdvisorBeans

advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Advisor.class, true, false);

緊接着,spring 會使使用 Advisor 中的 pointcut 來看當前類是否須要建立代理類,跟進方法能夠看到 canApply 方法中是遍歷了全部方法一個個匹配來看是否須要建立代理類的,若是有一個須要,則直接返回 true 。固然 spring 更嚴謹一些,它考慮到了可能有接口的方法須要有代理,我上面說在類加標識是不正確的。

而後經過 createProxy 建立了代理類,裏面有區分 cglib 仍是 aop ,下面單拿 cglib 來講

CglibAopProxy.getProxy 中對類進行加強,主要看 Enhancer 類是如何設置的就行了,有一個 callback 參數 ,咱們通常是第 0 個 callback 也即 DynamicAdvisedInterceptor 它是一個 cglib 的 MethodInterceptor

它重寫的是 MethodInterceptor 的 intercept 方法,下面看這個方法,this.advised 是前面傳過來的用戶行爲,getInterceptorsAndDynamicInterceptionAdviceAdvisor 適配成了 org.aopalliance.intercept.MethodInterceptor 分別對應切面的五種行爲

AbstractAspectJAdvice
  |- AspectJAfterReturningAdvice
  |- AspectJAfterAdvice implements org.aopalliance.intercept.MethodInterceptor
  |- AspectJAroundAdvice implements org.aopalliance.intercept.MethodInterceptor
  |- AspectJAfterThrowingAdvice implements org.aopalliance.intercept.MethodInterceptor
  |- AspectJMethodBeforeAdvice

最後它封裝一個執行器,根據順序調用攔截器鏈,也即用戶行爲列表,封裝執行的時候是強轉 org.aopalliance.intercept.MethodInterceptor 來執行的,但 AspectJAfterReturningAdviceAspectJMethodBeforeAdvice 沒有實現 org.aopalliance.intercept.MethodInterceptor 怎麼辦,因此 spring 在獲取用戶行爲鏈的時候增長了一個適配器,專門用於把這兩種轉換成 MethodInterceptor

其它說明

  • cglib 的 callback 只能寫一個,filter 用於選擇是第幾個 callback ,不要認爲也是鏈式的

  • spring aop 中有比較多的設計模式,學設計模式的能夠看下這塊的源碼 ,至少責任鏈,適配器,動態代理均可以在這看到
  • 切面類中若是有兩個同樣的行爲,好比有兩個 @Before,排序規則爲看方法名的 ascii 碼值,只測試過,並沒通過源碼,有興趣的能夠本身去看一下。

來個示例更容易理解

咱們除了使用 @Aspect 註解把切面規則告訴 spring 外,也能夠學自己 aop 的實現,咱們本身定義一個 Advisor ,由於 spring 就是掃描這個的,而後實現 pointcut 和 invoke 方法,同樣能夠實現 aop 。

聯繫上文: spring-data-redis-cache 使用及源碼走讀 咱們來看看 spring 的 redis-cache 是如何作切面的

文章說到,主要工做的類是 CacheInterceptor 它是一個 org.aopalliance.intercept.MethodInterceptor

Advisor 是 BeanFactoryCacheOperationSourceAdvisor 也就是說建立代理類會掃描到這個類,最後執行會把其轉成 MethodInterceptor,由於它是一個 PointcutAdvisor ,查看 DefaultAdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice 方法,第一個就是把 PointcutAdvisor 轉成 MethodInterceptor 繼續進入獲取攔截器的方法,能夠知道就是獲取的 advice 屬性 CacheInterceptor

一點小推廣

創做不易,但願能夠支持下個人開源軟件,及個人小工具,歡迎來 gitee 點星,fork ,提 bug 。

Excel 通用導入導出,支持 Excel 公式
博客地址:https://blog.csdn.net/sanri1993/article/details/100601578
gitee:https://gitee.com/sanri/sanri-excel-poi

使用模板代碼 ,從數據庫生成代碼 ,及一些項目中常常能夠用到的小工具
博客地址:https://blog.csdn.net/sanri1993/article/details/98664034
gitee:https://gitee.com/sanri/sanri-tools-maven

相關文章
相關標籤/搜索