一. 原理
AOP(Aspect Oriented Programming),也就是面向方面編程的技術(shù)。AOP基于IoC基礎(chǔ),是對(duì)OOP的有益補(bǔ)充檀训。
AOP將應(yīng)用系統(tǒng)分為兩部分,核心業(yè)務(wù)邏輯(Core business concerns)及橫向的通用邏輯享言,也就是所謂的方面Crosscutting enterprise concerns峻凫,例如,所有大中型應(yīng)用都要涉及到的持久化管理(Persistent)览露、事務(wù)管理(Transaction Management)荧琼、安全管理(Security)、日志管理(Logging)和調(diào)試管理(Debugging)等。
AOP正在成為軟件開發(fā)的下一個(gè)光環(huán)命锄。使用AOP堰乔,你可以將處理aspect的代碼注入主程序,通常主程序的主要目的并不在于處理這些aspect累舷。AOP可以防止代碼混亂浩考。
Spring framework是很有前途的AOP技術(shù)夹孔。作為一種非侵略性的被盈、輕型的AOP framework,你無需使用預(yù)編譯器或其他的元標(biāo)簽搭伤,便可以在Java程序中使用它只怎。這意味著開發(fā)團(tuán)隊(duì)里只需一人要對(duì)付AOP framework,其他人還是像往常一樣編程怜俐。
1. AOP概念
讓我們從定義一些重要的AOP概念開始身堡。
方面(Aspect):一個(gè)關(guān)注點(diǎn)的模塊化,這個(gè)關(guān)注點(diǎn)實(shí)現(xiàn)可能另外橫切多個(gè)對(duì)象拍鲤。事務(wù)管理是J2EE應(yīng)用中一個(gè)很好的橫切關(guān)注點(diǎn)例子贴谎。方面用Spring的Advisor或攔截器實(shí)現(xiàn)。
連接點(diǎn)(Joinpoint):程序執(zhí)行過程中明確的點(diǎn)季稳,如方法的調(diào)用或特定的異常被拋出擅这。
通知(Advice):在特定的連接點(diǎn),AOP框架執(zhí)行的動(dòng)作景鼠。各種類型的通知包括“around”仲翎、“before”和“throws”通知。通知類型將在下面討論铛漓。許多AOP框架包括Spring都是以攔截器做通知模型溯香,維護(hù)一個(gè)“圍繞”連接點(diǎn)的攔截器鏈。
切入點(diǎn)(Pointcut):指定一個(gè)通知將被引發(fā)的一系列連接點(diǎn)的集合浓恶。AOP框架必須允許開發(fā)者指定切入點(diǎn)玫坛,例如,使用正則表達(dá)式包晰。
引入(Introduction):添加方法或字段到被通知的類湿镀。Spring允許引入新的接口到任何被通知的對(duì)象。例如杜窄,你可以使用一個(gè)引入使任何對(duì)象實(shí)現(xiàn)IsModified接口肠骆,來簡(jiǎn)化緩存。
目標(biāo)對(duì)象(Target Object):包含連接點(diǎn)的對(duì)象塞耕,也被稱作被通知或被代理對(duì)象蚀腿。
AOP代理(AOP Proxy):AOP框架創(chuàng)建的對(duì)象,包含通知。在Spring中莉钙,AOP代理可以是JDK動(dòng)態(tài)代理或CGLIB代理廓脆。
編織(Weaving):組裝方面來創(chuàng)建一個(gè)被通知對(duì)象。這可以在編譯時(shí)完成(例如使用AspectJ編譯器)磁玉,也可以在運(yùn)行時(shí)完成停忿。Spring和其他純Java AOP框架一樣,在運(yùn)行時(shí)完成織入蚊伞。
2. 各種通知類型包括:
Around通知:包圍一個(gè)連接點(diǎn)的通知席赂,如方法調(diào)用。這是最強(qiáng)大的通知时迫。Aroud通知在方法調(diào)用前后完成自定義的行為颅停,它們負(fù)責(zé)選擇繼續(xù)執(zhí)行連接點(diǎn)或通過返回它們自己的返回值或拋出異常來短路執(zhí)行。
Before通知:在一個(gè)連接點(diǎn)之前執(zhí)行的通知掠拳,但這個(gè)通知不能阻止連接點(diǎn)前的執(zhí)行(除非它拋出一個(gè)異常)癞揉。
Throws通知:在方法拋出異常時(shí)執(zhí)行的通知。Spring提供強(qiáng)制類型的Throws通知溺欧,因此你可以書寫代碼捕獲感興趣的異常(和它的子類)喊熟,不需要從Throwable或Exception強(qiáng)制類型轉(zhuǎn)換。
After returning通知:在連接點(diǎn)正常完成后執(zhí)行的通知姐刁,例如芥牌,一個(gè)方法正常返回,沒有拋出異常龙填。
Around通知是最通用的通知類型胳泉。大部分基于攔截的AOP框架(如Nanning和Jboss 4)只提供Around通知。
如同AspectJ岩遗,Spring提供所有類型的通知扇商,我們推薦你使用最 為合適的通知類型來實(shí)現(xiàn)需要的行為。例如宿礁,如果只是需要用一個(gè)方法的返回值來更新緩存案铺,你最好實(shí)現(xiàn)一個(gè)after returning通知,而不是around通知梆靖,雖然around通知也能完成同樣的事情控汉。使用最合適的通知類型使編程模型變得簡(jiǎn)單,并能減少潛在錯(cuò) 誤返吻。例如姑子,你不需要調(diào)用在around通知中所需使用的MethodInvocation的proceed()方法,因此就調(diào)用失敗测僵。
切入點(diǎn)的概念是AOP的關(guān)鍵街佑,它使AOP區(qū)別于其他使用攔截的技術(shù)谢翎。切入點(diǎn)使通知獨(dú)立于OO的層次選定目標(biāo)。例如沐旨,提供聲明式事務(wù)管理的around通知可以被應(yīng)用到跨越多個(gè)對(duì)象的一組方法上森逮。 因此切入點(diǎn)構(gòu)成了AOP的結(jié)構(gòu)要素。
二. 攔截器(也稱攔截機(jī))
攔截機(jī) (Interceptor), 是 AOP (Aspect-Oriented Programming) 的另一種叫法磁携。AOP本身是一門語言褒侧,只不過我們使用的是基于JAVA的集成到Spring 中的 SpringAOP。同樣谊迄,我們將通過我們的例子來理解陌生的概念闷供。
接口類
Java代碼
<span style="font-size: medium;">package com.test.TestSpring3;
public interface UserService // 被攔截的接口
...{
public void printUser(String user);
}
</span>
實(shí)現(xiàn)類
Java代碼
<span style="font-size: medium;">package com.test.TestSpring3;
public class UserServiceImp implements UserService // 實(shí)現(xiàn)UserService接口
...{
public void printUser(String user) ...{
System.out.println("printUser user:" + user);
// 顯示user
}
}
</span>
AOP攔截器
Java代碼
<span style="font-size: medium;">package com.test.TestSpring3;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class UserInterceptor implements MethodInterceptor
// AOP方法攔截器
...{
public Object invoke(MethodInvocation arg0) throws Throwable ...{
try ...{
if (arg0.getMethod().getName().equals("printUser"))
// 攔截方法是否是UserService接口的printUser方法
...{
Object[] args = arg0.getArguments();
// 被攔截的參數(shù)
System.out.println("user:" + args[0]);
arg0.getArguments()[0] = "hello!";
// 修改被攔截的參數(shù)
}
System.out.println(arg0.getMethod().getName() + "---!");
return arg0.proceed();
// 運(yùn)行UserService接口的printUser方法
}
catch (Exception e) ...{
throw e;
}
}
}
</span><span style="font-size: medium;">
</span>
測(cè)試類
Java代碼
<span style="font-size: medium;">package com.test.TestSpring3;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.web.context.support.WebApplicationContextUtils;
public class TestInterceptor ...{
public static void main(String[] args) ...{
ApplicationContext ctx = new FileSystemXmlApplicationContext(
"classpath:applicationContext.xml");
// ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService us = (UserService) ctx.getBean("userService");
us.printUser("shawn");
}
}
</span><span style="font-size: medium;">
</span>
配置文件
Xml代碼
<span style="font-size: medium;"><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="userServiceImp"
class="com.test.TestSpring3.UserServiceImp" />
<bean id="userInterceptor" class="com.test.TestSpring3.UserInterceptor" />
<bean id="userService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理接口 -->
<property name="proxyInterfaces">
<value>com.test.TestSpring3.UserService</value>
</property>
<!-- 目標(biāo)實(shí)現(xiàn)類 -->
<property name="target">
<ref local="userServiceImp" />
</property>
<!-- 攔截器 -->
<property name="interceptorNames">
<list>
<value>userInterceptor</value>
</list>
</property>
</bean>
</beans>
</span>
輸出:
user:shawn
printUser---!
printUser user:hello!
結(jié)論:調(diào)用方法的時(shí)候 傳入的值被攔截修改了.
三. 攔截器中的事務(wù)管理(事務(wù)攔截機(jī))
如果不采用攔截機(jī)的機(jī)制時(shí),在使用JDBC進(jìn)行數(shù)據(jù)庫訪問時(shí)鳞上,存在兩種情況:
自動(dòng)提交:這是JDBC驅(qū)動(dòng)默認(rèn)的模式这吻,每次數(shù)據(jù)庫操作(CRUD)成功完成后,都作為一個(gè)單獨(dú)的事務(wù)自動(dòng)提交篙议,如果未成功完成,即拋出了 SQLException 的話怠硼,僅最近的一個(gè)操作將回滾鬼贱。
非自動(dòng)提交:這是想更好的控制事務(wù)時(shí)需要程序地方式進(jìn)行控制:
- 在進(jìn)行該事務(wù)單元的任何操作之前 setAutoCommit(false)
- 在成功完成事務(wù)單元后 commit()
- 在異常發(fā)生后 rollback()
自動(dòng)提交模式是不被推薦的,因?yàn)槊總€(gè)操作都將產(chǎn)生一個(gè)事務(wù)點(diǎn)香璃,這對(duì)于大的應(yīng)用來說性能將受到影響这难;再有,對(duì)于常見的業(yè)務(wù)邏輯葡秒,這種模式顯得無能為力姻乓。比如:
轉(zhuǎn)帳,從A帳戶取出100元眯牧,將其存入B帳戶蹋岩;如果在這兩個(gè)操作之間發(fā)生了錯(cuò)誤,那么用戶A將損失了100元学少,而本來應(yīng)該給帳戶B的剪个,卻因?yàn)槭〗o了銀行。
所以版确,建議在所有的應(yīng)用中扣囊,如果使用 JDBC 都將不得不采用非自動(dòng)提交模式(你們要能發(fā)現(xiàn)了在我們的 JDBC 那個(gè)例子中,我們采用的就是自動(dòng)提交模式绒疗,我們是為了把精力放在JDBC上侵歇,而不是事務(wù)處理上),即我們不得不在每個(gè)方法中:
Java代碼
<span style="font-size: medium;">try {
// 在獲得連接后吓蘑,立即通過調(diào)用 setAutoCommit(false) 將事務(wù)處理置為非自動(dòng)提交模式 // Prepare Query to fetch the user Information
pst = conn.prepareStatement(findByName);
// ... conn.commit();
}
catch(Exception ex) {
conn.rollback();
throw ex;
}
finally {
try {
// Close Result Set and Statement
if (rset != null) rset.close();
if (pst != null) pst.close();
}
catch (Exception ex) {
ex.printStackTrace();
throw new Exception("SQL Error while closing objects = " + ex.toString());
}
}
</span>
這樣代碼在AOP的倡導(dǎo)者看來是“骯臟”的代碼惕虑。他們認(rèn)為,所有的與事務(wù)有關(guān)的方法都應(yīng)當(dāng)可以集中配置(見聲明性事務(wù)控制),并自動(dòng)攔截枷遂,程序應(yīng)當(dāng)關(guān)心他們的主要任務(wù)樱衷,即商業(yè)邏輯,而不應(yīng)和事務(wù)處理的代碼攪和在一起酒唉。
我先看看 Spring 是怎么做到攔截的:
1. Spring 內(nèi)置支持的事務(wù)處理攔截機(jī)
這里因?yàn)橐玫絁petStore項(xiàng)目中的代碼矩桂,我們將 applicationContext.xml 全部?jī)?nèi)容列出:
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Application context definition for JPetStore's business layer.
- Contains bean references to the transaction manager and to the DAOs in
- dataAccessContext-local/jta.xml (see web.xml's "contextConfigLocation").
Jpetstore 的應(yīng)用上下文定義,包含事務(wù)管理和引用了在 dataAccessContext-local/jta.xml(具體使用了哪個(gè)要看 web.xml 中的 'contextConfigLocation' 的配置)中注冊(cè)的DAO
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<!-- ========================= GENERAL DEFINITIONS ========================= -->
<!-- Configurer that replaces ${...} placeholders with values from properties files
占位符的值將從列出的屬性文件中抽取出來
-->
<!-- (in this case, mail and JDBC related properties) -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>WEB-INF/mail.properties</value>
<value>WEB-INF/jdbc.properties</value>
</list>
</property>
</bean>
<!-- MailSender used by EmailAdvice
指定用于發(fā)送郵件的 javamail 實(shí)現(xiàn)者痪伦,這里使用了 spring 自帶的實(shí)現(xiàn)侄榴。此 bean 將被 emailAdvice 使用
-->
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="${mail.host}"/>
</bean>
<!-- ========================= BUSINESS OBJECT DEFINITIONS ======================== -->
<!-- 不需要,因?yàn)楸?SpringMVC 的實(shí)現(xiàn)使用 Generic validator for Account objects, to be used for example by the Spring web tier -->
<bean id="accountValidator" class="org.springframework.samples.jpetstore.domain.logic.AccountValidator"/>
<!-- 不需要网沾,因?yàn)楸?SpringMVC 的實(shí)現(xiàn)使用 Generic validator for Order objects, to be used for example by the Spring web tier -->
<bean id="orderValidator" class="org.springframework.samples.jpetstore.domain.logic.OrderValidator"/>
<!--
主要的商業(yè)邏輯對(duì)象癞蚕,即我們所說的門面對(duì)象
注入了所有的DAO,這些DAO是引用了 dataAccessContext-xxx.xml 中定義的DAO
門面對(duì)象中的所有方法的事務(wù)控制將通過下面的 aop:config 來加以控制
- JPetStore primary business object (default implementation).
- Transaction advice gets applied through the AOP configuration below.
-->
<bean id="petStore" class="org.springframework.samples.jpetstore.domain.logic.PetStoreImpl">
<property name="accountDao" ref="accountDao"/>
<property name="categoryDao" ref="categoryDao"/>
<property name="productDao" ref="productDao"/>
<property name="itemDao" ref="itemDao"/>
<property name="orderDao" ref="orderDao"/>
</bean>
<!-- ========================= ASPECT CONFIGURATION ======================== -->
<!-- AOP配置辉哥,用來控制哪些方法將需要進(jìn)行事務(wù)處理桦山,采用了AspectJ 的語法 -->
<aop:config>
<!--
This definition creates auto-proxy infrastructure based on the given pointcut,
expressed in AspectJ pointcut language. Here: applying the advice named
"txAdvice" to all methods on classes named PetStoreImpl.
-->
<!-- 指出在 PetStoreFacade 的所有方法都將采用 txAdvice(在緊接著的元素中定義了)事務(wù)方針,注意醋旦,我們這里雖然指定的是接口 PetStoreFacace, 但其暗示著其所有的實(shí)現(xiàn)類也將
同樣具有這種性質(zhì)恒水,因?yàn)楸旧砭褪菍?shí)現(xiàn)類的方法在執(zhí)行的,接口是沒有方法體的饲齐。 -->
<aop:advisor pointcut="execution(* *..PetStoreFacade.*(..))" advice-ref="txAdvice"/>
<!--
This definition creates auto-proxy infrastructure based on the given pointcut,
expressed in AspectJ pointcut language. Here: applying the advice named
"emailAdvice" to insertOrder(Order) method of PetStoreImpl
-->
<!-- 當(dāng)執(zhí)行 PetStoreFacade.insertOrder方法钉凌,該方法最后一個(gè)參數(shù)為Order類型時(shí)(其實(shí)我們的例子中只有一個(gè) insertOrder 方法,但這告訴了我們捂人,當(dāng)我們的接口或類中有重載了的方法御雕,
并且各個(gè)重載的方法可能使用不同的攔截機(jī)機(jī)制時(shí),我們可以通過方法的參數(shù)加以指定)滥搭,將執(zhí)行emailAdvice(在最后定義的那個(gè)元素)-->
<aop:advisor pointcut="execution(* *..PetStoreFacade.insertOrder(*..Order))" advice-ref="emailAdvice"/>
</aop:config>
<!--
事務(wù)方針聲明酸纲,用于控制采用什么樣的事務(wù)策略
Transaction advice definition, based on method name patterns.
Defaults to PROPAGATION_REQUIRED for all methods whose name starts with
"insert" or "update", and to PROPAGATION_REQUIRED with read-only hint
for all other methods.
-->
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="insert*"/>
<tx:method name="update*"/>
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 攔截機(jī),用于在適當(dāng)?shù)臅r(shí)機(jī)(通過AOP配置论熙,如上面)在方法執(zhí)行成功后發(fā)送郵件
AOP advice used to send confirmation email after order has been submitted -->
<!-- -->
<bean id="emailAdvice" class="org.springframework.samples.jpetstore.domain.logic.SendOrderConfirmationEmailAdvice">
<property name="mailSender" ref="mailSender"/>
</bean>
<!-- ========================= 忽略 REMOTE EXPORTER DEFINITIONS ======================== -->
</beans>
這個(gè)配置比想象的要簡(jiǎn)單的多:
Xml代碼
<span style="font-size: medium;"><aop:config>
<!-- This definition creates auto-proxy infrastructure based on the given pointcut, expressed in AspectJ pointcut language.
Here: applying the advice named "txAdvice" to all methods on classes named PetStoreImpl. 指出在 PetStoreFacade
的所有方法都將采用 txAdvice(在緊接著的元素中定義了)事務(wù)方針福青,注意,我們這里雖然指定的是接口 PetStoreFacace,
但其暗示著其所有的實(shí)現(xiàn)類也將同樣具有這種性質(zhì)脓诡,因?yàn)楸旧砭褪菍?shí)現(xiàn)類的方法在執(zhí)行的无午,接口是沒有方法體的。 -->
<aop:advisor pointcut="execution(* *..PetStoreFacade.*(..))" advice-ref="txAdvice"/>
<!-- 其它攔截機(jī)-->
</aop:config>
</span>
- 所有的攔截機(jī)配置都放在 <aop:config> 配置元素中.
- 下面還是需要理解一下幾個(gè)有關(guān)AOP的專用名詞祝谚,不過宪迟,是挺抽象的,最好能會(huì)意出其的用意
pointcut:切入點(diǎn)交惯,比如:updateAccount 方法需要進(jìn)行事務(wù)管理次泽,則這個(gè)切入點(diǎn)就是“執(zhí)行方法體”(execution)穿仪。Spring 所有支持的切入點(diǎn)類型在都在 Spring reference: 6.2.3.1. Supported Pointcut Designators 中列出了。
advice:要對(duì)這個(gè)切入點(diǎn)進(jìn)行什么操作意荤,比如事務(wù)控制
advisor:Spring 特有的概念啊片,將上兩個(gè)概念合到一個(gè)概念中來,即一個(gè) advisor 包含了一個(gè)切入點(diǎn)及對(duì)這個(gè)切入點(diǎn)所實(shí)施的操作玖像。
因?yàn)?方法執(zhí)行切入點(diǎn) execution 為最常見的切入點(diǎn)類型紫谷,我們著重介紹一下,execution 的完全形式為:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
這是一個(gè)正則表達(dá)式捐寥,其中由 '?' 結(jié)尾的部分是可選的笤昨。翻譯過來就是:
執(zhí)行(方法訪問修飾符? 方法返回類型 聲明類型? 方法名(方法參數(shù)類型) 拋出異常?)
所有的這些都是用來定義執(zhí)行切入點(diǎn),即那些方法應(yīng)該被侯選為切入點(diǎn):
- 方法訪問修飾符:即 public, private 等等
- 方法返回類型:即方法返回的類型握恳,如 void, String 等等
- 聲明類型:1.5的語法瞒窒,現(xiàn)在可以先忽略它
- 方法名:方法的名字
- 方法參數(shù)類型:方法的參數(shù)類型
- 拋出異常:方法聲明的拋出的異常
例如,所有dao代碼被定義在包 com.xyz.dao 及子包 com.xyz.dao.hibernate, 或者其它乡洼,如果還有的話崇裁,子包中, 里面定義的是提供DAO功能的接口或類,那么表達(dá)式:
execution(* com.xyz.dao..*.*(..))
表示切入點(diǎn)為:執(zhí)行定義在包 com.xyz.dao 及其子包(因?yàn)?.. 所致) 中的任何方法
詳細(xì)情況可以參見 Spring refernce: 6.2.3.4. Examples
因此這個(gè)表達(dá)式為執(zhí)行定義在類 PetStoreFacade 及其實(shí)現(xiàn)類中的所有方法就珠,采取的動(dòng)作定義在 txAdvice 中. 關(guān)于該 advice 的定義寇壳,(見聲明性事務(wù)控制)一節(jié)
<aop:advisor pointcut="execution(* *..PetStoreFacade.*(..))" advice-ref="txAdvice"/>
2. Spring 自定攔截機(jī)
來為了進(jìn)行事務(wù)控制,我們只需簡(jiǎn)單地配置幾下妻怎,所有的工作都由 Spring 來做。這樣固然很好泞歉,但有時(shí)我們需要有我們特有的控制邏輯琅关。因?yàn)镾pring 不可能包含所有人需要的所有攔截機(jī)党瓮。所以它提供了通過程序的方式加以定制的方式。我們的項(xiàng)目中就有這么一個(gè)攔截機(jī),在用戶確認(rèn)付款后辱揭,將定單信息通過 email 的方式發(fā)送給注冊(cè)用戶的郵箱中。
<aop:config>
...
<!-- 當(dāng)執(zhí)行 PetStoreFacade.insertOrder方法讼稚,該方法最后一個(gè)參數(shù)為Order類型時(shí)(其實(shí)我們的例子中只有一個(gè) insertOrder 方法舅踪,但這告訴了我們,當(dāng)我們的接口或類中有重載了的方法选侨,并且各個(gè)重載的方法可能使用不同的攔截機(jī)機(jī)制時(shí)掖鱼,我們可以通過方法的參數(shù)加以指定),將執(zhí)行emailAdvice(在最后定義的那個(gè)元素)-->
<aop:advisor pointcut="execution(* *..PetStoreFacade.insertOrder(*..Order))" advice-ref="emailAdvice"/>
</aop:config>
紅色的注釋已經(jīng)說的很清楚這個(gè) Advisor 了援制,它的切入點(diǎn)(pointcut) 為 PetStoreFacade 的 void insertOrder(Order order) 方法戏挡,采取的動(dòng)作為引用的 emailAdvice, 下面我們就來看看 emailAdvice:
<bean id="emailAdvice" class="org.springframework.samples.jpetstore.domain.logic.SendOrderConfirmationEmailAdvice">
<property name="mailSender" ref="mailSender"/>
</bean>
它給了這個(gè) advice 的實(shí)現(xiàn)類為 logic 包中 SendOrderConfirmationEmailAdvice, 該Bean 引用了我們前面定義的郵件發(fā)送器(一個(gè) Spring 內(nèi)置的郵件發(fā)送器).
下面看看這個(gè)實(shí)現(xiàn)類:
public class SendOrderConfirmationEmailAdvice implements AfterReturningAdvice, InitializingBean {
// user jes on localhost
private static final String DEFAULT_MAIL_FROM = "test@pprun.org";
private static final String DEFAULT_SUBJECT = "Thank you for your order!";
private final Log logger = LogFactory.getLog(getClass());
private MailSender mailSender;
private String mailFrom = DEFAULT_MAIL_FROM;
private String subject = DEFAULT_SUBJECT;
public void setMailSender(MailSender mailSender) {
this.mailSender = mailSender;
}
public void setMailFrom(String mailFrom) {
this.mailFrom = mailFrom;
}
public void setSubject(String subject) {
this.subject = subject;
}
public void throws Exception {
if (this.mailSender == null) {
throw new IllegalStateException("mailSender is required");
}
}
/**
*
* @param returnValue 被攔截的方法的返回值
* @param m 被攔截的方法的所有信息(Method類封裝了這些信息)
* @param args 被攔截的方法的所有參數(shù)組成的數(shù)組
* @param target 目標(biāo)對(duì)象,對(duì)于方法執(zhí)行來說晨仑,即是方法所在的類的實(shí)例(與 this 同褐墅,批當(dāng)前對(duì)象)
* @throws java.lang.Throwable
*/
public void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable {
// 我們被攔截的方法為 void insertOrder(Order order)拆檬,方法只有一個(gè)參數(shù),所以可知數(shù)據(jù)的第1個(gè)元素即是被傳進(jìn)的 order 對(duì)象
// 得到了order 對(duì)象妥凳,就可以將 order 對(duì)應(yīng)的帳戶名及帳單號(hào)發(fā)送到郵件中竟贯,以便確認(rèn)無誤。
Order order = (Order) args[0];
Account account = ((PetStoreFacade) target).getAccount(order.getUser().getUsername());
// don't do anything if email address is not set
if (account.getEmail() == null || account.getEmail().length() == 0) {
return;
}
StringBuffer text = new StringBuffer();
text.append("Dear ").append(account.getFirstname()).
append(' ').append(account.getLastname());
text.append(", thank your for your order from JPetStore. " +
"Please note that your order number is ");
text.append(order.getId());
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setTo(account.getEmail());
mailMessage.setFrom(this.mailFrom);
mailMessage.setSubject(this.subject);
mailMessage.setText(text.toString());
try {
this.mailSender.send(mailMessage);
}
catch (MailException ex) {
// just log it and go on
logger.warn("An exception occured when trying to send email", ex);
}
}
}
①. 紅色的內(nèi)容即為反向注入的 mailSender 屬性
②. 藍(lán)色的內(nèi)容為 Spring Bean 的一個(gè)通用的接口 InitializingBean 逝钥,實(shí)現(xiàn)類需要實(shí)現(xiàn)該接口定義的方法 afterPropertiesSet() 屑那,該方法中一般是在Bean 被初始化后并設(shè)置了所有的 setter 注入后調(diào)用的。所以這里是保證郵件發(fā)送器配置正確晌缘。因?yàn)槿绻麤]有配置正確齐莲,下面的工作是無法進(jìn)行的,所以與其等那時(shí)拋出異常磷箕,還不如早早地在部署時(shí)就告知 (通過拋出 IllegalStateException 來提示)
③. 綠色的內(nèi)容為這個(gè) Advise 的核心选酗,即在切入點(diǎn)被切入后將采用的動(dòng)作。因?yàn)?Advise 也同樣有多種類型岳枷,比如我們這里的“方法正常返回”芒填,“方法執(zhí)行前”,“方法執(zhí)行后”空繁,“環(huán)繞在方法執(zhí)行前后”殿衰,“方法拋出異常時(shí)”等等(詳情參見 Spring Reference: 6.2.4. Declaring advice)。但是我們的邏輯為在用戶確認(rèn)定單并且執(zhí)行成功(所謂的成功是指將這一定單插入到了表 Order 中了)后盛泡,將發(fā)送一確認(rèn)信。所以”方法正常返回“完全符合我們的要求傲诵。
接口AfterReturningAdvice 即是 Spring中表示”方法正常返回“ 這一語義的 Advice, 所以我們實(shí)現(xiàn)這個(gè)接口及其必須的方法 afterReturning.
方法代碼的工作其實(shí)并不重要,只要我們理解這些“魔法”一樣的技術(shù)后拴竹,實(shí)現(xiàn)代碼是很簡(jiǎn)單的。值得提及的是這個(gè)方法的參數(shù)栓拜,這些參數(shù)是封裝了切入點(diǎn)的所有信息座泳,請(qǐng)見上面的注釋幕与。在我們的實(shí)現(xiàn)中只使用了被攔截方法的參數(shù)挑势,在復(fù)雜的 Advice 實(shí)現(xiàn)中可能會(huì)用到切入點(diǎn)所有信息。