Spring框架研究總結之AOP(一)

Spring框架研究總結之AOP(一)

 

Spring是一個開源的企業應用框架,是一個分層架構,由7個定義穩定的模塊所組成,而所有的模塊都是在覈心容器基礎上搭建;其體系本身提供了強大的IOC(控制反轉)、AOP(面向切面)以及DI(依賴注入)等較顯著的功能,那麼下面就來詳細介紹下Spring框架的模塊組成及相關特點。

 

l   什麼是AOP

l   場景及概念

l   AOP的使用

 

一、什麼是AOP

AOP(Aspect-Oriented Programming),即爲面向切面或方面編程,被認爲是OOP(Object-OrientedProgramming),面向對象編程的拓展和完善。OOP引入了繼承、封裝及多態等特性來搭建一種對象層次結構,是垂直方向上的層級結構;而當我們要爲分散的對象引入公共行爲時,OOP則顯得力不從心,也就是說OOP允許你定義從上到下的邏輯關係,卻不適合定義從左到右的邏輯關係,比如:異常處理,它散佈在所有對象結構中,這種散佈在各處的無關代碼片段被稱之爲橫切代碼片段,在OOP中導致了大量重複的代碼,不利於各個模塊的重用。而AOP則相反,它使用了橫切技術,剖開封裝對象的內部,並將那些影響了多個類的公共行爲封裝到了重用的模塊,稱之爲“Aspect”,即方面。

 

所謂的的方面,可理解爲將與業務無關,卻被業務模塊共同調用的邏輯或責任鏈封裝起來,減少系統代碼的重複,降低模塊間的耦合度,並有利於日後的系統拓展和維護。有人做了個有趣的比喻:如果說“對象”是一個空心的圓柱體,其中封裝的是對象的屬性和行爲;那麼面向方面編程的方法,就彷彿一把利刃,將這些空心圓柱體剖開,以獲得其內部的消息。而剖開的切面,也就是所謂的“方面”,然後它又以巧奪天功的妙手將這些剖開的切面復原,不留痕跡。

 

實現AOP的技術,主要分爲兩大類:一是採用動態代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行爲的執行;二是採用靜態織入的方式,引入特定的語法創建“方面”,從而使得編譯器可以在編譯期間織入有關“方面”的代碼。

 

二、場景及概念

1、一般使用的場景

事務、權限、緩存、錯誤處理、記錄跟蹤、性能優化、持久化、同步處理及資源池等場景。

 

2、幾個重要感概念

A、方面(Aspect

某個關注點的模塊化或片面化,該關注點可以橫切多個對象,而事務機制是J2EE應用中一個很好的橫切關注點例子,在Spring中,使用advisor或攔截器來實現面向方面功能的。

 

B、通知(Advice

在某個關注點或是連接點上執行的動作行爲,各種類型的通知,包括around、before、after及throws通知。大部分aop框架,包括spring框架,都是以攔截器模型來實現連接通知實現的,維護一個被圍繞的連接點攔截器鏈子。在Spring中有4個通知advice,分別爲:BeforeAdvice、AfterAdvice、ThrowAdvice及DynamicInstroductionAdvice,後面回詳細介紹它們。

 

C、切入點(Pointcut

一個通知將被引發的一系列連接點的集合,而aop框架允許開發者指定切入點:例如,使用正則表達式。 Spring定義了Pointcut接口,用來組合MethodMatcher和ClassFilter,可以通過名字很清楚的理解, MethodMatcher是用來檢查目標類的方法是否可以被應用此通知,而ClassFilter是用來檢查Pointcut是否應該應用到目標類上。

 

D、連接點(Introduction)

指的是,程序執行過程中明確的點,如方法的調用或特定的異常拋出。

 

E、引入

添加方法或字段到被通知的類。 Spring允許引入新的接口到任何被通知的對象。例如,你可以使用一個引入使任何對象實現 IsModified接口,來簡化緩存。在Spring中要使用Introduction, 可有通過DelegatingIntroductionInterceptor來實現通知,通過DefaultIntroductionAdvisor來配置Advice和代理類要實現的接口。

 

F、目標對象(Target Object)

包含連接點的對象。也被稱作被通知或被代理對象。

 

G、Aop代理(Aop Proxy)

AOP框架創建的對象,包含通知。在Spring中,AOP代理可以是JDK動態代理或者CGLIB代理。

 

H、織入

組裝方面來創建一個被通知對象。這可以在編譯時完成(例如使用AspectJ編譯器),也可以在運行時完成。Spring和其他純Java AOP框架一樣,在運行時完成織入。

 

三、AOP的使用

Spring AOP(面向方面編程)框架,用於在模塊化方面的橫切關注點,是一個攔截器攔截一些動作,例如,當一個方法執行時,Spring AOP可以劫持一個執行的方法,在方法執行之前或之後添加額外的功能。具體請看下面的例子驗證,對於重複性的代碼,這裏不會列出,請讀者仔細研讀並瞭解整體代碼結構。

 

1、Advice通知

在Spring AOP中,目前有4種類型的通知支持:

• 通知(Advice)之前 - 該方法執行前運行;

• 通知(Advice)返回之後 – 運行後,該方法返回一個結果;

• 通知(Advice)拋出之後 – 運行方法拋出異常後;

• 環繞通知 – 環繞方法執行運行,結合以上這三個通知;

 

接下來,就來演示下如何實現上面的AOP通知支持。我們先創建一個Java項目,目錄結構如下:

 

A、方法執行之前(MethodBeforeAdvice

目標對象:

public class CustomerService{

    private String name;

    private String url;

   

    public void setName(String name) {

       this.name = name;

    }

   

    public void setUrl(String url) {

       this.url = url;

    }

   

    public void printName() {

       System.out.println("Customer name:" + this.name);

       System.out.println("--------------------------");

    }

   

    public void printUrl() {

       System.out.println("Customer url:" + this.url);

       System.out.println("--------------------------");

    }

   

    public voidprintThrowException() {

       System.out.println("Exception err throw!");

       System.out.println("--------------------------");

       throw newIllegalArgumentException();

    }

}

 

織入方法:

public class CustomBeforeMethodimplements MethodBeforeAdvice {

    @Override

    public void before(Method method, Object[] args, Object target) throws Throwable {

       System.out.println("CustomBeforeMethod:Before method becalled!");

    }

}

 

XML配置:

<!-- target object  -->

    <bean id="customerService"class="com.spring.aop.bean.CustomerService">

       <property name="name"value="Jackey" />

       <property name="url"value="http://blog.csdn.net/why_2012_gogo"/>

    </bean>

 

<!-- aop of before call the aopmethods -->

    <bean id="customBeforeMethod"class="com.spring.aop.impls.CustomBeforeMethod"/>

 

<!-- proxy of aop listenerand callback -->

    <bean id="customerServiceProxy"class="org.springframework.aop.framework.ProxyFactoryBean">

       <property name="target"ref="customerService" />

       <property name="interceptorNames">

           <list>

              <value>customBeforeMethod</value>

              <value>customThrowMethod</value>

           </list>

       </property>

    </bean>

 

如何驗證:

public class App {

    private staticApplicationContext context;

 

    public static void main(String[] args) {

       context = new ClassPathXmlApplicationContext("com/spring/aop/config/applicationContext.xml");

      

       try {

           CustomerServicecs = (CustomerService) context.getBean("customerServiceProxy");

           cs.printName();

           cs.printUrl();

           cs.printThrowException();

       }catch(Exception e) {

       }

    }

}

 

執行結果:

 

從上圖看出,在執行目標對象的每個方法之前都會先執行織入的方法的執行,然後再執行目標對象自身的方法;同時,在拋出異常之後,執行了ThrowsAdvice接口實現類的異常方法。

 

B、方法返回之後(AfterReturningAdvice

織入方法:

public classCustomAfterMethod implements AfterReturningAdvice{

 

    @Override

    public void afterReturning(ObjectreturnValue, Method method, Object[] args,Object target) throws Throwable {

       System.out.println("CustomAfterMethod:After method becalled!");

    }

}

 

XML配置:

<!-- aop of after call the aopmethods -->

    <bean id="customAfterMethod"class="com.spring.aop.impls.CustomAfterMethod"/>

 

 

<!-- proxy of aop listenerand callback -->

    <bean id="customerServiceProxy"class="org.springframework.aop.framework.ProxyFactoryBean">

       <property name="target"ref="customerService" />

       <property name="interceptorNames">

           <list>

              <value>customAfterMethod</value>

              <value>customThrowMethod</value

           </list>

       </property>

    </bean>

 

執行結果:

 

同樣,在目標類中每個方法執行之後,織入的方法功能才執行。

 

C、拋出異常之後(ThrowsAdvice

織入方法:

public classCustomAfterMethod implementsAfterReturningAdvice {

 

    @Override

    public voidafterReturning(Object returnValue, Method method, Object[] args,Object target) throws Throwable {

       System.out.println("CustomAfterMethod:After method becalled!");

    }

}

 

XML配置:

<!-- aop of throws call the aopmethods -->

    <bean id="customThrowMethod"class="com.spring.aop.impls.CustomThrowMethod"/>

 

執行結果:

在上面的結果圖中,已經羅列出。

 

D、方法執行前後(MethodInterceptor

織入方法:

public classCustomAroundMethod implements MethodInterceptor {

 

   @Override

   public Object invoke(MethodInvocation methodInvocation) throws Throwable { 

      // same with MethodBeforeAdvice

      System.out.println("CustomAroundMethod : Before methodcalled!");

     

      try {

         // proceed to original method

         Objectresult = methodInvocation.proceed();

         // same with AfterReturningAdvice

         System.out.println("CustomAroundMethod : Before aftercalled!");

         return result;

      } catch(IllegalArgumentException e) {

         // same with ThrowsAdvice

         System.out.println("CustomAroundMethod : Throw exceptioncalled!");

         throw e;

      }

   }

}

 

XML配置:

<!-- aop of around call the aopmethods -->

    <bean id="customAroundMethod"class="com.spring.aop.impls.CustomAroundMethod"/>

 

<!-- proxy of aop listenerand callback -->

    <bean id="customerServiceProxy"class="org.springframework.aop.framework.ProxyFactoryBean">

       <property name="target"ref="customerService" />

       <property name="interceptorNames">

           <list>

              <value>customAroundMethod</value>

           </list>

       </property>

    </bean>

 

執行結果:

 

2、Advisor攔截

這裏我們引入了“切入點”概念,並結合Advisor攔截機制,同樣實現上面的通知效果,而且我們可以執行攔截的目標對象的方法,具體如下:

 

A、目標對象

public class CustomerService{

    private String name;

    private String url;

   

    public void setName(String name) {

       this.name = name;

    }

   

    public void setUrl(String url) {

       this.url = url;

    }

   

    public void printName() {

       System.out.println("Customer name:" + this.name);

       System.out.println("--------------------------");

    }

   

    public void printUrl() {

       System.out.println("Customer url:" + this.url);

       System.out.println("--------------------------");

    }

   

    public voidprintThrowException() {

       System.out.println("Exception err throw!");

       System.out.println("--------------------------");

       throw new IllegalArgumentException();

    }

}

 

B、XML配置

目標對象:

<!-- target object  -->

    <bean id="customerService"class="com.spring.aop.bean.CustomerService">

       <property name="name"value="Jackey" />

       <property name="url"value="http://blog.csdn.net/why_2012_gogo"/>

    </bean>

 

織入功能:

<!-- aop of before call the aopmethods -->

    <bean id="customBeforeMethod"class="com.spring.aop.impls.CustomBeforeMethod"/>

 

代理攔截:

<!-- proxy of aop listenerand callback -->

    <bean id="customerServiceProxy"class="org.springframework.aop.framework.ProxyFactoryBean">

       <property name="target"ref="customerService" />

       <property name="interceptorNames">

           <list>

              <!--<value>customBeforeMethod</value> -->

              <!--<value>customAfterMethod</value>

              <value>customThrowMethod</value>-->

              <!--<value>customAroundMethod</value> -->

              <value>customerAdvisor</value>

           </list>

       </property>

    </bean>

 

切入點:

<!-- name match method pointcutof aop -->

    <bean id="customerMethodPointcut"class="org.springframework.aop.support.NameMatchMethodPointcut">

       <property name="mappedName"value="printName" />

    </bean>

 

攔截器:

<!-- advisor match pointcut of aop-->

    <bean id="customerAdvisor"class="org.springframework.aop.support.DefaultPointcutAdvisor">

        <property name="pointcut"ref="customerMethodPointcut" />

       <property name="advice"ref="customBeforeMethod" />

    </bean>

 

C、織入功能

這裏我們就以攔截目標對象的切入點方法執行之前的織入爲例,其它攔截實現是相同的,這裏不再贅述,具體如下:

public classCustomBeforeMethod implementsMethodBeforeAdvice {

    @Override

    public void before(Method method, Object[] args, Object target) throws Throwable {

       System.out.println("CustomBeforeMethod:Before method becalled!");

    }

}

 

D、如何驗證

public class App {

    private staticApplicationContext context;

 

    public static void main(String[] args) {

       context = new ClassPathXmlApplicationContext("com/spring/aop/config/applicationContext.xml");

      

       try {

           CustomerServicecs = (CustomerService) context.getBean("customerServiceProxy");

           cs.printName();

           cs.printUrl();

           cs.printThrowException();

       }catch(Exception e) {

       }

    }

}

 

E、執行結果

 

從上圖可以看出,我們只攔截了目標對象的printName()方法,所以只在執行該方法之前,纔會執行織入的功能實現。

 

另外,上面的攔截器是根據目標對象的織入方法的名字進行攔截的,其實還可以根據正則表達式來攔截欲織入的方法的,此時需要使用到org.springframework.aop.support.RegexpMethodPointcutAdvisor

與根據名字進行攔截不同的地方,只是攔截器和代理配置不同,具體如下:

攔截器:

<bean id="customerAdvisor2"class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">

       <property name="patterns">

           <list>

              <value>.*Url.*</value>

           </list>

       </property>

       <property name="advice"ref="customBeforeMethod" />

    </bean>

 

代理攔截:

<!-- proxy of aop listenerand callback -->

    <bean id="customerServiceProxy"class="org.springframework.aop.framework.ProxyFactoryBean">

       <property name="target"ref="customerService" />

       <property name="interceptorNames">

           <list>

              <value>customerAdvisor2</value>

           </list>

       </property>

    </bean>

 

執行結果:

 

由於攔截器匹配的規則爲.*Url.* ,所以只有目標對象中的printUrl()方法纔會被攔截,從上圖也看出了,在執行目標對象方法printUrl()之前,先執行了織入的printUrl功能方法。 

 

3、自動代理

在前面介紹的通知的攔截,是通過手動設置ProxyFactoryBean代理工廠來實現的過濾和攔截功能,如果存在很多需要被攔截的目標對象,使用手動創建攔截會比較麻煩和不利於維護,因此可以使用Spring的自動代理創建者,來自動實現同樣的功能,當然,如果攔截的對象不是很多,建議使用前者。自動代理實現比較簡單,具體如下:

 

3.1、自動代理,結合advisor攔截

其它部分均相同,不同的是自動代理創建部分,如下:

 

XML配置:

<!-- auto proxy creator for beannames(advices) -->

    <bean id="customerServiceAutoProxy"class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">

       <property name="beanNames">

           <list>

              <value>*Service</value>

           </list>

       </property>

       <property name="interceptorNames">

           <list>

              <value>customerAdvisor2</value>

           </list>

       </property>

    </bean>

 

注意:

beanNames:正則或是全名匹配目標對象;

interceptorNames:直接引用前面創建的advisor即可;

 

如何驗證:

public class App {

    private staticApplicationContext context;

 

    public static void main(String[] args) {

       context = new ClassPathXmlApplicationContext("com/spring/aop/config/applicationContext.xml");

      

       try {

           CustomerServicecs = (CustomerService) context.getBean("customerServiceAutoProxy");

           cs.printName();

           cs.printUrl();

           cs.printThrowException();

       }catch(Exception e) {

       }

    }

}

 

此時,我們需要獲得實例id爲customerService,即目標對象本身,因爲自動代理已經自動實例化,並匹配好目標對象以及初始化了攔截器。

 

執行結果:

 

3.2、自動代理攔截

這裏要介紹的是Spring中的 DefaultAdvisorAutoProxyCreator ,它的功能可是強大的不得了,因爲有了它,你只需要配置好Advisor攔截,並指定好欲織入的功能即可,因爲其會自動搜索匹配目標對象(根據設置的攔截器匹配),並會自動創建代理Bean,完成攔截需求。

 

實現上面同樣的功能,我們只需要做下面的幾件事兒:

XML配置:

<!-- target object  -->

    <bean id="customerService"class="com.spring.aop.bean.CustomerService">

       <property name="name"value="Jackey" />

       <property name="url"value="http://blog.csdn.net/why_2012_gogo"/>

    </bean>

 

<!-- aop of before call the aopmethods -->

    <bean id="customBeforeMethod"class="com.spring.aop.impls.CustomBeforeMethod"/>

 

<bean id="customerAdvisor2"class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">

       <property name="patterns">

           <list>

              <value>.*Url.*</value>

           </list>

       </property>

       <property name="advice"ref="customBeforeMethod" />

    </bean>

 

<!-- auto default advisor proxycreator -->

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

 

如何驗證:

public class App {

    private staticApplicationContext context;

 

    public static void main(String[] args) {

       context = new ClassPathXmlApplicationContext("com/spring/aop/config/applicationContext.xml");

      

       try {

           CustomerServicecs = (CustomerService) context.getBean("customerService");

           cs.printName();

           cs.printUrl();

           cs.printThrowException();

       }catch(Exception e) {

       }

    }

}

 

執行結果:

 

 

 

 

對於AspectJ與Spring AOP的整合以及AOP的事務例子,將在下篇文章中進行介紹,期望各位同學繼續跟進,Spring框架之AOP(一)就介紹到這裏,由於作者水平有限,如有問題請在評論發言或是QQ羣討論,謝謝。

 

 

 

技術討論羣:

245389109(新)

 

發佈了132 篇原創文章 · 獲贊 164 · 訪問量 47萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章