Spring框架研究總結之AOP(一)
Spring是一個開源的企業應用框架,是一個分層架構,由7個定義穩定的模塊所組成,而所有的模塊都是在覈心容器基礎上搭建;其體系本身提供了強大的IOC(控制反轉)、AOP(面向切面)以及DI(依賴注入)等較顯著的功能,那麼下面就來詳細介紹下Spring框架的模塊組成及相關特點。
l 什麼是AOP
l 場景及概念
l AOP的使用
一、什麼是AOP
AOP(Aspect-Oriented Programming),即爲面向切面或方面編程,被認爲是OOP(Object-OrientedProgramming),面向對象編程的拓展和完善。OOP引入了繼承、封裝及多態等特性來搭建一種對象層次結構,是垂直方向上的層級結構;而當我們要爲分散的對象引入公共行爲時,OOP則顯得力不從心,也就是說OOP允許你定義從上到下的邏輯關係,卻不適合定義從左到右的邏輯關係,比如:異常處理,它散佈在所有對象結構中,這種散佈在各處的無關代碼片段被稱之爲橫切代碼片段,在OOP中導致了大量重複的代碼,不利於各個模塊的重用。而AOP則相反,它使用了橫切技術,剖開封裝對象的內部,並將那些影響了多個類的公共行爲封裝到了重用的模塊,稱之爲“Aspect”,即方面。
所謂的的方面,可理解爲將與業務無關,卻被業務模塊共同調用的邏輯或責任鏈封裝起來,減少系統代碼的重複,降低模塊間的耦合度,並有利於日後的系統拓展和維護。有人做了個有趣的比喻:如果說“對象”是一個空心的圓柱體,其中封裝的是對象的屬性和行爲;那麼面向方面編程的方法,就彷彿一把利刃,將這些空心圓柱體剖開,以獲得其內部的消息。而剖開的切面,也就是所謂的“方面”,然後它又以巧奪天功的妙手將這些剖開的切面復原,不留痕跡。
實現AOP的技術,主要分爲兩大類:一是採用動態代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行爲的執行;二是採用靜態織入的方式,引入特定的語法創建“方面”,從而使得編譯器可以在編譯期間織入有關“方面”的代碼。
二、場景及概念
1、一般使用的場景
事務、權限、緩存、錯誤處理、記錄跟蹤、性能優化、持久化、同步處理及資源池等場景。
2、幾個重要感概念
A、方面(Aspect)
某個關注點的模塊化或片面化,該關注點可以橫切多個對象,而事務機制是J2EE應用中一個很好的橫切關注點例子,在Spring中,使用advisor或攔截器來實現面向方面功能的。
B、通知(Advice)
在某個關注點或是連接點上執行的動作行爲,各種類型的通知,包括around、before、after及throws通知。大部分aop框架,包括spring框架,都是以攔截器模型來實現連接通知實現的,維護一個被圍繞的連接點攔截器鏈子。在Spring中有4個通知advice,分別爲:BeforeAdvice、AfterAdvice、ThrowAdvice及DynamicInstroductionAdvice,後面回詳細介紹它們。
C、切入點(Pointcut)
一個通知將被引發的一系列連接點的集合,而aop框架允許開發者指定切入點:例如,使用正則表達式。 Spring定義了Pointcut接口,用來組合MethodMatcher和ClassFilter,可以通過名字很清楚的理解, MethodMatcher是用來檢查目標類的方法是否可以被應用此通知,而ClassFilter是用來檢查Pointcut是否應該應用到目標類上。
D、連接點(Introduction)
指的是,程序執行過程中明確的點,如方法的調用或特定的異常拋出。
E、引入
添加方法或字段到被通知的類。 Spring允許引入新的接口到任何被通知的對象。例如,你可以使用一個引入使任何對象實現 IsModified接口,來簡化緩存。在Spring中要使用Introduction, 可有通過DelegatingIntroductionInterceptor來實現通知,通過DefaultIntroductionAdvisor來配置Advice和代理類要實現的接口。
F、目標對象(Target Object)
包含連接點的對象。也被稱作被通知或被代理對象。
G、Aop代理(Aop Proxy)
AOP框架創建的對象,包含通知。在Spring中,AOP代理可以是JDK動態代理或者CGLIB代理。
H、織入
組裝方面來創建一個被通知對象。這可以在編譯時完成(例如使用AspectJ編譯器),也可以在運行時完成。Spring和其他純Java AOP框架一樣,在運行時完成織入。
三、AOP的使用
Spring AOP(面向方面編程)框架,用於在模塊化方面的橫切關注點,是一個攔截器攔截一些動作,例如,當一個方法執行時,Spring AOP可以劫持一個執行的方法,在方法執行之前或之後添加額外的功能。具體請看下面的例子驗證,對於重複性的代碼,這裏不會列出,請讀者仔細研讀並瞭解整體代碼結構。
1、Advice通知
在Spring AOP中,目前有4種類型的通知支持:
• 通知(Advice)之前 - 該方法執行前運行;
• 通知(Advice)返回之後 – 運行後,該方法返回一個結果;
• 通知(Advice)拋出之後 – 運行方法拋出異常後;
• 環繞通知 – 環繞方法執行運行,結合以上這三個通知;
接下來,就來演示下如何實現上面的AOP通知支持。我們先創建一個Java項目,目錄結構如下:
A、方法執行之前(MethodBeforeAdvice)
目標對象:
public class CustomerService{
private String name;
private String url;
public void setName(String name) {
this.name = name;
}
public void setUrl(String url) {
this.url = url;
}
public void printName() {
System.out.println("Customer name:" + this.name);
System.out.println("--------------------------");
}
public void printUrl() {
System.out.println("Customer url:" + this.url);
System.out.println("--------------------------");
}
public voidprintThrowException() {
System.out.println("Exception err throw!");
System.out.println("--------------------------");
throw newIllegalArgumentException();
}
}
織入方法:
public class CustomBeforeMethodimplements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("CustomBeforeMethod:Before method becalled!");
}
}
XML配置:
<!-- target object -->
<bean id="customerService"class="com.spring.aop.bean.CustomerService">
<property name="name"value="Jackey" />
<property name="url"value="http://blog.csdn.net/why_2012_gogo"/>
</bean>
<!-- aop of before call the aopmethods -->
<bean id="customBeforeMethod"class="com.spring.aop.impls.CustomBeforeMethod"/>
<!-- proxy of aop listenerand callback -->
<bean id="customerServiceProxy"class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target"ref="customerService" />
<property name="interceptorNames">
<list>
<value>customBeforeMethod</value>
<value>customThrowMethod</value>
</list>
</property>
</bean>
如何驗證:
public class App {
private staticApplicationContext context;
public static void main(String[] args) {
context = new ClassPathXmlApplicationContext("com/spring/aop/config/applicationContext.xml");
try {
CustomerServicecs = (CustomerService) context.getBean("customerServiceProxy");
cs.printName();
cs.printUrl();
cs.printThrowException();
}catch(Exception e) {
}
}
}
執行結果:
從上圖看出,在執行目標對象的每個方法之前都會先執行織入的方法的執行,然後再執行目標對象自身的方法;同時,在拋出異常之後,執行了ThrowsAdvice接口實現類的異常方法。
B、方法返回之後(AfterReturningAdvice)
織入方法:
public classCustomAfterMethod implements AfterReturningAdvice{
@Override
public void afterReturning(ObjectreturnValue, Method method, Object[] args,Object target) throws Throwable {
System.out.println("CustomAfterMethod:After method becalled!");
}
}
XML配置:
<!-- aop of after call the aopmethods -->
<bean id="customAfterMethod"class="com.spring.aop.impls.CustomAfterMethod"/>
<!-- proxy of aop listenerand callback -->
<bean id="customerServiceProxy"class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target"ref="customerService" />
<property name="interceptorNames">
<list>
<value>customAfterMethod</value>
<value>customThrowMethod</value》
</list>
</property>
</bean>
執行結果:
同樣,在目標類中每個方法執行之後,織入的方法功能才執行。
C、拋出異常之後(ThrowsAdvice)
織入方法:
public classCustomAfterMethod implementsAfterReturningAdvice {
@Override
public voidafterReturning(Object returnValue, Method method, Object[] args,Object target) throws Throwable {
System.out.println("CustomAfterMethod:After method becalled!");
}
}
XML配置:
<!-- aop of throws call the aopmethods -->
<bean id="customThrowMethod"class="com.spring.aop.impls.CustomThrowMethod"/>
執行結果:
在上面的結果圖中,已經羅列出。
D、方法執行前後(MethodInterceptor)
織入方法:
public classCustomAroundMethod implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
// same with MethodBeforeAdvice
System.out.println("CustomAroundMethod : Before methodcalled!");
try {
// proceed to original method
Objectresult = methodInvocation.proceed();
// same with AfterReturningAdvice
System.out.println("CustomAroundMethod : Before aftercalled!");
return result;
} catch(IllegalArgumentException e) {
// same with ThrowsAdvice
System.out.println("CustomAroundMethod : Throw exceptioncalled!");
throw e;
}
}
}
XML配置:
<!-- aop of around call the aopmethods -->
<bean id="customAroundMethod"class="com.spring.aop.impls.CustomAroundMethod"/>
<!-- proxy of aop listenerand callback -->
<bean id="customerServiceProxy"class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target"ref="customerService" />
<property name="interceptorNames">
<list>
<value>customAroundMethod</value>
</list>
</property>
</bean>
執行結果:
2、Advisor攔截
這裏我們引入了“切入點”概念,並結合Advisor攔截機制,同樣實現上面的通知效果,而且我們可以執行攔截的目標對象的方法,具體如下:
A、目標對象
public class CustomerService{
private String name;
private String url;
public void setName(String name) {
this.name = name;
}
public void setUrl(String url) {
this.url = url;
}
public void printName() {
System.out.println("Customer name:" + this.name);
System.out.println("--------------------------");
}
public void printUrl() {
System.out.println("Customer url:" + this.url);
System.out.println("--------------------------");
}
public voidprintThrowException() {
System.out.println("Exception err throw!");
System.out.println("--------------------------");
throw new IllegalArgumentException();
}
}
B、XML配置
目標對象:
<!-- target object -->
<bean id="customerService"class="com.spring.aop.bean.CustomerService">
<property name="name"value="Jackey" />
<property name="url"value="http://blog.csdn.net/why_2012_gogo"/>
</bean>
織入功能:
<!-- aop of before call the aopmethods -->
<bean id="customBeforeMethod"class="com.spring.aop.impls.CustomBeforeMethod"/>
代理攔截:
<!-- proxy of aop listenerand callback -->
<bean id="customerServiceProxy"class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target"ref="customerService" />
<property name="interceptorNames">
<list>
<!--<value>customBeforeMethod</value> -->
<!--<value>customAfterMethod</value>
<value>customThrowMethod</value>-->
<!--<value>customAroundMethod</value> -->
<value>customerAdvisor</value>
</list>
</property>
</bean>
切入點:
<!-- name match method pointcutof aop -->
<bean id="customerMethodPointcut"class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedName"value="printName" />
</bean>
攔截器:
<!-- advisor match pointcut of aop-->
<bean id="customerAdvisor"class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut"ref="customerMethodPointcut" />
<property name="advice"ref="customBeforeMethod" />
</bean>
C、織入功能
這裏我們就以攔截目標對象的切入點方法執行之前的織入爲例,其它攔截實現是相同的,這裏不再贅述,具體如下:
public classCustomBeforeMethod implementsMethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("CustomBeforeMethod:Before method becalled!");
}
}
D、如何驗證
public class App {
private staticApplicationContext context;
public static void main(String[] args) {
context = new ClassPathXmlApplicationContext("com/spring/aop/config/applicationContext.xml");
try {
CustomerServicecs = (CustomerService) context.getBean("customerServiceProxy");
cs.printName();
cs.printUrl();
cs.printThrowException();
}catch(Exception e) {
}
}
}
E、執行結果
從上圖可以看出,我們只攔截了目標對象的printName()方法,所以只在執行該方法之前,纔會執行織入的功能實現。
另外,上面的攔截器是根據目標對象的織入方法的名字進行攔截的,其實還可以根據正則表達式來攔截欲織入的方法的,此時需要使用到org.springframework.aop.support.RegexpMethodPointcutAdvisor
與根據名字進行攔截不同的地方,只是攔截器和代理配置不同,具體如下:
攔截器:
<bean id="customerAdvisor2"class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="patterns">
<list>
<value>.*Url.*</value>
</list>
</property>
<property name="advice"ref="customBeforeMethod" />
</bean>
代理攔截:
<!-- proxy of aop listenerand callback -->
<bean id="customerServiceProxy"class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target"ref="customerService" />
<property name="interceptorNames">
<list>
<value>customerAdvisor2</value>
</list>
</property>
</bean>
執行結果:
由於攔截器匹配的規則爲.*Url.* ,所以只有目標對象中的printUrl()方法纔會被攔截,從上圖也看出了,在執行目標對象方法printUrl()之前,先執行了織入的printUrl功能方法。
3、自動代理
在前面介紹的通知的攔截,是通過手動設置ProxyFactoryBean代理工廠來實現的過濾和攔截功能,如果存在很多需要被攔截的目標對象,使用手動創建攔截會比較麻煩和不利於維護,因此可以使用Spring的自動代理創建者,來自動實現同樣的功能,當然,如果攔截的對象不是很多,建議使用前者。自動代理實現比較簡單,具體如下:
3.1、自動代理,結合advisor攔截
其它部分均相同,不同的是自動代理創建部分,如下:
XML配置:
<!-- auto proxy creator for beannames(advices) -->
<bean id="customerServiceAutoProxy"class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>*Service</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>customerAdvisor2</value>
</list>
</property>
</bean>
注意:
beanNames:正則或是全名匹配目標對象;
interceptorNames:直接引用前面創建的advisor即可;
如何驗證:
public class App {
private staticApplicationContext context;
public static void main(String[] args) {
context = new ClassPathXmlApplicationContext("com/spring/aop/config/applicationContext.xml");
try {
CustomerServicecs = (CustomerService) context.getBean("customerServiceAutoProxy");
cs.printName();
cs.printUrl();
cs.printThrowException();
}catch(Exception e) {
}
}
}
此時,我們需要獲得實例id爲customerService,即目標對象本身,因爲自動代理已經自動實例化,並匹配好目標對象以及初始化了攔截器。
執行結果:
3.2、自動代理攔截
這裏要介紹的是Spring中的 DefaultAdvisorAutoProxyCreator ,它的功能可是強大的不得了,因爲有了它,你只需要配置好Advisor攔截,並指定好欲織入的功能即可,因爲其會自動搜索匹配目標對象(根據設置的攔截器匹配),並會自動創建代理Bean,完成攔截需求。
實現上面同樣的功能,我們只需要做下面的幾件事兒:
XML配置:
<!-- target object -->
<bean id="customerService"class="com.spring.aop.bean.CustomerService">
<property name="name"value="Jackey" />
<property name="url"value="http://blog.csdn.net/why_2012_gogo"/>
</bean>
<!-- aop of before call the aopmethods -->
<bean id="customBeforeMethod"class="com.spring.aop.impls.CustomBeforeMethod"/>
<bean id="customerAdvisor2"class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="patterns">
<list>
<value>.*Url.*</value>
</list>
</property>
<property name="advice"ref="customBeforeMethod" />
</bean>
<!-- auto default advisor proxycreator -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
如何驗證:
public class App {
private staticApplicationContext context;
public static void main(String[] args) {
context = new ClassPathXmlApplicationContext("com/spring/aop/config/applicationContext.xml");
try {
CustomerServicecs = (CustomerService) context.getBean("customerService");
cs.printName();
cs.printUrl();
cs.printThrowException();
}catch(Exception e) {
}
}
}
執行結果:
對於AspectJ與Spring AOP的整合以及AOP的事務例子,將在下篇文章中進行介紹,期望各位同學繼續跟進,Spring框架之AOP(一)就介紹到這裏,由於作者水平有限,如有問題請在評論發言或是QQ羣討論,謝謝。
技術討論羣:
245389109(新)