Spring 面向方面程式設計 AOP

AngeliaZheng發表於2018-09-15

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

在Spring AOP中,有 5 種型別通知(advices)的支援:

  • 前置通知(Before advice):在目標方法執行之前執行,前置通知不會影響目標方法的執行,除非此處丟擲異常。
  • 正常返回通知(After returning advice):在目標方法執行完成後執行,如果目標方法丟擲異常,則不會執行。目標方法執行後,該方法返回一個結果。
  • 異常返回通知(After throwing advice):在目標方法丟擲異常後執行。
  • 返回通知(After advice):在目標方法執行完成後執行,不管是正常執行完成,還是丟擲異常,都會執行返回通知中的內容。
  • 環繞通知(Around advice):環繞通知圍繞在目標方法前後,即一個方法呼叫的前後。這是最強大的通知型別,能在方法呼叫前後自定義一些操作。環繞通知還需要負責決定是繼續處理join point(呼叫ProceedingJoinPoint的proceed方法)還是中斷執行。

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. 返回通知

該方法執行完成後執行,不管是正常執行完成,還是丟擲異常,都會執行返回通知中的內容。建立一個實現AfterAdvice介面的類。

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

bean配置

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

5. 環繞通知

環繞通知結合了上面的三個通知,在方法執行過程中執行。建立一個實現了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() 方法。

6. 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了。

7. 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" />

相關文章