1. 程式人生 > >Spring 面向方面程式設計 AOP

Spring 面向方面程式設計 AOP

Spring AOP(面向方面程式設計)框架,用於在模組化方面的橫切關注點。簡單得說,它只是一個攔截器,用來攔截一些過程。例如,當一個方法執行,Spring AOP 可以劫持此執行方法,在方法執行之前或之後新增額外的功能。

在Spring AOP中,有 4 種類型通知(advices)的支援:     之前通知 - 該方法執行前執行 -MethodBeforeAdvice     返回後通知 – 執行後,該方法返回一個結果     丟擲之後通知 – 執行方法丟擲異常後,     環繞通知 – 環繞方法執行執行,結合以上這三個通知。

1. 之前通知

它會在方法執行之前執行。建立一個實現 MethodBeforeAdvice 介面的類。

public class ArticleServiceMethodBeforeAdvice implements MethodBeforeAdvice {

	public void before(Method method, Object[] args, Object target)	throws Throwable {
		System.out.println("Before " + method.getName() + " of " + target.getClass().getSimpleName() + " class.");
	}
}

在 bean 配置檔案(Spring-Bean.xml)配置代理物件。

<bean id="articleDao" class="com.angelia.spring.dao.ArticleDaoImpl">
	<property name="dataSource" ref="dataSource" />
</bean>
<bean id="articleService" class="com.angelia.spring.service.ArticleServiceImpl"	scope="prototype">
	<property name="articleDao">
		<ref bean="articleDao" />
	</property>
</bean>

<bean id="articleServiceBeforeAdvice" class="com.angelia.spring.dao.aop.ArticleServiceMethodBeforeAdvice" />
<bean id="articleServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="target" ref="articleService" />
	<property name="interceptorNames">
		<list>
			<value>articleServiceBeforeAdvice</value>
		</list>
	</property>
</bean>

   ‘target’ – 定義你想攔截的bean。‘interceptorNames’ – 定義要應用這個代理/目標物件的類(通知)。

 2. 返回後通知

該方法返回一個結果之後它將執行。建立一個實現AfterReturningAdvice介面的類。

public class ArticleServiceAfterReturningAdvice implements AfterReturningAdvice {
	
	public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
		System.out.println("After return result of the " + method.getName() + " of " + target.getClass().getSimpleName() + " class.");
	}
}

bean配置檔案

<bean id="articleServiceAfterAdvice" class="com.angelia.spring.dao.aop.ArticleServiceAfterReturningAdvice" />
<bean id="articleServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="target" ref="articleService" />
	<property name="interceptorNames">
		<list>
			<value>articleServiceBeforeAdvice</value>
			<value>articleServiceAfterAdvice</value>
		</list>
	</property>
</bean>

3.丟擲後通知

它將在執行方法丟擲一個異常後。建立一個實現ThrowsAdvice介面的類,並建立一個afterThrowing方法攔截丟擲:IllegalArgumentException異常

public class ArticleServiceThrowsAdvice implements ThrowsAdvice {
	
	public void afterThrowing(IllegalArgumentException e) throws Throwable {
		System.out.println("ArticleServiceThrowsAdvice : Throw exception in ArticleServiceImpl!");
	}
}

 bean配置檔案

<bean id="articleServiceThrowsAdvice" class="com.angelia.spring.dao.aop.ArticleServiceThrowsAdvice" />
<bean id="articleServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="target" ref="articleService" />
	<property name="interceptorNames">
		<list>
			<value>articleServiceBeforeAdvice</value>
			<value>articleServiceAfterAdvice</value>
			<value>articleServiceThrowsAdvice</value>
		</list>
	</property>
</bean>

4.環繞通知

環繞通知結合了上面的三個通知,在方法執行過程中執行。建立一個實現了MethodInterceptor介面的類。

public class ArticleServiceAroundMethod implements MethodInterceptor {

	public Object invoke(MethodInvocation invocation) throws Throwable {
		
		// MethodBeforeAdvice
		System.out.println("ArticleServiceMethodBeforeAdvice...");
		try {
			// 執行原來的方法
			Object result = invocation.proceed();
			// AfterReturningAdvice
			System.out.println("ArticleServiceAfterReturningAdvice...");

			return result;

		} catch (IllegalArgumentException e) {
			// ThrowsAdvice
			System.out.println("ArticleServiceThrowsAdvice...");
			throw e;
		}
	}
}

必須呼叫“invocation.proceed();” 繼續在原來的方法執行,否則原來的方法將不會執行。

bean配置檔案

<bean id="articleServiceAroundMethod" class="com.angelia.spring.dao.aop.ArticleServiceAroundMethod" />
<bean id="articleServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="target" ref="articleService" />
	<property name="interceptorNames">
		<list>
			<!-- <value>articleServiceBeforeAdvice</value>
			<value>articleServiceAfterAdvice</value>
			<value>articleServiceThrowsAdvice</value> -->
			<value>articleServiceAroundMethod</value>
		</list>
	</property>
</bean>

在每一個 ArticleService 方法執行後,將執行 ArticleServiceAroundMethod 的 invoke() 方法。

5. Spring AOP 切入點 (Pointcut,Advisor)

上面的Spring AOP通知的例子,一個類的整個方法被自動攔截。但在大多數情況下,可能只需要一種方式來攔截部分方法,這就是為什麼引入'切入點'的原因。它允許你根據method的名字去攔截指定的method。另外,一個Pointcut必須結合一個Advisor來使用。

在Spring AOP中,有3個常用的概念,Advices、Pointcut、Advisor。

  • Advices:表示一個method執行前或執行後的動作。
  • Pointcut:表示根據method的名字或者正則表示式去攔截一個method。
  • Advisor:Advice和Pointcut組成的獨立的單元,並且能夠傳給proxy factory 物件。

前面的例子,ArticleServiceImpl中全部的method方法全部被攔截了,下邊將利用Pointcuts只攔截queryArticleById()。我們可以用名字匹配法和正則表示式匹配法去匹配要攔截的method。

切入點 - 名稱匹配

建立一個NameMatchMethodPointcut的bean,將你想攔截的方法的名字queryArticleById注入到屬性mappedName。建立一個DefaultPointcutAdvisor的advisor bean,將pointcut和advice關聯起來。

<bean id="articleServiceBeforeAdvice" class="com.angelia.spring.dao.aop.ArticleServiceMethodBeforeAdvice" />

<bean id="articlePointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
	<property name="mappedName" value="queryArticleById" />
</bean>
<bean id="articleAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
	<property name="pointcut" ref="articlePointcut" />
	<property name="advice" ref="articleServiceBeforeAdvice" />
</bean>

<bean id="articleServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="target" ref="articleService" />
	<property name="interceptorNames">
		<list>
			<!-- <value>articleServiceBeforeAdvice</value> -->
			<value>articleAdvisor</value>			
		</list>
	</property>
</bean>

以上配置中pointcut和advisor可以合併在一起配置,即不用單獨配置articlePointcut和articleAdvisor,只要配置articleAdvisor時class選擇NameMatchMethodPointcutAdvisor如下:

<bean id="articleAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
	<property name="mappedName" value="queryArticleById" />
        <property name="advice" ref="articleServiceBeforeAdvice" />
</bean>

實際上這種做法將 method 名字與具體的advice捆綁在一起,有悖於 Spring 鬆耦合理念,如果將 method 名字單獨配置成pointcut(切入點),advice和pointcut的結合會更靈活,使一個pointcut可以和多個advice結合。

切入點 - 正則表示式

可以用正則表示式匹配需要攔截的method。

<bean id="articleAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="patterns">
        <list>
            <value>.*Article.*</value>
        </list>
    </property>
    <property name="advice" ref="articleServiceBeforeAdvice" />
</bean>

現在,你可以攔截名字中包含Article字元的method了。

6. Spring自動建立代理

前面的 Spring AOP例子 – advice, pointcut 和 advisor, 必須手動建立一個代理bean(ProxyFactryBean),對每個Bean需要AOP支援。這顯然不是一種有效的方式。例如,如果想在客戶模組,所有的Servic類實現SQL日誌支援的AOP功能,那麼必須手動建立很多代理工廠bean,因此在 bean配置檔案可能會氾濫代理類。幸運的是,Spring有兩個自動代理建立者來自動建立代理bean。

BeanNameAutoProxyCreator

自動代理機制,只需要建立一個的 BeanNameAutoProxyCreator,幷包含所有你的bean(通過bean的名字,或正則表示式名)和“advisor” 作為一個單位。

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
	<property name="beanNames">
		<list>
			<value>*Service</value>
		</list>
	</property>
	<property name="interceptorNames">
		<list>
			<value>articleAdvisor</value>
		</list>
	</property>
</bean>

現在,可以通過“articleService”的原始名稱獲取bean, 如果知道這個bean已經代理。

DefaultAdvisorAutoProxyCreator

DefaultAdvisorAutoProxyCreator 是非常強大的,如果有 bean 相關連,Spring會自動建立一個代理。

<bean id="articleServiceBeforeAdvice" class="com.angelia.spring.dao.aop.ArticleServiceMethodBeforeAdvice" />
<bean id="articleAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
	<property name="mappedName" value="queryArticleById" />
	<property name="advice" ref="articleServiceBeforeAdvice" />
</bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />