曹工說Spring Boot源碼(16)-- Spring從xml文件里到底得到了什么(aop:config完整解析【上】)


寫在前面的話

相關背景及資源:

曹工說Spring Boot源碼(1)-- Bean Definition到底是什么,附spring思維導圖分享

曹工說Spring Boot源碼(2)-- Bean Definition到底是什么,咱們對着接口,逐個方法講解

曹工說Spring Boot源碼(3)-- 手動注冊Bean Definition不比游戲好玩嗎,我們來試一下

曹工說Spring Boot源碼(4)-- 我是怎么自定義ApplicationContext,從json文件讀取bean definition的?

曹工說Spring Boot源碼(5)-- 怎么從properties文件讀取bean

曹工說Spring Boot源碼(6)-- Spring怎么從xml文件里解析bean的

曹工說Spring Boot源碼(7)-- Spring解析xml文件,到底從中得到了什么(上)

曹工說Spring Boot源碼(8)-- Spring解析xml文件,到底從中得到了什么(util命名空間)

曹工說Spring Boot源碼(9)-- Spring解析xml文件,到底從中得到了什么(context命名空間上)

曹工說Spring Boot源碼(10)-- Spring解析xml文件,到底從中得到了什么(context:annotation-config 解析)

曹工說Spring Boot源碼(11)-- context:component-scan,你真的會用嗎(這次來說說它的奇技淫巧)

曹工說Spring Boot源碼(12)-- Spring解析xml文件,到底從中得到了什么(context:component-scan完整解析)

曹工說Spring Boot源碼(13)-- AspectJ的運行時織入(Load-Time-Weaving),基本內容是講清楚了(附源碼)

曹工說Spring Boot源碼(14)-- AspectJ的Load-Time-Weaving的兩種實現方式細細講解,以及怎么和Spring Instrumentation集成

曹工說Spring Boot源碼(15)-- Spring從xml文件里到底得到了什么(context:load-time-weaver 完整解析)

工程代碼地址 思維導圖地址

工程結構圖:

概要

本篇是spring源碼的第16篇,前面已經把context命名空間下,常用的幾個元素講解差不多了,包括:

context:property-placeholder
context:property-override
context:annotation-config
context:component-scan
context:load-time-weaver

接下來,着重講解aop命名空間。該命名空間下,有以下幾個元素:

<aop:config></aop:config>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<aop:scoped-proxy></aop:scoped-proxy>

本講講解aop:config,在沒有注解的時代,大家的aop就是這么配的,如下所示:

<!--目標對象-->
    <bean id="performer" class="foo.Performer"/>

    <!--切面-->
    <bean id="performAspect" class="foo.PerformAspect"/>

    <!--配置切入點-->
    <aop:config>
        <aop:pointcut id="mypointcut" expression="execution(public * foo.Perform.sing(..))"/>

        <aop:aspect ref="performAspect">
            <aop:after method="afterPerform" pointcut-ref="mypointcut"/>
        </aop:aspect>
    </aop:config>

大家可能覺得xml落伍了,沒錯,我也這么覺得,但我深入了解后發現,通過注解配置切面,和通過xml配置切面,最終其實殊途同歸,最終都轉變為了內部結構List<org.springframework.aop.Advisor>,無非是讀取配置的方式不同。

public interface Advisor {
   
   Advice getAdvice();
}

Advice這個東西,通俗來說是"通知",我截取了spring官網說明:

  • Join point: A point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.
  • Advice: Action taken by an aspect at a particular join point. Different types of advice include “around”, “before” and “after” advice. (Advice types are discussed later.) Many AOP frameworks, including Spring, model an advice as an interceptor and maintain a chain of interceptors around the join point.

大概翻譯就是:

連接點:程序執行過程中的一個執行點,比如方法調用,或者異常處理。在spring aop里,永遠指方法調用

通知:切面在一個特定的連接點所采取的動作。advice包含了多種類型,包括"around"、“before”、“after”。很多aop框架,包括spring,將advice建模為一個攔截器,在切點處維護一個攔截器鏈。

Advice,在文檔里,都是說,表示的是在連接點所采取的動作,比如性能檢測、記錄日志、事務等。

但是,我要說明的是,在源碼里,是不太一樣的。針對前面提到的各種類型的advice,"around"、“before”、“after”等,其在spring里,是使用以下幾個類來表示的。

我們隨便找個org.springframework.aop.aspectj.AspectJAfterAdvice來看看,這個是代表after類型的advice:

public AspectJAfterAdvice(
      Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {

   super(aspectJBeforeAdviceMethod, pointcut, aif);
}

以上是該類,唯一的構造函數,其中將3個參數,直接傳給了父類的構造函數。

	protected final Method aspectJAdviceMethod;

	private final AspectJExpressionPointcut pointcut;

	private final AspectInstanceFactory aspectInstanceFactory;

public AbstractAspectJAdvice(
      Method aspectJAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aspectInstanceFactory) {
   this.aspectJAdviceMethod = aspectJAdviceMethod;
   this.pointcut = pointcut;
   this.aspectInstanceFactory = aspectInstanceFactory;
}

這里面,三個字段,其中,aspectJAdviceMethod就是我們的通知方法,其類型是JDK里的Method,即我們自定義的那些性能、日志等aop業務邏輯所在之處;pointcut,代表了切點,我們默認寫的表達式是使用AspectJ的語法寫的,想想,是不是不會寫的時候,有時候查着查着,就查到aspectJ的官網去了;aspectInstanceFactory,里面封裝了切面對象。

這幾個屬性,有啥關系?

切面 = 切點 + 通知,即,在什么時間,干什么事。

那用這幾個屬性,怎么表達呢? 簡單來說,是不是,在每個方法執行時,匹配是否和pointcut匹配,如果匹配,則執行:

return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);

沒錯,上面那個代碼其實就是spring里來的,spring在"干什么事"這部分,就是這么做的:

#org.springframework.aop.aspectj.AbstractAspectJAdvice
protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
		Object[] actualArgs = args;
		if (this.aspectJAdviceMethod.getParameterTypes().length == 0) {
			actualArgs = null;
		}
		try {
			ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
			return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
		}
	}

說了半天,主要就是說,spring aop源碼里的advice,不只是文檔里提到的advice,而是包含了完整的切點和切面的邏輯,這里的advice,其實也是狹義的,即spring aop里的方法級別的advice

啰嗦了半天,我們馬上進入正題。

使用

源碼見:spring-aop-xml-demo

目標類和接口如下:

package foo;


public class Performer implements Perform {
    @Override
    public void sing() {
        System.out.println("男孩在唱歌");

    }
}

package foo;

public interface Perform {
    void sing();
}

切面如下:

package foo;

public class PerformAspect {

    public void afterPerform() {
        System.out.println("表演之后要行禮");
    }
}

xml配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">


    <!--目標對象-->
    <bean id="performer" class="foo.Performer"/>

    <!--切面-->
    <bean id="performAspect" class="foo.PerformAspect"/>

    <!--配置切入點-->
    <aop:config>
        <aop:pointcut id="mypointcut" expression="execution(public * foo.Perform.sing(..))"/>

        <aop:aspect ref="performAspect">
            <aop:after method="afterPerform" pointcut-ref="mypointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

測試代碼如下:

package foo;

public final class Main {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                "context-namespace-test-aop.xml");
        // json輸出bean definition
        List<BeanDefinition> list =
                ctx.getBeanFactory().getBeanDefinitionList();
        MyFastJson.printJsonStringForBeanDefinitionList(list);
        
        Perform performer = (Perform) ctx.getBean(Perform.class);
        performer.sing();
    }
}

執行結果如下:

男孩在唱歌
表演之后要行禮

簡略源碼說明

上面那個例子,很簡單就實現了aop,但是spring為此做了很多工作,總結起來,有以下幾步:

步驟1:解析xml文件,獲取bean definition

bean definition 中bean class 備注
PerformAspect 通知
Performer 要切的目標
AspectJExpressionPointcut 切點,即<aop:pointcut />那一行
org.springframework.aop.aspectj.AspectJPointcutAdvisor advisor,請翻到文章開頭,實際表達一個:切點+切面方法;
AspectJAwareAdvisorAutoProxyCreator 實現了BeanPostProcessor接口,在spring getBean過程中,檢查是否匹配切點,匹配則創建代理,並使用代理對象替換ioc容器中真實的bean

步驟2:AspectJAwareAdvisorAutoProxyCreator 狸貓換太子

這個bean definition,本來也很普通,但讓它變得不普通的是,這個bean class,實現了BeanPostProcessor接口。BeanPostProcessor接口的功能,看下面就明白:

public interface BeanPostProcessor {

   Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
   
   Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

這兩個方法,在spring創建bean時,被調用,一個是在初始化之前,一個是初始化之后。

關於生命周期,大家可以看上圖,一定要搞明白,什么叫實例化,什么叫初始化,什么叫屬性注入,我們這里,

AspectJAwareAdvisorAutoProxyCreator 生效的地方,主要是在 初始化之后。它實現了postProcessAfterInitialization方法,這個方法,其return的結果,就會取代原有的bean,來存放到ioc容器中。

后續,如果有其他bean,依賴這個bean的話,拿到的也是代理之后的了。

大家可以看看其實現:

AspectJAwareAdvisorAutoProxyCreator.java
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
	if (bean != null) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		if (!this.earlyProxyReferences.contains(cacheKey)) {
		     // 這里根據原來的bean,創建動態代理,並返回給ioc容器,完成狸貓換太子操作
			return wrapIfNecessary(bean, beanName, cacheKey);
		}
	}
	return bean;
}

而我們創建的代理對象,其實是會包含要應用的advisor的,大家看下圖,其中specificInterceptors的第二個元素,就是前面我們步驟1解析的那個bean definition。

當然了,大家熟知的,有接口時創建jdk代理,沒接口時創建cglib代理,就是在這個步驟發生的,下一篇會細講。

步驟3:花非花,霧非霧

運行時,看似調用target,實際調用代理的對應方法:

可以看到,這里拿到的,已經是代理對象,而不是真實對象了,調用代理對象時,就會像tomcat的filter鏈那樣,tomcat是filter鏈進行鏈式處理,直到最后調用servlet;這里是interceptor鏈先挨個調用自己在target方法之前要執行的邏輯,然后調用target,最后調用要在target之后執行的邏輯。

總結

今天這篇算是aop的開胃菜,前面只說了大概的步驟,並沒有講透,詳細的源碼分析實在不適合揉到一篇來講,所以會分到下一講或兩講。

希望對大家有所幫助,謝謝。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM