詳解Spring AOP原理及攔截器,再有不懂者,請(qǐng)直接把這篇文章甩給他

一. 原理

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>
  1. 所有的攔截機(jī)配置都放在 <aop:config> 配置元素中.
  2. 下面還是需要理解一下幾個(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)所有信息。

# 鏈接 Java程序員福利"常用資料分享"

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末纽门,一起剝皮案震驚了整個(gè)濱河市薛耻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赏陵,老刑警劉巖饼齿,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饲漾,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡缕溉,警方通過查閱死者的電腦和手機(jī)考传,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來证鸥,“玉大人僚楞,你說我怎么就攤上這事⊥鞑悖” “怎么了泉褐?”我有些...
    開封第一講書人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)鸟蜡。 經(jīng)常有香客問我膜赃,道長(zhǎng),這世上最難降的妖魔是什么揉忘? 我笑而不...
    開封第一講書人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任跳座,我火速辦了婚禮,結(jié)果婚禮上泣矛,老公的妹妹穿的比我還像新娘疲眷。我一直安慰自己,他們只是感情好您朽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開白布狂丝。 她就那樣靜靜地躺著,像睡著了一般哗总。 火紅的嫁衣襯著肌膚如雪美侦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,610評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音耻煤,去河邊找鬼。 笑死哈蝇,一個(gè)胖子當(dāng)著我的面吹牛攘已,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播样勃,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼剧防!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起峭拘,我...
    開封第一講書人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤鸡挠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后拣展,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡溜腐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年挺益,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乘寒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡烂翰,死狀恐怖蚤氏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情竿滨,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布毁葱,位于F島的核電站,受9級(jí)特大地震影響倾剿,放射性物質(zhì)發(fā)生泄漏蚌成。R本人自食惡果不足惜凛捏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一葵袭、第九天 我趴在偏房一處隱蔽的房頂上張望乖菱。 院中可真熱鬧,春花似錦窒所、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至藻丢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間悠反,已是汗流浹背馍佑。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留茵臭,地道東北人舅世。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親逼龟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容