原理java
AOP(Aspect Oriented Programming),也就是面向方面編程的技術。AOP基於IoC基礎,是對OOP的有益補充。web
AOP將應用系統分爲兩部分,核心業務邏輯(Core business concerns)及橫向的通用邏輯,也就是所謂的方面Crosscutting enterprise concerns,例如,全部大中型應用都要涉及到的持久化管理(Persistent)、事務管理(Transaction Management)、安全管理(Security)、日誌管理(Logging)和調試管理(Debugging)等。正則表達式
AOP正在成爲軟件開發的下一個光環。使用AOP,你能夠將處理aspect的代碼注入主程序,一般主程序的主要目的並不在於處理這些aspect。AOP能夠防止代碼混亂。spring
Spring framework是頗有前途的AOP技術。做爲一種非侵略性的、輕型的AOP framework,你無需使用預編譯器或其餘的元標籤,即可以在Java程序中使用它。這意味着開發團隊裏只需一人要對付AOP framework,其餘人仍是像往常同樣編程。數據庫
AOP概念express
讓咱們從定義一些重要的AOP概念開始。編程
— 方面(Aspect):一個關注點的模塊化,這個關注點實現可能另外橫切多個對象。事務管理是J2EE應用中一個很好的橫切關注點例子。方面用Spring的Advisor或攔截器實現。數組
— 鏈接點(Joinpoint):程序執行過程當中明確的點,如方法的調用或特定的異常被拋出。緩存
— 通知(Advice):在特定的鏈接點,AOP框架執行的動做。各類類型的通知包括「around」、「before」和「throws」通知。通知類型將在下面討論。許多AOP框架包括Spring都是以攔截器作通知模型,維護一個「圍繞」鏈接點的攔截器鏈。安全
— 切入點(Pointcut):指定一個通知將被引起的一系列鏈接點的集合。AOP框架必須容許開發者指定切入點,例如,使用正則表達式。
— 引入(Introduction):添加方法或字段到被通知的類。Spring容許引入新的接口到任何被通知的對象。例如,你可使用一個引入使任何對象實現IsModified接口,來簡化緩存。
— 目標對象(Target Object):包含鏈接點的對象,也被稱做被通知或被代理對象。
— AOP代理(AOP Proxy):AOP框架建立的對象,包含通知。在Spring中,AOP代理能夠是JDK動態代理或CGLIB代理。
— 編織(Weaving):組裝方面來建立一個被通知對象。這能夠在編譯時完成(例如使用AspectJ編譯器),也能夠在運行時完成。Spring和其餘純Java AOP框架同樣,在運行時完成織入。
各類通知類型包括:
— Around通知:包圍一個鏈接點的通知,如方法調用。這是最強大的通知。Aroud通知在方法調用先後完成自定義的行爲,它們負責選擇繼續執行鏈接點或經過返回它們本身的返回值或拋出異常來短路執行。
— Before通知:在一個鏈接點以前執行的通知,但這個通知不能阻止鏈接點前的執行(除非它拋出一個異常)。
— Throws通知:在方法拋出異常時執行的通知。Spring提供強制類型的Throws通知,所以你能夠書寫代碼捕獲感興趣的異常(和它的子類),不須要從Throwable或Exception強制類型轉換。
— After returning通知:在鏈接點正常完成後執行的通知,例如,一個方法正常返回,沒有拋出異常。
Around通知是最通用的通知類型。大部分基於攔截的AOP框架(如Nanning和Jboss 4)只提供Around通知。
如同AspectJ,Spring提供全部類型的通知,咱們推薦你使用最爲合適的通知類型來實現須要的行爲。例如,若是隻是須要用一個方法的返回值來更新緩存,你最好實現一個after returning通知,而不是around通知,雖然around通知也能完成一樣的事情。使用最合適的通知類型使編程模型變得簡單,並能減小潛在錯誤。例如,你不須要調用在around通知中所需使用的MethodInvocation的proceed()方法,所以就調用失敗。
切入點的概念是AOP的關鍵,它使AOP區別於其餘使用攔截的技術。切入點使通知獨立於OO的層次選定目標。例如,提供聲明式事務管理的around通知能夠被應用到跨越多個對象的一組方法上。 所以切入點構成了AOP的結構要素。
攔截器(也稱攔截機)
攔截機 (Interceptor), 是 AOP (Aspect-Oriented Programming) 的另外一種叫法。AOP自己是一門語言,只不過咱們使用的是基於JAVA的集成到Spring 中的 SpringAOP。一樣,咱們將經過咱們的例子來理解陌生的概念。
接口類
實現類
AOP攔截器
測試類
配置文件
輸出:
user:shawn
printUser---!
printUser user:hello!
結論:調用方法的時候 傳入的值被攔截修改了.
攔截器中的事務管理(事務攔截機)
若是不採用攔截機的機制時,在使用JDBC進行數據庫訪問時,存在兩種狀況:
自動提交模式是不被推薦的,由於每一個操做都將產生一個事務點,這對於大的應用來講性能將受到影響;再有,對於常見的業務邏輯,這種模式顯得無能爲力。好比:
轉賬,從A賬戶取出100元,將其存入B賬戶;若是在這兩個操做之間發生了錯誤,那麼用戶A將損失了100元,而原本應該給賬戶B的,卻由於失敗給了銀行。
因此,建議在全部的應用中,若是使用 JDBC 都將不得不採用非自動提交模式(大家要能發現了在咱們的 JDBC 那個例子中,咱們採用的就是自動提交模式,咱們是爲了把精力放在JDBC上,而不是事務處理上),即咱們不得不在每一個方法中:
這樣代碼在AOP的倡導者看來是「骯髒」的代碼。他們認爲,全部的與事務有關的方法都應當能夠集中配置(見聲明性事務控制),並自動攔截,程序應當關心他們的主要任務,即商業邏輯,而不該和事務處理的代碼攪和在一塊兒。
我先看看 Spring 是怎麼作到攔截的:
這裏由於要用到JpetStore項目中的代碼,咱們將 applicationContext.xml 所有內容列出:
<?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 的應用上下文定義,包含事務管理和引用了在 dataAccessContext-local/jta.xml(具體使用了哪一個要看 web.xml 中的 'contextConfigLocation' 的配置)中註冊的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
指定用於發送郵件的 javamail 實現者,這裏使用了 spring 自帶的實現。此 bean 將被 emailAdvice 使用
-->
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="${mail.host}"/>
</bean>
<!-- ========================= BUSINESS OBJECT DEFINITIONS ======================== -->
<!-- 不須要,由於被 SpringMVC 的實現使用 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"/>
<!-- 不須要,由於被 SpringMVC 的實現使用 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"/>
<!--
主要的商業邏輯對象,即咱們所說的門面對象
注入了全部的DAO,這些DAO是引用了 dataAccessContext-xxx.xml 中定義的DAO
門面對象中的全部方法的事務控制將經過下面的 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配置,用來控制哪些方法將須要進行事務處理,採用了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(在緊接着的元素中定義了)事務方針,注意,咱們這裏雖然指定的是接口 PetStoreFacace, 但其暗示着其全部的實現類也將
一樣具備這種性質,由於自己就是實現類的方法在執行的,接口是沒有方法體的。 -->
<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
-->
<!-- 當執行 PetStoreFacade.insertOrder方法,該方法最後一個參數爲Order類型時(其實咱們的例子中只有一個 insertOrder 方法,但這告訴了咱們,當咱們的接口或類中有重載了的方法,
而且各個重載的方法可能使用不一樣的攔截機機制時,咱們能夠經過方法的參數加以指定),將執行emailAdvice(在最後定義的那個元素)-->
<aop:advisor pointcut="execution(* *..PetStoreFacade.insertOrder(*..Order))" advice-ref="emailAdvice"/>
</aop:config>
<!--
事務方針聲明,用於控制採用什麼樣的事務策略
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>
<!-- 攔截機,用於在適當的時機(經過AOP配置,如上面)在方法執行成功後發送郵件
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>
這個配置比想象的要簡單的多:
1. 全部的攔截機配置都放在 <aop:config> 配置元素中.
2. 下面仍是須要理解一下幾個有關AOP的專用名詞,不過,是挺抽象的,最好能會意出其的用意
由於 方法執行切入點 execution 爲最多見的切入點類型,咱們着重介紹一下,execution 的徹底形式爲:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
這是一個正則表達式,其中由 '?' 結尾的部分是可選的。翻譯過來就是:
執行(方法訪問修飾符? 方法返回類型 聲明類型? 方法名(方法參數類型) 拋出異常?)
全部的這些都是用來定義執行切入點,即那些方法應該被侯選爲切入點:
例如,全部dao代碼被定義在包 com.xyz.dao 及子包 com.xyz.dao.hibernate, 或者其它,若是還有的話,子包中, 裏面定義的是提供DAO功能的接口或類,那麼表達式:
execution(* com.xyz.dao..*.*(..))
表示切入點爲:執行定義在包 com.xyz.dao 及其子包(由於 .. 所致) 中的任何方法
詳細狀況能夠參見 Spring refernce: 6.2.3.4. Examples
所以這個表達式爲執行定義在類 PetStoreFacade 及其實現類中的全部方法,採起的動做定義在 txAdvice 中. 關於該 advice 的定義,(見聲明性事務控制)一節
<aop:advisor pointcut="execution(* *..PetStoreFacade.*(..))" advice-ref="txAdvice"/>
來爲了進行事務控制,咱們只需簡單地配置幾下,全部的工做都由 Spring 來作。這樣當然很好,但有時咱們須要有咱們特有的控制邏輯。由於Spring 不可能包含全部人須要的全部攔截機。因此它提供了經過程序的方式加以定製的方式。咱們的項目中就有這麼一個攔截機,在用戶確認付款後,將定單信息經過 email 的方式發送給註冊用戶的郵箱中。
<aop:config> ... <!-- 當執行 PetStoreFacade.insertOrder方法,該方法最後一個參數爲Order類型時(其實咱們的例子中只有一個 insertOrder 方法,但這告訴了咱們,當咱們的接口或類中有重載了的方法,
而且各個重載的方法可能使用不一樣的攔截機機制時,咱們能夠經過方法的參數加以指定),將執行emailAdvice(在最後定義的那個元素)--> <aop:advisor pointcut="execution(* *..PetStoreFacade.insertOrder(*..Order))" advice-ref="emailAdvice"/> </aop:config>
紅色的註釋已經說的很清楚這個 Advisor 了,它的切入點(pointcut) 爲 PetStoreFacade 的 void insertOrder(Order order) 方法,採起的動做爲引用的 emailAdvice, 下面咱們就來看看 emailAdvice:
<bean id="emailAdvice" class="org.springframework.samples.jpetstore.domain.logic.SendOrderConfirmationEmailAdvice"> <property name="mailSender" ref="mailSender"/> </bean>
它給了這個 advice 的實現類爲 logic 包中 SendOrderConfirmationEmailAdvice, 該Bean 引用了咱們前面定義的郵件發送器(一個 Spring 內置的郵件發送器).
下面看看這個實現類:
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 被攔截的方法的全部參數組成的數組
* @param target 目標對象,對於方法執行來講,便是方法所在的類的實例(與 this 同,批當前對象)
* @throws java.lang.Throwable
*/ public void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable { // 咱們被攔截的方法爲 void insertOrder(Order order),方法只有一個參數,因此可知數據的第1個元素便是被傳進的 order 對象 // 獲得了order 對象,就能夠將 order 對應的賬戶名及賬單號發送到郵件中,以便確認無誤。 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); } } }
1. 紅色的內容即爲反向注入的 mailSender 屬性
2. 藍色的內容爲 Spring Bean 的一個通用的接口 InitializingBean ,實現類須要實現該接口定義的方法 afterPropertiesSet() ,該方法中通常是在Bean 被初始化後並設置了全部的 setter 注入後調用的。因此這裏是保證郵件發送器配置正確。由於若是沒有配置正確,下面的工做是沒法進行的,因此與其等那時拋出異常,還不如早早地在部署時就告知(經過拋出 IllegalStateException 來提示)
3. 綠色的內容爲這個 Advise 的核心,即在切入點被切入後將採用的動做。由於 Advise 也一樣有多種類型,好比咱們這裏的「方法正常返回」,「方法執行前」,「方法執行後」,「環繞在方法執行先後」,「方法拋出異常時」等等(詳情參見 Spring Reference: 6.2.4. Declaring advice)。可是咱們的邏輯爲在用戶確認定單而且執行成功(所謂的成功是指將這必定單插入到了表 Order 中了)後,將發送一確認信。因此」方法正常返回「徹底符合咱們的要求。
接口AfterReturningAdvice 便是 Spring中表示」方法正常返回「 這一語義的 Advice, 因此咱們實現這個接口及其必須的方法 afterReturning.
方法代碼的工做其實並不重要,只要咱們理解這些「魔法」同樣的技術後,實現代碼是很簡單的。值得說起的是這個方法的參數,這些參數是封裝了切入點的全部信息,請見上面的註釋。在咱們的實現中只使用了被攔截方法的參數,在複雜的 Advice 實現中可能會用到切入點全部信息。