<b> </b><b>Spring</b><b>對AOP</b><b>的支援</b>
<b>4.1 AOP</b><b>介紹</b>
首先讓我們從一些重要的AOP概念和術語開始。這些術語不是Spring特有的。不過AOP術語并不是特别的直覺,如果Spring使用自己的術語,将會變得更加令人困惑。
· 切面(Aspect):一個關注點的子產品化,這個關注點可能會橫切多個對象。事務管理是J2EE應用中一個關于橫切關注點的很好的例子。在Spring AOP中,切面可以使用基于模式或者基于@Aspect注解的方式來實作。
· 連接配接點(Joinpoint):在程式執行過程中某個特定的點,比如某方法調用的時候或者處理異常的時候。在Spring AOP中,一個連接配接點總是表示一個方法的執行。
· 通知(Advice):在切面的某個特定的連接配接點上執行的動作。其中包括了“around”、“before”和“after”等不同類型的通知(通知的類型将在後面部分進行讨論)。許多AOP架構(包括Spring)都是以攔截器做通知模型,并維護一個以連接配接點為中心的攔截器鍊。
· 切入點(Pointcut):比對連接配接點的斷言。通知和一個切入點表達式關聯,并在滿足這個切入點的連接配接點上運作(例如,當執行某個特定名稱的方法時)。切入點表達式如何和連接配接點比對是AOP的核心:Spring預設使用AspectJ切入點文法。
· 引入(Introduction):用來給一個類型聲明額外的方法或屬性(也被稱為連接配接類型聲明(inter-type declaration))。Spring允許引入新的接口(以及一個對應的實作)到任何被代理的對象。例如,你可以使用引入來使一個bean實作<code>IsModified</code>接口,以便簡化緩存機制。
· 目标對象(Target Object): 被一個或者多個切面所通知的對象。也被稱做被通知(advised)對象。既然Spring AOP是通過運作時代理實作的,這個對象永遠是一個被代理(proxied)對象。
· AOP代理(AOP Proxy):AOP架構建立的對象,用來實作切面契約(例如通知方法執行等等)。在Spring中,AOP代理可以是JDK動态代理或者CGLIB代理。
· 織入(Weaving):把切面連接配接到其它的應用程式類型或者對象上,并建立一個被通知的對象。這些可以在編譯時(例如使用AspectJ編譯器),類加載時和運作時完成。Spring和其他純Java AOP架構一樣,在運作時完成織入。
通知類型:
前置通知(Before advice):在某連接配接點之前執行的通知,但這個通知不能阻止連接配接點之前的執行流程(除非它抛出一個異常)。
後置通知(After returning advice):在某連接配接點正常完成後執行的通知:例如,一個方法沒有抛出任何異常,正常傳回。
異常通知(After throwing advice):在方法抛出異常退出時執行的通知。
最終通知(After (finally) advice):當某連接配接點退出的時候執行的通知(不論是正常傳回還是異常退出)。
環繞通知(Around Advice):包圍一個連接配接點的通知,如方法調用。這是最強大的一種通知類型。環繞通知可以在方法調用前後完成自定義的行為。它也會選擇是否繼續執行連接配接點或直接傳回它自己的傳回值或抛出異常來結束執行。
環繞通知是最常用的通知類型。和AspectJ一樣,Spring提供所有類型的通知,我們推薦你使用盡可能簡單的通知類型來實作需要的功能。例如,如果你隻是需要一個方法的傳回值來更新緩存,最好使用後置通知而不是環繞通知,盡管環繞通知也能完成同樣的事情。用最合适的通知類型可以使得程式設計模型變得簡單,并且能夠避免很多潛在的錯誤。比如,你不需要在JoinPoint上調用用于環繞通知的proceed()方法,就不會有調用的問題。在Spring 2.0中,所有的通知參數都是靜态類型,是以你可以使用合适的類型(例如一個方法執行後的傳回值類型)作為通知的參數而不是使用Object數組。 通過切入點比對連接配接點的概念是AOP的關鍵,這使得AOP不同于其它僅僅提供攔截功能的舊技術。 切入點使得通知可以獨立對應到面向對象的層次結構中。例如,一個提供聲明式事務管理的環繞通知可以被應用到一組橫跨多個對象的方法上(例如服務層的所有業務操作)。
<b>4.2 </b><b>建立通知</b>
我們通過一個簡單的例子來了解AOP。
代碼清單1
<b>public</b> <b>class</b> Foo {
<b>public</b> <b>void</b> printName(String name){
System.out.println("The Name is : " + name);
}
<b>public</b> <b>void</b> printAge(String age){
System.out.println("The Age is : " + age);
}
<b>import</b> java.lang.reflect.Method;
<b>import</b> org.springframework.aop.MethodBeforeAdvice;
<b>public</b> <b>class</b> FooBeforeAdvice <b>implements</b> MethodBeforeAdvice{
@Override
<b>public</b> <b>void</b> before(Method method, Object[] args, Object object)
<b>throws</b> Throwable {
//列印出類的名稱
System.out.print("Class Name is " +
object.getClass().getSimpleName() + " ");
//列印出參數的值
System.out.println("arg is "+(String)args[0] + " ");
<b>import</b> org.springframework.aop.framework.ProxyFactory;
<b>public</b> <b>class</b> Test {
<b>public</b> <b>static</b> <b>void</b> main(String[] args) {
Foo foo = <b>new</b> Foo();
FooBeforeAdvice advice = <b>new</b> FooBeforeAdvice();
//Spring提供的代理工廠
ProxyFactory pf = <b>new</b> ProxyFactory();
//設定代理目标
pf.setTarget(foo);
//為代理目标添加增強
pf.addAdvice(advice);
//生成代理執行個體
Foo proxy = (Foo)pf.getProxy();
proxy.printName("Tony");
proxy.printAge("27");
}
控制台輸出資訊
Class Name is Foo arg is Tony
The Name is : Tony
Class Name is Foo arg is 27
The Age is : 27
代碼清單1中我們的Foo.java類有兩個方法,我們建立了FooBeforeAdvice繼承前置增強接口MethodBeforeAdvice,在before方法中我同通過參數object獲得代理的對象資訊,通過參數args獲得代理方法的參數值,最後我們通過ProxyFactory将目标類和增強類融合,生成了代理執行個體并調用代理執行個體的方法。而在Spring中又如何配置AOP呢?
<b>4.3</b><b>前置增強</b><b></b>
代碼清單2
<bean id="foo" class="com.tony.test.Foo" scope="singleton"/>
<bean id="fooBeforeAdvice" class="com.tony.test.FooBeforeAdvice"/>
<bean id="proxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interceptorNames"><!-- 指定增強 -->
<list>
<value>fooBeforeAdvice</value>
</list>
</property>
<!-- 指定目标代理Bean -->
<property name="target" ref="foo"/>
</bean>
<b>import</b> org.springframework.beans.factory.BeanFactory;
<b>import</b> org.springframework.beans.factory.xml.XmlBeanFactory;
<b>import</b> org.springframework.core.io.ClassPathResource;
ClassPathResource resource = <b>new</b>
ClassPathResource("spring-config-beans.xml");
//執行個體化BeanFactory
BeanFactory factory = <b>new</b> XmlBeanFactory(resource);
Foo foo = (Foo)factory.getBean("proxy");
foo.printName("Tony");
foo.printAge("27");
控制台資訊
代碼清單2中我們隻需修改Spring的配置檔案,将增強類和目标類裝配起來就可以了,我們在Test.java就像正常的調用Foo一樣,可是控制台取已經是被攔截了。
<b>4.4</b><b>後置增強</b><b></b>
代碼清單1
<b>public</b> <b>class</b> Foo {
<b>public</b> String printName(String name){
<b>return</b> "The Name is : " + name;
<b>public</b> String printAge(String age){
<b>return</b> "The Age is : " + age;
<b>import</b> org.springframework.aop.AfterReturningAdvice;
<b>public</b> <b>class</b> FooAfterAdvice <b>implements</b> AfterReturningAdvice{
@Override//參數分别是代理方法的傳回值,被代理的方法,方法的參數,代理對象
<b>public</b> <b>void</b> afterReturning(Object returnValue, Method method,
Object[] args,Object object) <b>throws</b> Throwable {
object.getClass().getSimpleName() + " ");
System.out.println("arg is "+
(String)args[0] + " ");
//列印出傳回值圖
System.out.println("ReturnValue is " +
returnValue.toString());
<bean id="fooAfterAdvice" class="com.tony.test.FooAfterAdvice"/>
<value>fooAfterAdvice</value>
控制台輸出
ReturnValue is The Name is : Tony
ReturnValue is The Age is : 27
代碼清單中我們修改了Foo.java類兩個方法都傳回String類型的參數,定義了一個FooAfterAdvice.java類這個類實作了AfterReturningAdvice接口,分别列印出被代理類的名稱,方法參數值和傳回值。我們檢視控制台輸出的資訊,發現在目标方法執行結束後還列印出增強類輸出的資訊。
<b>4.5</b><b>環繞增強</b><b></b>
<b>代碼清單</b><b>1</b>
<b>import</b> org.aopalliance.intercept.MethodInterceptor;
<b>import</b> org.aopalliance.intercept.MethodInvocation;
<b>public</b> <b>class</b> FooInterceptor <b>implements</b> MethodInterceptor{
<b>public</b> Object invoke(MethodInvocation invocation) <b>throws</b> Throwable {
//目标方法入參
Object[] args = invocation.getArguments();
//列印方法入參
System.out.println("準備執行目标方法");
//執行目标方法
Object obj = invocation.proceed();
System.out.println("目标方法執行完成");
//傳回方法傳回值
<b>return</b> obj;
<bean id="fooInterceptor" class="com.tony.test.FooInterceptor"/>
<value>fooInterceptor</value>
準備執行目标方法
目标方法執行完成
代碼清單1中我們定義了FooInterceptor.java實作MethodInterceptor接口對目标方法進行環繞增強,檢視控制台我們就能看出FooInterceptor在每次方法的執行前後都進行了處理。
我的其它Spring文章,也許會對您有幫助
<a href="http://tonyaction.blog.51cto.com/227462/84008" target="_blank">Spring的任務排程和郵件發送</a>
<a href="http://tonyaction.blog.51cto.com/227462/84178" target="_blank">Spring應用的單元測試</a>
<a href="http://tonyaction.blog.51cto.com/227462/84724" target="_blank">Spring的資料庫支援</a>
<a href="http://tonyaction.blog.51cto.com/227462/84893" target="_blank">Spring的MVC架構</a>
<a href="http://tonyaction.blog.51cto.com/227462/85159" target="_blank">Spring的IoC容器</a>
<a href="http://tonyaction.blog.51cto.com/227462/85504" target="_blank">Spring對AOP的支援</a>
<a href="http://tonyaction.blog.51cto.com/227462/83874" target="_blank">Spring2.5注釋驅動與基于注釋的MVC</a>
本文轉自 tony_action 51CTO部落格,原文連結:http://blog.51cto.com/tonyaction/85504,如需轉載請自行聯系原作者