Spring對AOP的支持
4.1 AOP介紹
首先讓咱們從一些重要的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實現IsModified
接口,以便簡化緩存機制。
·
目標對象(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不一樣於其它僅僅提供攔截功能的舊技術。 切入點使得通知能夠獨立對應到面向對象的層次結構中。例如,一個提供聲明式事務管理的環繞通知能夠被應用到一組橫跨多個對象的方法上(例如服務層的全部業務操做)。
4.2 建立通知
咱們經過一個簡單的例子來理解AOP。
代碼清單1
public
class
Foo {
public
void
printName(String name){
System.
out
.println(
"The Name is : "
+ name);
}
public
void
printAge(String age){
System.
out
.println(
"The Age is : "
+ age);
}
}
import
java.lang.reflect.Method;
import
org.springframework.aop.MethodBeforeAdvice;
public
class
FooBeforeAdvice
implements
MethodBeforeAdvice{
@Override
public
void
before(Method method, Object[] args, Object object)
throws
Throwable {
//
打印出類的名稱
System.
out
.print(
"Class Name is "
+
object.getClass().getSimpleName() +
" "
);
//
打印出參數的值
System.
out
.println(
"arg is "
+(String)args[0] +
" "
);
}
}
import
org.springframework.aop.framework.ProxyFactory;
public
class
Test {
public
static
void
main(String[] args) {
Foo foo =
new
Foo();
FooBeforeAdvice advice =
new
FooBeforeAdvice();
//Spring
提供的代理工廠
ProxyFactory pf =
new
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
呢?
4.3
前置加強
代碼清單
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
>
import
org.springframework.beans.factory.BeanFactory;
import
org.springframework.beans.factory.xml.XmlBeanFactory;
import
org.springframework.core.io.ClassPathResource;
public
class
Test {
public
static
void
main(String[] args) {
ClassPathResource resource =
new
ClassPathResource(
"spring-config-beans.xml"
);
//
實例化
BeanFactory
BeanFactory factory =
new
XmlBeanFactory(resource);
Foo foo = (Foo)factory.getBean(
"proxy"
);
foo.printName(
"Tony"
);
foo.printAge(
"27"
);
}
}
控制檯信息
Class Name is Foo arg is Tony
The Name is : Tony
Class Name is Foo arg is 27
The Age is : 27
代碼清單
2
中咱們只需修改
Spring
的配置文件,將加強類和目標類裝配起來就能夠了,咱們在
Test.java
就像正常的調用
Foo
同樣,但是控制檯取已是被攔截了。
4.4
後置加強
代碼清單
1
public
class
Foo {
public
String printName(String name){
return
"The Name is : "
+ name;
}
public
String printAge(String age){
return
"The Age is : "
+ age;
}
}
import
java.lang.reflect.Method;
import
org.springframework.aop.AfterReturningAdvice;
public
class
FooAfterAdvice
implements
AfterReturningAdvice{
@Override
//
參數分別是代理方法的返回值,被代理的方法,方法的參數,代理對象
public
void
afterReturning(Object returnValue, Method method,
Object[] args,Object object)
throws
Throwable {
//
打印出類的名稱
System.
out
.print(
"Class Name is "
+
object.getClass().getSimpleName() +
" "
);
//
打印出參數的值
System.
out
.println(
"arg is "
+
(String)args[0] +
" "
);
//
打印出返回值圖
System.
out
.println(
"ReturnValue is "
+
returnValue.toString());
}
}
<
bean
id
=
"foo"
class
=
"com.tony.test.Foo"
scope
=
"singleton"
/>
<
bean
id
=
"fooAfterAdvice"
class
=
"com.tony.test.FooAfterAdvice"
/>
<
bean
id
=
"proxy"
class
=
"org.springframework.aop.framework.ProxyFactoryBean"
>
<
property
name
=
"interceptorNames"
>
<!--
指定加強
-->
<
list
>
<
value
>
fooAfterAdvice
</
value
>
</
list
>
</
property
>
<!--
指定目標代理
Bean -->
<
property
name
=
"target"
ref
=
"foo"
/>
</
bean
>
控制檯輸出
Class Name is Foo arg is Tony
ReturnValue is The Name is : Tony
Class Name is Foo arg is 27
ReturnValue is The Age is : 27
代碼清單中咱們修改了
Foo.java
類兩個方法都返回
String
類型的參數,定義了一個
FooAfterAdvice.java
類這個類實現了
AfterReturningAdvice
接口,分別打印出被代理類的名稱,方法參數值和返回值。咱們查看控制檯輸出的信息,發如今目標方法執行結束後還打印出加強類輸出的信息。
4.5
環繞加強
代碼清單
1
public
class
Foo {
public
void
printName(String name){
System.
out
.println(
"The Name is : "
+ name);
}
public
void
printAge(String age){
System.
out
.println(
"The Age is : "
+ age);
}
}
import
org.aopalliance.intercept.MethodInterceptor;
import
org.aopalliance.intercept.MethodInvocation;
public
class
FooInterceptor
implements
MethodInterceptor{
@Override
public
Object invoke(MethodInvocation invocation)
throws
Throwable {
//
目標方法入參
Object[] args = invocation.getArguments();
//
打印方法入參
System.
out
.println(
"
準備執行目標方法
"
);
//
執行目標方法
Object obj = invocation.proceed();
System.
out
.println(
"
目標方法執行完成
"
);
//
返回方法返回值
return
obj;
}
}
<
bean
id
=
"foo"
class
=
"com.tony.test.Foo"
scope
=
"singleton"
/>
<
bean
id
=
"fooInterceptor"
class
=
"com.tony.test.FooInterceptor"
/>
<
bean
id
=
"fooAfterAdvice"
class
=
"com.tony.test.FooAfterAdvice"
/>
<
bean
id
=
"proxy"
class
=
"org.springframework.aop.framework.ProxyFactoryBean"
>
<
property
name
=
"interceptorNames"
>
<!--
指定加強
-->
<
list
>
<
value
>
fooInterceptor
</
value
>
</
list
>
</
property
>
<!--
指定目標代理
Bean -->
<
property
name
=
"target"
ref
=
"foo"
/>
</
bean
>
控制檯輸出
準備執行目標方法
The Name is : Tony
目標方法執行完成
準備執行目標方法
The Age is : 27
目標方法執行完成
代碼清單
1
中咱們定義了
FooInterceptor.java
實現
MethodInterceptor
接口對目標方法進行環繞加強,查看控制檯咱們就能看出
FooInterceptor
在每次方法的執行先後都進行了處理。