手寫Spring框架,是時候擼個AOP與Bean生命週期融合了!

小傅哥發表於2021-07-23


作者:小傅哥
部落格:https://bugstack.cn

沉澱、分享、成長,讓自己和他人都能有所收穫!?

一、前言

嘎小子,這片程式碼水太深你把握不住!

在電視劇《楚漢傳奇》中有這麼一段劉邦與韓信的飲酒對話,劉邦問韓信我那個曹參讀過書見過世面能帶多少兵,韓信說能帶一萬五,又補充說一萬五都吃力。劉邦又一一說出樊噲盧綰周勃,韓信笑著說不足2萬,腦子不行。這時候劉邦有點掛不住臉了,問:那我呢,我能帶多少兵。韓信說,你能帶十萬。劉邦一看比他們都多,啊,還行。轉頭一想就問韓信那你呢,你能帶多少兵。韓信喝多了,說啊,我,我多多益善。這時候劉邦惱了領導勁上來了,問:那我為什麼能管著你,你給我說,說呀!


這像不像你領導問你,你能寫多少程式碼、搭多少框架、接多少專案。可能很大一部分沒經歷太多的新人碼農,僅僅是能完成一些簡單的功能模組開發,而沒有辦法駕馭整個專案的涉及到的所有工程,也不能為專案提煉出一些可複用的通用性元件模組。在初級碼農的心裡,接一點需求還好,但沒有人帶的時候完全接一個較大型專案就會比較慌了,不知道這裡有沒有坑,自己也把握住不。這些程式碼一塊塊的帶著能寫,但是都弄到一塊,就太難了!

在程式碼開發成長的這條路上,要經歷CRUD、ERP查資料、介面包裝、功能開發、服務整合、系統建設等,一直到獨立帶人承擔較大型專案的搭建。這一過程需要你能有大量的編寫程式碼經驗積累和複雜問題的處理手段,之後才能一段段的把看似獨立的模組後者程式碼片段組裝成一個較大型能跑起來的專案。就像 Spring 的開發過程一樣,我們總是不斷在新增新的功能片段,最後又把技術實現與Spring 容器整合,讓使用方可以更簡單的運用 Spring 提供的能力。

二、目標

在上一章節我們通過基於 Proxy.newProxyInstance 代理操作中處理方法匹配和方法攔截,對匹配的物件進行自定義的處理操作。並把這樣的技術核心內容拆解到 Spring 中,用於實現 AOP 部分,通過拆分後基本可以明確各個類的職責,包括你的代理目標物件屬性、攔截器屬性、方法匹配屬性,以及兩種不同的代理操作 JDK 和 CGlib 的方式。

再有了一個 AOP 核心功能的實現後,我們可以通過單元測試的方式進行驗證切面功能對方法進行攔截,但如果這是一個面向使用者使用的功能,就不太可能讓使用者這麼複雜且沒有與 Spring 結合的方式單獨使用 AOP,雖然可以滿足需求,但使用上還是過去分散。

因此我們需要在本章節完成 AOP 核心功能與 Spring 框架的整合,最終能通過在 Spring 配置的方式完成切面的操作。

三、方案

  1. 從 BeanPostProcessor 開始,讓 xml 中的配置載入到 DefaultAdvisorAutoProxyCreator 實現,
  2. 其實再有了核心功能的開發後,這事也不難。只不過我們要解決幾個問題,包括:怎麼串聯到Bean的生命週期中、怎麼組裝各項功能、怎麼適配代理,
  3. 藉著 BeanPostProcessor,把動態代理融入到Bean的生命週期

其實在有了AOP的核心功能實現後,把這部分功能服務融入到 Spring 其實也不難,只不過要解決幾個問題,包括:怎麼藉著 BeanPostProcessor 把動態代理融入到 Bean 的生命週期中,以及如何組裝各項切點、攔截、前置的功能和適配對應的代理器。整體設計結構如下圖:

  • 為了可以讓物件建立過程中,能把xml中配置的代理物件也就是切面的一些類物件例項化,就需要用到 BeanPostProcessor 提供的方法,因為這個類的中的方法可以分別作用與 Bean 物件執行初始化前後修改 Bean 的物件的擴充套件資訊。但這裡需要集合於 BeanPostProcessor 實現新的介面和實現類,這樣才能定向獲取對應的類資訊。
  • 但因為建立的是代理物件不是之前流程裡的普通物件,所以我們需要前置於其他物件的建立,所以在實際開發的過程中,需要在 AbstractAutowireCapableBeanFactory#createBean 優先完成 Bean 物件的判斷,是否需要代理,有則直接返回代理物件。在Spring的原始碼中會有 createBean 和 doCreateBean 的方法拆分
  • 這裡還包括要解決方法攔截器的具體功能,提供一些 BeforeAdvice、AfterAdvice 的實現,讓使用者可以更簡化的使用切面功能。除此之外還包括需要包裝切面表示式以及攔截方法的整合,以及提供不同型別的代理方式的代理工廠,來包裝我們的切面服務。

四、實現

1. 工程結構

small-spring-step-12
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.springframework
    │           ├── aop
    │           │   ├── aspectj
    │           │   │   └── AspectJExpressionPointcut.java
    │           │   │   └── AspectJExpressionPointcutAdvisor.java
    │           │   ├── framework 
    │           │   │   ├── adapter
    │           │   │   │   └── MethodBeforeAdviceInterceptor.java
    │           │   │   ├── autoproxy
    │           │   │   │   └── MethodBeforeAdviceInterceptor.java
    │           │   │   ├── AopProxy.java
    │           │   │   ├── Cglib2AopProxy.java
    │           │   │   ├── JdkDynamicAopProxy.java
    │           │   │   ├── ProxyFactory.java
    │           │   │   └── ReflectiveMethodInvocation.java
    │           │   ├── AdvisedSupport.java
    │           │   ├── Advisor.java
    │           │   ├── BeforeAdvice.java
    │           │   ├── ClassFilter.java
    │           │   ├── MethodBeforeAdvice.java
    │           │   ├── MethodMatcher.java
    │           │   ├── Pointcut.java
    │           │   ├── PointcutAdvisor.java
    │           │   └── TargetSource.java
    │           ├── beans
    │           │   ├── factory
    │           │   │   ├── config
    │           │   │   │   ├── AutowireCapableBeanFactory.java
    │           │   │   │   ├── BeanDefinition.java
    │           │   │   │   ├── BeanFactoryPostProcessor.java
    │           │   │   │   ├── BeanPostProcessor.java
    │           │   │   │   ├── BeanReference.java
    │           │   │   │   ├── ConfigurableBeanFactory.java
    │           │   │   │   ├── InstantiationAwareBeanPostProcessor.java
    │           │   │   │   └── SingletonBeanRegistry.java
    │           │   │   ├── support
    │           │   │   │   ├── AbstractAutowireCapableBeanFactory.java
    │           │   │   │   ├── AbstractBeanDefinitionReader.java
    │           │   │   │   ├── AbstractBeanFactory.java
    │           │   │   │   ├── BeanDefinitionReader.java
    │           │   │   │   ├── BeanDefinitionRegistry.java
    │           │   │   │   ├── CglibSubclassingInstantiationStrategy.java
    │           │   │   │   ├── DefaultListableBeanFactory.java
    │           │   │   │   ├── DefaultSingletonBeanRegistry.java
    │           │   │   │   ├── DisposableBeanAdapter.java
    │           │   │   │   ├── FactoryBeanRegistrySupport.java
    │           │   │   │   ├── InstantiationStrategy.java
    │           │   │   │   └── SimpleInstantiationStrategy.java  
    │           │   │   ├── support
    │           │   │   │   └── XmlBeanDefinitionReader.java
    │           │   │   ├── Aware.java
    │           │   │   ├── BeanClassLoaderAware.java
    │           │   │   ├── BeanFactory.java
    │           │   │   ├── BeanFactoryAware.java
    │           │   │   ├── BeanNameAware.java
    │           │   │   ├── ConfigurableListableBeanFactory.java
    │           │   │   ├── DisposableBean.java
    │           │   │   ├── FactoryBean.java
    │           │   │   ├── HierarchicalBeanFactory.java
    │           │   │   ├── InitializingBean.java
    │           │   │   └── ListableBeanFactory.java
    │           │   ├── BeansException.java
    │           │   ├── PropertyValue.java
    │           │   └── PropertyValues.java 
    │           ├── context
    │           │   ├── event
    │           │   │   ├── AbstractApplicationEventMulticaster.java 
    │           │   │   ├── ApplicationContextEvent.java 
    │           │   │   ├── ApplicationEventMulticaster.java 
    │           │   │   ├── ContextClosedEvent.java 
    │           │   │   ├── ContextRefreshedEvent.java 
    │           │   │   └── SimpleApplicationEventMulticaster.java 
    │           │   ├── support
    │           │   │   ├── AbstractApplicationContext.java 
    │           │   │   ├── AbstractRefreshableApplicationContext.java 
    │           │   │   ├── AbstractXmlApplicationContext.java 
    │           │   │   ├── ApplicationContextAwareProcessor.java 
    │           │   │   └── ClassPathXmlApplicationContext.java 
    │           │   ├── ApplicationContext.java 
    │           │   ├── ApplicationContextAware.java 
    │           │   ├── ApplicationEvent.java 
    │           │   ├── ApplicationEventPublisher.java 
    │           │   ├── ApplicationListener.java 
    │           │   └── ConfigurableApplicationContext.java
    │           ├── core.io
    │           │   ├── ClassPathResource.java 
    │           │   ├── DefaultResourceLoader.java 
    │           │   ├── FileSystemResource.java 
    │           │   ├── Resource.java 
    │           │   ├── ResourceLoader.java 
    │           │   └── UrlResource.java
    │           └── utils
    │               └── ClassUtils.java
    └── test
        └── java
            └── cn.bugstack.springframework.test
                ├── bean
                │   ├── IUserService.java
                │   ├── UserService.java
                │   └── UserServiceInterceptor.java
                └── ApiTest.java

工程原始碼公眾號「bugstack蟲洞棧」,回覆:Spring 專欄,獲取完整原始碼

AOP 動態代理融入到Bean的生命週期中類關係,如圖 13-2

圖 13-2

  • 整個類關係圖中可以看到,在以 BeanPostProcessor 介面實現繼承的 InstantiationAwareBeanPostProcessor 介面後,做了一個自動代理建立的類 DefaultAdvisorAutoProxyCreator,這個類的就是用於處理整個 AOP 代理融入到 Bean 生命週期中的核心類。
  • DefaultAdvisorAutoProxyCreator 會依賴於攔截器、代理工廠和Pointcut與Advisor的包裝服務 AspectJExpressionPointcutAdvisor,由它提供切面、攔截方法和表示式。
  • Spring 的 AOP 把 Advice 細化了 BeforeAdvice、AfterAdvice、AfterReturningAdvice、ThrowsAdvice,目前我們做的測試案例中只用到了 BeforeAdvice,這部分可以對照 Spring 的原始碼進行補充測試。

2. 定義Advice攔截器鏈

cn.bugstack.springframework.aop.BeforeAdvice

public interface BeforeAdvice extends Advice {

}

cn.bugstack.springframework.aop.MethodBeforeAdvice

public interface MethodBeforeAdvice extends BeforeAdvice {

    /**
     * Callback before a given method is invoked.
     *
     * @param method method being invoked
     * @param args   arguments to the method
     * @param target target of the method invocation. May be <code>null</code>.
     * @throws Throwable if this object wishes to abort the call.
     *                   Any exception thrown will be returned to the caller if it's
     *                   allowed by the method signature. Otherwise the exception
     *                   will be wrapped as a runtime exception.
     */
    void before(Method method, Object[] args, Object target) throws Throwable;

}
  • 在 Spring 框架中,Advice 都是通過方法攔截器 MethodInterceptor 實現的。環繞 Advice 類似一個攔截器的鏈路,Before Advice、After advice等,不過暫時我們需要那麼多就只定義了一個 MethodBeforeAdvice 的介面定義。

3. 定義 Advisor 訪問者

cn.bugstack.springframework.aop.Advisor

public interface Advisor {

    /**
     * Return the advice part of this aspect. An advice may be an
     * interceptor, a before advice, a throws advice, etc.
     * @return the advice that should apply if the pointcut matches
     * @see org.aopalliance.intercept.MethodInterceptor
     * @see BeforeAdvice
     */
    Advice getAdvice();

}

cn.bugstack.springframework.aop.PointcutAdvisor

public interface PointcutAdvisor extends Advisor {

    /**
     * Get the Pointcut that drives this advisor.
     */
    Pointcut getPointcut();

}
  • Advisor 承擔了 Pointcut 和 Advice 的組合,Pointcut 用於獲取 JoinPoint,而 Advice 決定於 JoinPoint 執行什麼操作。

cn.bugstack.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor

public class AspectJExpressionPointcutAdvisor implements PointcutAdvisor {

    // 切面
    private AspectJExpressionPointcut pointcut;
    // 具體的攔截方法
    private Advice advice;
    // 表示式
    private String expression;

    public void setExpression(String expression){
        this.expression = expression;
    }

    @Override
    public Pointcut getPointcut() {
        if (null == pointcut) {
            pointcut = new AspectJExpressionPointcut(expression);
        }
        return pointcut;
    }

    @Override
    public Advice getAdvice() {
        return advice;
    }

    public void setAdvice(Advice advice){
        this.advice = advice;
    }

}
  • AspectJExpressionPointcutAdvisor 實現了 PointcutAdvisor 介面,把切面 pointcut、攔截方法 advice 和具體的攔截表示式包裝在一起。這樣就可以在 xml 的配置中定義一個 pointcutAdvisor 切面攔截器了。

4. 方法攔截器

cn.bugstack.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor

public class MethodBeforeAdviceInterceptor implements MethodInterceptor {

    private MethodBeforeAdvice advice;

    public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
        this.advice = advice;
    }

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        this.advice.before(methodInvocation.getMethod(), methodInvocation.getArguments(), methodInvocation.getThis());
        return methodInvocation.proceed();
    }

}
  • MethodBeforeAdviceInterceptor 實現了 MethodInterceptor 介面,在 invoke 方法中呼叫 advice 中的 before 方法,傳入對應的引數資訊。
  • 而這個 advice.before 則是用於自己實現 MethodBeforeAdvice 介面後做的相應處理。其實可以看到具體的 MethodInterceptor 實現類,其實和我們之前做的測試是一樣的,只不過現在交給了 Spring 來處理

5. 代理工廠

cn.bugstack.springframework.aop.framework.ProxyFactory

public class ProxyFactory {

    private AdvisedSupport advisedSupport;

    public ProxyFactory(AdvisedSupport advisedSupport) {
        this.advisedSupport = advisedSupport;
    }

    public Object getProxy() {
        return createAopProxy().getProxy();
    }

    private AopProxy createAopProxy() {
        if (advisedSupport.isProxyTargetClass()) {
            return new Cglib2AopProxy(advisedSupport);
        }

        return new JdkDynamicAopProxy(advisedSupport);
    }

}
  • 其實這個代理工廠主要解決的是關於 JDK 和 Cglib 兩種代理的選擇問題,有了代理工廠就可以按照不同的建立需求進行控制。

6. 融入Bean生命週期的自動代理建立者

cn.bugstack.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator

public class DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {

    private DefaultListableBeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = (DefaultListableBeanFactory) beanFactory;
    }

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {

        if (isInfrastructureClass(beanClass)) return null;

        Collection<AspectJExpressionPointcutAdvisor> advisors = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values();

        for (AspectJExpressionPointcutAdvisor advisor : advisors) {
            ClassFilter classFilter = advisor.getPointcut().getClassFilter();
            if (!classFilter.matches(beanClass)) continue;

            AdvisedSupport advisedSupport = new AdvisedSupport();

            TargetSource targetSource = null;
            try {
                targetSource = new TargetSource(beanClass.getDeclaredConstructor().newInstance());
            } catch (Exception e) {
                e.printStackTrace();
            }
            advisedSupport.setTargetSource(targetSource);
            advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());
            advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher());
            advisedSupport.setProxyTargetClass(false);

            return new ProxyFactory(advisedSupport).getProxy();

        }

        return null;
    }
    
}
  • 這個 DefaultAdvisorAutoProxyCreator 類的主要核心實現在於 postProcessBeforeInstantiation 方法中,從通過 beanFactory.getBeansOfType 獲取 AspectJExpressionPointcutAdvisor 開始。
  • 獲取了 advisors 以後就可以遍歷相應的 AspectJExpressionPointcutAdvisor 填充對應的屬性資訊,包括:目標物件、攔截方法、匹配器,之後返回代理物件即可。
  • 那麼現在呼叫方獲取到的這個 Bean 物件就是一個已經被切面注入的物件了,當呼叫方法的時候,則會被按需攔截,處理使用者需要的資訊。

五、測試

1. 事先準備

public class UserService implements IUserService {

    public String queryUserInfo() {
        try {
            Thread.sleep(new Random(1).nextInt(100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "小傅哥,100001,深圳";
    }

    public String register(String userName) {
        try {
            Thread.sleep(new Random(1).nextInt(100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "註冊使用者:" + userName + " success!";
    }

}
  • 在 UserService 中提供了2個不同方法,另外你還可以增加新的類來加入測試。後面我們的測試過程,會給這個兩個方法新增我們的攔截處理,列印方法執行耗時。

2. 自定義攔截方法

public class UserServiceBeforeAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("攔截方法:" + method.getName());
    }

}
  • 與上一章節的攔截方法相比,我們不在是實現 MethodInterceptor 介面,而是實現 MethodBeforeAdvice 環繞攔截。在這個方法中我們可以獲取到方法的一些資訊,如果還開發了它的 MethodAfterAdvice 則可以兩個介面一起實現。

3. spring.xml 配置 AOP

<beans>

    <bean id="userService" class="cn.bugstack.springframework.test.bean.UserService"/>

    <bean class="cn.bugstack.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

    <bean id="beforeAdvice" class="cn.bugstack.springframework.test.bean.UserServiceBeforeAdvice"/>

    <bean id="methodInterceptor" class="cn.bugstack.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor">
        <property name="advice" ref="beforeAdvice"/>
    </bean>

    <bean id="pointcutAdvisor" class="cn.bugstack.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
        <property name="expression" value="execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))"/>
        <property name="advice" ref="methodInterceptor"/>
    </bean>

</beans>
  • 這回再使用 AOP 就可以像 Spring 中一樣,通過在 xml 中配置即可。因為我們已經把 AOP 的功能融合到 Bean 的生命週期裡去了,你的新增攔截方法都會被自動處理。

4. 單元測試

@Test
public void test_aop() {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
    IUserService userService = applicationContext.getBean("userService", IUserService.class);
    System.out.println("測試結果:" + userService.queryUserInfo());
}
  • 在單元測試中你只需要按照正常獲取和使用 Bean 物件即可,不過這個時候如果被切面攔截了,那麼其實你獲取到的就是對應的代理物件裡面的處理操作了。

測試結果

攔截方法:queryUserInfo
測試結果:小傅哥,100001,深圳

Process finished with exit code 0
  • 通過測試結果可以看到,我們已經讓攔截方法生效了,也不需要自己手動處理切面、攔截方法等內容。截圖上可以看到,這個時候的 IUserService 就是一個代理物件

六、總結

  • 本章節實現 AOP 功能的外在體現主要是把以前自己在單元測試中的切面攔截,交給 Spring 的 xml 配置了,也就不需要自己手動處理了。那麼這裡有一個非常重要的知識點,就是把相應的功能如何與 Spring 的 Bean 生命週期結合起來,本章節用到的 BeanPostProcessor,因為它可以解決在 Bean 物件執行初始化方法之前,用於修改新例項化 Bean 物件的擴充套件點,所以我們也就可以處理自己的 AOP 代理物件邏輯了。
  • 一個功能的實現往往包括核心部分、組裝部分、連結部分,為了這些各自職責的分工,則需要建立介面和類,由不同關係的繼承、實現進行組裝。只有明確了各個職責分工,才好靈活的擴充套件相應的功能邏輯,否則很難駕馭大型系統的開發和建設,也就是那種不好把握的感覺。
  • 目前我們實現的 AOP 與 Spring 原始碼中的核心邏輯是類似的,但更會偏簡單一些,也不會考慮更多的複雜場景遇到的問題,包括是否有建構函式、是否為代理中的切面等。其實也可以看出只要是 Java 中的一些特性,都需要在真實使用的 Spring 中進行完整的實現,否則在使用這些功能的時候就會遇到各種問題。

七、系列推薦

相關文章