?? spring aop+
字號:
6.3 Spring AOP面向方面編程原理:AOP概念(1)
介紹完IoC之后,我們來介紹另外一個重要的概念: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,其他人還是像往常一樣編程。
6.3.1 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的結構要素。
下面讓我們實現一個Spring AOP的例子。在這個例子中,我們將實現一個before advice,這意味著advice的代碼在被調用的public方法開始前被執行。以下是這個before advice的實現代碼。
package com.ascenttech.springaop.test;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class TestBeforeAdvice implements MethodBeforeAdvice {
public void before(Method m, Object[] args, Object target)
throws Throwable {
System.out.println("Hello world! (by "
+ this.getClass().getName()
+ ")");
}
}
接口MethodBeforeAdvice只有一個方法before需要實現,它定義了advice的實現。before方法共用3個參數,它們提供了相當豐富的信息。參數Method m是advice開始后執行的方法,方法名稱可以用作判斷是否執行代碼的條件。Object[] args是傳給被調用的public方法的參數數組。當需要記日志時,參數args和被執行方法的名稱都是非常有用的信息。你也可以改變傳給m的參數,但要小心使用這個功能;編寫最初主程序的程序員并不知道主程序可能會和傳入參數的發生沖突。Object target是執行方法m對象的引用。
在下面的BeanImpl類中,每個public方法調用前,都會執行advice,代碼如下。
package com.ascenttech.springaop.test;
public class BeanImpl implements Bean {
public void theMethod() {
System.out.println(this.getClass().getName()
+ "." + new Exception().getStackTrace()[0].getMethodName()
+ "()"
+ " says HELLO!");
}
}
類BeanImpl實現了下面的接口Bean,代碼如下。
package com.ascenttech.springaop.test;
public interface Bean {
public void theMethod();
}
雖然不是必須使用接口,但面向接口而不是面向實現編程是良好的編程實踐,Spring也鼓勵這樣做。
pointcut和advice通過配置文件來實現,因此,接下來你只需編寫主方法的Java代碼,代碼如下。
package com.ascenttech.springaop.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class Main {
public static void main(String[] args) {
//Read the configuration file
ApplicationContext ctx
= new FileSystemXmlApplicationContext("springconfig.xml");
//Instantiate an object
Bean x = (Bean) ctx.getBean("bean");
//Execute the public method of the bean (the test)
x.theMethod();
}
}
我們從讀入和處理配置文件開始,接下來馬上要創建它。這個配置文件將作為粘合程序不同部分的“膠水”。讀入和處理配置文件后,我們會得到一個創建工廠ctx,任何一個Spring管理的對象都必須通過這個工廠來創建。對象通過工廠創建后便可正常使用。
僅僅用配置文件便可把程序的每一部分組裝起來,代碼如下。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework. org/dtd/spring-beans.dtd">
<beans>
<!--CONFIG-->
<bean id="bean" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.ascenttech.springaop.test.Bean</value>
</property>
<property name="target">
<ref local="beanTarget"/>
</property>
<property name="interceptorNames">
<list>
<value>theAdvisor</value>
</list>
</property>
</bean>
<!--CLASS-->
<bean id="beanTarget" class="com.ascenttech.springaop.test.BeanImpl"/>
<!--ADVISOR-->
<!--Note: An advisor assembles pointcut and advice-->
<bean id="theAdvisor" class="org.springframework.aop.support.RegexpMethod PointcutAdvisor">
<property name="advice">
<ref local="theBeforeAdvice"/>
</property>
<property name="pattern">
<value>com\.ascenttech\.springaop\.test\.Bean\.theMethod</value>
</property>
</bean>
<!--ADVICE-->
<bean id="theBeforeAdvice" class="com.ascenttech.springaop.test.TestBefore Advice"/>
</beans>
4個bean定義的次序并不重要。我們現在有了一個advice、一個包含了正則表達式pointcut的advisor、一個主程序類和一個配置好的接口,通過工廠ctx,這個接口返回自己本身實現的一個引用。
BeanImpl和TestBeforeAdvice都是直接配置。我們用一個惟一的ID創建一個bean元素,并指定了一個實現類,這就是全部的工作。
advisor通過Spring framework提供的一個RegexMethodPointcutAdvisor類來實現。我們用advisor的第一個屬性來指定它所需的advice-bean,第二個屬性則用正則表達式定義了pointcut,確保良好的性能和易讀性。
最后配置的是bean,它可以通過一個工廠來創建。bean的定義看起來比實際上要復雜。bean是ProxyFactoryBean的一個實現,它是Spring framework的一部分。這個bean的行為通過以下的3個屬性來定義。
— 屬性proxyInterface定義了接口類。
— 屬性target指向本地配置的一個bean,這個bean返回一個接口的實現。
— 屬性interceptorNames是惟一允許定義一個值列表的屬性,這個列表包含所有需要在beanTarget上執行的advisor。注意,advisor列表的次序是非常重要的。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -