淺析Spring AOP

    在正常的業務流程中,每每存在着一些業務邏輯,例如安全審計、日誌管理,它們存在於每個業務中,然而卻和實際的業務邏輯沒有太強的關聯關係。正則表達式

        

                   圖1spring

  這些邏輯咱們稱爲橫切邏輯。若是把橫切的邏輯代碼寫在業務代碼中,散落在各個地方,則會變得很是難以維護,代碼也會顯得過於臃腫。express

  Spring AOP爲處理這些問題提供了一種很好的方法。安全

1 AOP術語

  1 通知(advice)。通知是指真正執行的目標邏輯。例如爲系統記日誌,那麼通知作的事情,就是記錄日誌。app

  2 鏈接點(joinpoit)。鏈接點指什麼時候執行advice的代碼。系統中若是想要爲全部的start()方法執行以前,記錄一條"our system is starting"的日誌,那麼,start()方法被調用的時機,稱爲一個鏈接點。ide

  3 切點(pointcut)。切點指何處執行advice的代碼。例如指定特定的一個路徑下的start()方法被調用時,執行advice的代碼,那麼,這個特定的路徑能夠理解爲切點。一般咱們使用正則表達式定義匹配的類和方法來肯定切點。函數

  4 切面(aspect)。 切面是通知、切點和鏈接點的所有內容。它定義了什麼時候在何處,完成什麼功能。源碼分析

  5 織入(weaving)。織入指實際代碼執行到切點時,完成對目標對象代理的過程。織入有以下三種方式:學習

    a.編譯器織入。須要特殊編譯器支持,例如AspectJ的織入編譯器,在編譯期間將代碼織入。測試

    b.類加載時。目標類在載入JVM時將切面引入。須要特殊的classLoader支持,改變目標類的字節碼,增長新的動做。

    c.運行期。在運行期運用動態代理技術,代理目標類。Spring AOP就是使用這種方式。

2 Spring對AOP的支持

  Spring對AOP提供了多種支持,大致上分爲2種思路。

  1 使用動態代理,在運行期對目標類生成代理對象。代理類封裝了目標類,並攔截被通知的方法的調用,再將調用轉發給真正的目標類方法調用。在代理類中,能夠執行切面邏輯。基於這種模式,僅限於對方法的攔截。若是想要實現更細粒度的攔截,例如特定的字段的修改,則須要使用第2種思路了。

  2 使用AspectJ特有的AOP語言。這種方式的成本在於,須要學習AspectJ的語法。

  大多數狀況下,第一種思路已經能夠知足咱們的需求。

3 Demo

  咱們可使用execution指示器來指示切點。如圖2

  

                   圖2

  execution指示器具體指定了哪一個包下面哪一個類執行什麼方法時,會被織入橫切邏輯。Spring AOP的demo網上有不少,可是原理其實都同樣。這裏貼出其中一種。

  咱們須要2個類,一個類(Biz)用做正常的業務邏輯,一個類用做橫切(AdviceTest)。同時寫一個類Main測試

  1.Biz

1 public class Biz {
2     public void doBiz(){
3         System.out.println("doing biz");
4     }
5 }
View Code

  2.AdviceTest

 1 public class AdviceTest implements Advice, MethodInterceptor {
 2 
 3     @Override
 4     public Object invoke(MethodInvocation invocation) throws Throwable {
 5         advice();
 6         invocation.proceed();
 7         return null;
 8     }
 9 
10     public void advice(){
11         System.out.println("this is a advice");
12     }
13 
14 }
View Code

  3.Main

1 public class Main {
2     public static void main(String[] args) {
3         ApplicationContext applicationContext = new ClassPathXmlApplicationContext(new String[]{"config/spring/spring-aop.xml"});
4         Biz biz = (Biz)applicationContext.getBean("biz");
5         biz.doBiz();
6     }
7 }
View Code

  4.Spring配置

 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <beans
 3         xmlns="http://www.springframework.org/schema/beans"
 4         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 5         xmlns:aop="http://www.springframework.org/schema/aop"
 6         xsi:schemaLocation="
 7             http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
 8             http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
 9             ">
10 
11     <bean id="adviceTest" class="aop.AdviceTest"/>
12     
13     <bean id="biz" class="aop.Biz"/>
14     
15     <aop:config>
16         <aop:pointcut id="bizPointCut" expression="execution(* aop.Biz.*(..))"/>
17         <aop:advisor pointcut-ref="bizPointCut" advice-ref="adviceTest"/>
18     </aop:config>
19 
20 </beans>
View Code

  執行Main函數代碼,輸出以下 

this is a advice
doing biz

  可見,實際的biz邏輯已經通過加強。

4 源碼分析

  Spring提供了兩種方式來生成代理對象: JDKProxy和Cglib,具體使用哪一種方式生成由AopProxyFactory根據AdvisedSupport對象的配置來決定。默認的策略是若是目標類是接口,則使用JDK動態代理技術,不然使用Cglib來生成代理。在咱們的例子中,顯然使用的是Cglib。如圖3

  

                           圖3

  爲何咱們配置了Biz的bean,獲得的卻不是Biz的實例呢?這和Spring對xml文件的標籤解析策略有關。對於AOP相關的bean的解析,在AopNamespaceHandler類裏面有相關代碼,感興趣的同窗能夠去研究下。

  咱們繼續往下面單步調試,發現進入了CglibAopProxy類裏面。

 1 public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
 2             Object oldProxy = null;
 3             boolean setProxyContext = false;
 4             Class<?> targetClass = null;
 5             Object target = null;
 6             try {
 7                 if (this.advised.exposeProxy) {
 8                     // Make invocation available if necessary.
 9                     oldProxy = AopContext.setCurrentProxy(proxy);
10                     setProxyContext = true;
11                 }
12                 // May be null. Get as late as possible to minimize the time we
13                 // "own" the target, in case it comes from a pool...
14                 target = getTarget();
15                 if (target != null) {
16                     targetClass = target.getClass();
17                 }
18                 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
19                 Object retVal;
20                 // Check whether we only have one InvokerInterceptor: that is,
21                 // no real advice, but just reflective invocation of the target.
22                 if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
23                     // We can skip creating a MethodInvocation: just invoke the target directly.
24                     // Note that the final invoker must be an InvokerInterceptor, so we know
25                     // it does nothing but a reflective operation on the target, and no hot
26                     // swapping or fancy proxying.
27                     Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
28                     retVal = methodProxy.invoke(target, argsToUse);
29                 }
30                 else {
31                     // We need to create a method invocation...
32                     retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
33                 }
34                 retVal = processReturnType(proxy, target, method, retVal);
35                 return retVal;
36             }
37             finally {
38                 if (target != null) {
39                     releaseTarget(target);
40                 }
41                 if (setProxyContext) {
42                     // Restore old proxy.
43                     AopContext.setCurrentProxy(oldProxy);
44                 }
45             }
46         }
View Code

  無恥的把代碼直接貼出來。咱們重點關注下下面這一行

   retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed(); 

  在本文例子的場景下,會在此建立一個CglibMethodInvocation執行上下文,並執行proceed()方法。

                         圖4

最終執行AdviceTest中invoke()方法的調用。在AdviceTest的invoke方法中,咱們能夠自定義本身想要執行的加強邏輯invocation.proceed()來執行目標類的方法。

  

                       圖5

5 總結

  Spring AOP爲許多與業務無關的邏輯的執行,提供了一種很好的解決思路。本文也只是拋磚引玉,在實際的Spring源碼中,仍是比較複雜的,還須要細細研究才行。

 

參考文獻:

 《Spring In Action》

 《Spring源碼深度解析》

  

做者: mayday芋頭
本博客中未標明轉載的文章歸做者mayday芋頭和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。
相關文章
相關標籤/搜索