原理
AOP(aspect Oriented Programming),也就是面向方面編程的技術。AOP基于IoC基礎,是對OOP的有益補充。
AOP將應用系統分為兩部分,核心業務邏輯(Core business concerns)及橫向的通用邏輯,也就是所謂的方面Crosscutting enterprise concerns,例如,所有大中型應用都要涉及到的持久化管理(Persistent)、事務管理(Transaction Management)、安全管理(Security)、日志管理(Logging)和調試管理(Debugging)等。
AOP正在成為軟件開發的下一個光環。使用AOP,你可以將處理aspect的代碼注入主程序,通常主程序的主要目的并不在于處理這些aspect。AOP可以防止代碼混亂。
Spring framework是很有前途的AOP技術。作為一種非侵略性的、輕型的AOP framework,你無需使用預編譯器或其他的元標簽,便可以在java程序中使用它。這意味著開發團隊里只需一人要對付AOP framework,其他人還是像往常一樣編程。
AOP概念
讓我們從定義一些重要的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。同樣,我們將通過我們的例子來理解陌生的概念。
接口類
Java代碼實現類
Java代碼AOP攔截器
Java代碼測試類
Java代碼配置文件
Xml代碼
輸出: user:shawn printUser---! printUseruser:hello!
結論:調用方法的時候傳入的值被攔截修改了.
攔截器中的事務管理(事務攔截機)
如果不采用攔截機的機制時,在使用JDBC進行數據庫訪問時,存在兩種情況:
自動提交模式是不被推薦的,因為每個操作都將產生一個事務點,這對于大的應用來說性能將受到影響;再有,對于常見的業務邏輯,這種模式顯得無能為力。比如:轉帳,從A帳戶取出100元,將其存入B帳戶;如果在這兩個操作之間發生了錯誤,那么用戶A將損失了100元,而本來應該給帳戶B的,卻因為失敗給了銀行。所以,建議在所有的應用中,如果使用 JDBC 都將不得不采用非自動提交模式(你們要能發現了在我們的 JDBC 那個例子中,我們采用的就是自動提交模式,我們是為了把精力放在JDBC上,而不是事務處理上),即我們不得不在每個方法中:
Java代碼這樣代碼在AOP的倡導者看來是“骯臟”的代碼。他們認為,所有的與事務有關的方法都應當可以集中配置(見聲明性事務控制),并自動攔截,程序應當關心他們的主要任務,即商業邏輯,而不應和事務處理的代碼攪和在一起。我先看看 Spring 是怎么做到攔截的:
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.xsdhttp://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>
這個配置比想象的要簡單的多:
Xml代碼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 來做。這樣固然很好,但有時我們需要有我們特有的控制邏輯。因為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 = "[email protected]"; 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 實現中可能會用到切入點所有信息。
文章出處:http://javacrazyer.VEvb.com/blog/794035
新聞熱點
疑難解答