1. 程式人生 > >AOP: Spring3核心技術之AOP配置

AOP: Spring3核心技術之AOP配置

AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計(也叫面向方面),可以通過預編譯方式和執行期動態代理實現在不修改原始碼的情況下給程式動態統一新增功能的一種技術。AOP實際是GoF設計模式的延續,設計模式孜孜不倦追求的是呼叫者和被呼叫者之間的解耦,AOP可以說也是這種目標的一種實現。

主要的功能是:日誌記錄,效能統計,安全控制,事務處理,異常處理等等。

主要的意圖是:將日誌記錄,效能統計,安全控制,事務處理,異常處理等程式碼從業務邏輯程式碼中劃分出來,通過對這些行為的分離,我們希望可以將它們獨立到非指導業務邏輯的方法中,進而改

變這些行為的時候不影響業務邏輯的程式碼。

在Spring配置檔案中,所有AOP相關定義必須放在<aop:config>標籤下,該標籤下可以有<aop:pointcut>、<aop:advisor>、<aop:aspect>標籤,配置順序不可變。 

● <aop:pointcut>:用來定義切入點,該切入點可以重用; 
● <aop:advisor>:用來定義只有一個通知和一個切入點的切面; 
● <aop:aspect>:用來定義切面,該切面可以包含多個切入點和通知,而且標籤內部的通知和切入點定義是無序的;和advisor的區別就在此,advisor只包含一個通知和一個切入點。


java程式碼:

package aop;

import org.aspectj.lang.ProceedingJoinPoint;

public class Intercepter {
	public void beforeDomain() {  
        System.out.println("This is beforeDomain....");  
    }  
      
    public void afterDomain() {  
        System.out.println("This is afterDomain....");
    }  
      
    public void afterReturning() {  
        System.out.println("This is afterReturning....");  
    }  
      
    public void afterThrowing() {  
        System.out.println("This is afterThrowing....");  
    }  
      
    public Object around(ProceedingJoinPoint pjp) throws Throwable {    
        System.out.println("===========around before advice");    
        Object retVal = pjp.proceed(new Object[] {"【環繞通知】"});
        //Object retVal = pjp.proceed();
        System.out.println("===========around after advice");    
        return retVal;    
    }  
}
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:p="http://www.springframework.org/schema/p"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
	
	<bean id="beanA" class="aop.MyBeanA"/>
	<bean id="beanB" class="aop.MyBeanB"/>
	<bean id="aspectBean" class="aop.Intercepter"/>
	
	<aop:config proxy-target-class="false">
		<aop:aspect ref="aspectBean">
			<!-- 定義切點 -->
			<aop:pointcut expression="execution(public * aop.*.domain(..))" id="MyAspect"/>
			<!-- 前置通知 -->
			<aop:before pointcut-ref="MyAspect" method="beforeDomain"/>
			<!-- 後置通知 -->
			<aop:after-returning pointcut-ref="MyAspect" method="afterReturning"/>
			<aop:after-throwing pointcut-ref="MyAspect" method="afterThrowing"/>
			<aop:after pointcut-ref="MyAspect" method="afterDomain"/>
			<!-- 環繞通知 -->
			<aop:around pointcut="execution(public * aop.*.sayAround(..))" method="around"/>
		</aop:aspect>
	</aop:config>

</beans>

java程式碼:
public interface MyBean {  
    public void domain();  
} 

java程式碼:
public class MyBeanA{  
    public void domain() {  
        System.out.println("MyBeanA is executing...");  
    }  
  
    public void sayAround(String param) {    
          System.out.println("around param:" + param);    
    }  
} 

java程式碼:
public class MyBeanB implements MyBean{  
    public void domain() {  
        System.out.println("MyBeanB is executing...");  
    //throw new RuntimeException("This is a RuntimeException");  
    }  
}

測試程式碼:
package aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainTest {
	
	public static void main(String[] args) {
		ApplicationContext act = new ClassPathXmlApplicationContext("aopconfig.xml");
		
		MyBean b = (MyBean) act.getBean("beanB");
		b.domain();
		
		MyBeanA a = (MyBeanA) act.getBean("beanA",MyBeanA.class);
		a.domain();
		a.sayAround("hello around ....");
	}
}

宣告切面 
    切面就是包含切入點和通知的物件,在Spring容器中將被定義為一個Bean,xml形式的切面需要一個切面支援Bean,該支援Bean的欄位和方法提供了切面的狀態和行為資訊,並通過配置方式來指定切入點和通知實現。 
    切面使用<aop:aspect>標籤指定,ref屬性用來引用切面支援Bean。 
    切面支援Bean“aspectSupportBean”跟普通Bean完全一樣使用,切面使用“ref”屬性引用它。 

宣告切入點 
    切入點在Spring中也是一個Bean,Bean定義方式可以有很三種方式: 
● 在<aop:config>標籤下使用<aop:pointcut>宣告一個切入點Bean,該切入點可以被多個切面使用,對於需要共享使用的切入點最好使用該方式,該切入點使用id屬性指定Bean名字,在通知定義時使用pointcut-ref屬性通過該id引用切入點,expression屬性指定切入點表示式。 
● 在<aop:aspect>標籤下使用<aop:pointcut>宣告一個切入點Bean,該切入點可以被多個切面使用,但一般該切入點只被該切面使用,當然也可以被其他切面使用,但最好不要那樣使用,該切入點使用id屬性指定Bean名字,在通知定義時使用pointcut-ref屬性通過該id引用切入點,expression屬性指定切入點表示式 
● 匿名切入點Bean,可以在宣告通知時通過pointcut屬性指定切入點表示式,該切入點是匿名切入點,只被該通知使用

<aop:config>    
 <aop:aspect ref="aspectSupportBean">    
     <aop:after pointcut="execution(* cn.javass..*.*(..))" method="afterAdvice"/>    
 </aop:aspect>  
</aop:config> 

宣告通知:(前置通知,後置通知,環繞通知) 
一、前置通知:在切入點選擇的連線點處的方法之前執行的通知,該通知不影響正常程式執行流程(除非該通知丟擲異常,該異常將中斷當前方法鏈的執行而返回)。 
Spring中在切入點選擇的方法之前執行,通過<aop:aspect>標籤下的<aop:before>標籤宣告:

<aop:before pointcut="切入點表示式"  pointcut-ref="切入點Bean引用"    
     method="前置通知實現方法名" arg-names="前置通知實現方法引數列表引數名字"/>

● pointcut和pointcut-ref:二者選一,指定切入點; 
● method:指定前置通知實現方法名,如果是多型需要加上引數型別,多個用“,”隔開,如beforeAdvice(java.lang.String); 
● arg-names:指定通知實現方法的引數名字,多個用“,”分隔,可選,切入點中使用“args(param)”匹配的目標方法引數將自動傳遞給通知實現方法同名引數。

二、後置通知:在切入點選擇的連線點處的方法之後執行的通知,包括如下型別的後置通知: 
● 後置返回通知:在切入點選擇的連線點處的方法正常執行完畢時執行的通知,必須是連線點處的方法沒丟擲任何異常正常返回時才呼叫後置通知。 
在切入點選擇的方法正常返回時執行,通過<aop:aspect>標籤下的<aop:after-returning>標籤宣告:

<aop:after-returning pointcut="切入點表示式"  pointcut-ref="切入點Bean引用"    
        method="後置返回通知實現方法名"    
        arg-names="後置返回通知實現方法引數列表引數名字"    
        returning="返回值對應的後置返回通知實現方法引數名"    
/>

● 後置異常通知:在切入點選擇的連線點處的方法丟擲異常返回時執行的通知,必須是連線點處的方法丟擲任何異常返回時才呼叫異常通知。 
在切入點選擇的方法丟擲異常時執行,通過<aop:aspect>標籤下的<aop:after-throwing>標籤宣告:
<aop:after-throwing pointcut="切入點表示式"  pointcut-ref="切入點Bean引用"    
                                method="後置異常通知實現方法名"    
                                arg-names="後置異常通知實現方法引數列表引數名字"    
                                throwing="將丟擲的異常賦值給的通知實現方法引數名"/> 

● 後置最終通知:在切入點選擇的連線點處的方法返回時執行的通知,不管拋沒丟擲異常都執行,類似於Java中的finally塊。 
在切入點選擇的方法返回時執行,不管是正常返回還是丟擲異常都執行,通過<aop:aspect>標籤下的<aop:after >標籤宣告:

<aop:after pointcut="切入點表示式"  pointcut-ref="切入點Bean引用"    
                  method="後置最終通知實現方法名"    
                  arg-names="後置最終通知實現方法引數列表引數名字"/> 

三、環繞通知:環繞著在切入點選擇的連線點處的方法所執行的通知,環繞通知可以在方法呼叫之前和之後自定義任何行為,並且可以決定是否執行連線點處的方法、替換返回值、丟擲異常等等。 
環繞著在切入點選擇的連線點處的方法所執行的通知,環繞通知非常強大,可以決定目標方法是否執行,什麼時候執行,執行時是否需要替換方法引數,執行完畢是否需要替換返回值,可通過<aop:aspect>標籤下的<aop:around >標籤宣告:

<aop:around pointcut="切入點表示式"  pointcut-ref="切入點Bean引用"    
                     method="後置最終通知實現方法名"    
                     arg-names="後置最終通知實現方法引數列表引數名字"/>

環繞通知第一個引數必須是org.aspectj.lang.ProceedingJoinPoint型別,在通知實現方法內部使用ProceedingJoinPoint的proceed()方法使目標方法執行,proceed 方法可以傳入可選的Object[]陣列,該陣列的值將被作為目標方法執行時的引數。 

四、引入
    Spring允許為目標物件引入新的介面,通過在< aop:aspect>標籤內使用< aop:declare-parents>標籤進行引入,定義方式如下:

<aop:declare-parents    
          types-matching="AspectJ語法型別表示式"    
          implement-interface=引入的介面"                 
          default-impl="引入介面的預設實現"    
          delegate-ref="引入介面的預設實現Bean引用"/>  

五、Advisor
Advisor表示只有一個通知和一個切入點的切面,由於Spring AOP都是基於AOP的攔截器模型的環繞通知的,所以引入Advisor來支援各種通知型別(如前置通知等5種),Advisor概念來自於Spring1.2對AOP的支援,在AspectJ中沒有相應的概念對應。 
Advisor可以使用<aop:config>標籤下的<aop:advisor>標籤定義:
<aop:advisor pointcut="切入點表示式" pointcut-ref="切入點Bean引用"    
                     advice-ref="通知API實現引用"/>  
  
<bean id="beforeAdvice" class="cn.javass.spring.chapter6.aop.BeforeAdviceImpl"/>  
<aop:advisor pointcut="execution(* cn.javass..*.sayAdvisorBefore(..))"    
                     advice-ref="beforeAdvice"/>  

除了在進行事務控制的情況下,其他情況一般不推薦使用該方式,該方式屬於侵入式設計,必須實現通知API
<!-- 事務管理器配置,單資料來源事務 -->  
<bean id="transactionManager"  
    class="org.springframework.orm.hibernate3.HibernateTransactionManager">  
    <property name="sessionFactory" ref="sessionFactory" />  
</bean>  
  
<aop:config>  
    <aop:advisor pointcut="execution(* com.spring.test.service..*.*(..))"  
             advice-ref="txAdvice" />  
</aop:config>  
      
<tx:advice id="txAdvice" transaction-manager="transactionManager">  
    <tx:attributes>  
        <tx:method name="get*" read-only="true" />  
        <tx:method name="find*" read-only="true" />  
        <tx:method name="list*" read-only="true" />  
        <tx:method name="save*" />  
        <tx:method name="update*" />  
        <tx:method name="delete*" />  
    </tx:attributes>  
</tx:advice> 

參考: