相關背景及資源:html
曹工說Spring Boot源碼(1)-- Bean Definition究竟是什麼,附spring思惟導圖分享java
曹工說Spring Boot源碼(2)-- Bean Definition究竟是什麼,我們對着接口,逐個方法講解git
曹工說Spring Boot源碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,咱們來試一下spring
曹工說Spring Boot源碼(4)-- 我是怎麼自定義ApplicationContext,從json文件讀取bean definition的?shell
曹工說Spring Boot源碼(5)-- 怎麼從properties文件讀取beanexpress
曹工說Spring Boot源碼(6)-- Spring怎麼從xml文件裏解析bean的json
曹工說Spring Boot源碼(7)-- Spring解析xml文件,到底從中獲得了什麼(上)tomcat
曹工說Spring Boot源碼(8)-- Spring解析xml文件,到底從中獲得了什麼(util命名空間)框架
曹工說Spring Boot源碼(9)-- Spring解析xml文件,到底從中獲得了什麼(context命名空間上)ide
曹工說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。
囉嗦了半天,咱們立刻進入正題。
目標類和接口以下:
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爲此作了不少工做,總結起來,有如下幾步:
bean definition 中bean class | 備註 |
---|---|
PerformAspect | 通知 |
Performer | 要切的目標 |
AspectJExpressionPointcut | 切點,即
|
org.springframework.aop.aspectj.AspectJPointcutAdvisor | advisor,請翻到文章開頭,實際表達一個:切點+切面方法; |
AspectJAwareAdvisorAutoProxyCreator | 實現了BeanPostProcessor接口,在spring getBean過程當中,檢查是否匹配切點,匹配則建立代理,並使用代理對象替換ioc容器中真實的bean |
這個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代理,就是在這個步驟發生的,下一篇會細講。
運行時,看似調用target,實際調用代理的對應方法:
能夠看到,這裏拿到的,已是代理對象,而不是真實對象了,調用代理對象時,就會像tomcat的filter鏈那樣,tomcat是filter鏈進行鏈式處理,直到最後調用servlet;這裏是interceptor鏈先挨個調用本身在target方法以前要執行的邏輯,而後調用target,最後調用要在target以後執行的邏輯。
今天這篇算是aop的開胃菜,前面只說了大概的步驟,並無講透,詳細的源碼分析實在不適合揉到一篇來說,因此會分到下一講或兩講。
但願對你們有所幫助,謝謝。