Spring學習手冊(9)—— Spring AOP入門講述了AOP技術以及AOP基本概念,最后我們了解了Spring對AOP的支持。本文我們將以XML配置的方式來學習Spring AOP的具體使用。
一、引入aop模式
如果想使用XML的方式配置AOP信息,我們需要先在XML配置文件中引入aop模式(aop schema),因此我們的XML配置文件頭頭信息如下:
<?xml version="1.0" encoding="UTF-8"?>
<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" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- bean definitions here -->
</beans>
這樣配之后,我們就可以在xml文件里面直接引用aop
標簽了。
二、定義一個切面
一個Spring AOP的切面(Aspect)在xml中也是一個傳統的bean
,而使用標簽<aop:aspect>
定義一個切面,并且使用ref
來執行被定義為該切面的bean。
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
如上,我們定義了一個id為aBean的bean實例,然后使用<aop:aspect>
標簽定義了一個id為myAspect的切面,而該切面指向aBean。
三、定義一個切點(pointcut)
上篇我們說過,切點表達式用于匹配連接點(join point),然后根據配置的增強(Advice)方法在連接點運行時選擇合適時間執行。因為Spring AOP目前僅支持運行方法類型的連接點,所以也可以認為與一個切點匹配一個bean的執行方法。
一個切點包含兩部分:
- 包含方法和名字的簽名;
- 切點表達式:用于匹配具體的方法
因此我們使用xml定義切點如下所示:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
...
</aop:aspect>
</aop:config>
<aop:pointcut>
必須在<aop:aspect>
內部使用定義切點,使用expression
的值指明切點表達式,id為該切點定義類唯一標示方便配置引用。這里并沒有定義切點(pointcut) 簽名,切點簽名定義一般在使用@AspectJ
注解方式定義切點時定義。下面我們著重說明下切點表達式的語法情況。
支持的切點(pointcut)標示
Spring AOP 支持以下AspectJ 的切點標示(AspectJ pointcut designators)簡稱PCD,由于Spring AOP并沒有全部支持所有的PCD,因此若使用了不存在該列表內的標示則會拋出異常。
標簽名 | 說明 |
---|---|
execution | 匹配運行方法的連接點 |
within | 使匹配連接點限定在特定類型 |
this | 限定匹配的連接點是給定類型的實例 |
target | 限定匹配連接點的目標是給定類型的實例 |
args | 限定連接點的參數是給定類型的實例 |
@target | 限定匹配的連接點的運行的對象有該類型的注解 |
@args | 運行時傳遞的參數擁有給定類型的注解 |
@within | 限定匹配給定指定注解類型的連接點 |
@annotation | 限定連接點擁有指定的注解 |
bean | 使得連接點匹配特質的bean或bean集合 |
Tip:我們可以使用
&&
、||
、!
來將連接點表達式關聯起來。
然而在XML配置中該類字符需要轉換,因此我們可以使用更語義化的句子符號and
、or
、not
。
例子
使用execution
例子
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
以上除ret-type-pattern
、name-pattern
、param-pattern
為必須外,其他皆為可選。
- 匹配所有的公有方法
execution(public * *(..)) - 匹配所有以set開頭的方法
execution(* set*(..)) - 匹配所有AccountService接口定義的方法
execution(* com.xyz.service.AccountService.*(..)) - 匹配所有service包內的方法
execution(* com.xyz.service..(..)) - 匹配所有service包以及子包內的方法
execution(* com.xyz.service...(..)) - 所有在service包內的連接點
within(com.xyz.service.*) - 所有service包以及子包內的連接點
within(com.xyz.service..*) - 所有實現AccountService接口的連接點
this(com.xyz.service.AccountService) - 所有的目標對象實現AccountService接口的連接點
target(com.xyz.service.AccountService) - 有一個參數并且運行時傳入參數為Serializable的連接點
args(java.io.Serializable) - 目標對象有@Transactional注解
@target(org.springframework.transaction.annotation.Transactional) - 定義的目標對象類型含有@Transactional注解
@within(org.springframework.transaction.annotation.Transactional) - 含有@Transactional注解的可運行方法
@annotation(org.springframework.transaction.annotation.Transactional) - 含有一個參數并且在運行時傳入的參數含有@Classified注解
@args(com.xyz.security.Classified) - bean名字定義為tradeService的連接點
bean(tradeService) - bean名字滿足Service命名的所有連接點
bean(Service)
四、定義增強方法(Advice)
Before advice
<aop:aspect id="beforeExample" ref="aBean">
<aop:pointcut id="dataAccessOperation"
expression="execution(* com.xyz.myapp.dao.*.*(..))"/>
<aop:before
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
<aop:before>
使用pointcut-ref
引用一個切點,并且指定增強執行方法為doAccessCheck。值的注意的是名為aBean的bean(也就是我們定義的切面)必須實現doAccessCheck方法。該方法會在目標方法執行前執行。
After returning advice
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:pointcut id="dataAccessOperation"
expression="execution(* com.xyz.myapp.dao.*.*(..))"/>
<aop:after-returning
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
同上,該方法會在切點執行正常返回后執行。
當然如果你需要獲取返回對象的話,你需要將配置信息改為如下所示:
<aop:aspect id="afterReturningExample" ref="aBean">
...
<aop:after-returning
pointcut-ref="dataAccessOperation"
returning="retVal"
method="doAccessCheck"/>
...
</aop:aspect>
其中doAccessCheck方法定義如下,其中參數名必須與XML里面配置相同,也就是說必須與XML中的retVal一致。
public void doAccessCheck(Object retVal) {...}
After throwing advice
<aop:aspect id="afterThrowingExample" ref="aBean">
...
<aop:after-throwing
pointcut-ref="dataAccessOperation"
method="doRecoveryActions"/>
...
</aop:aspect>
當然如果需要獲取拋出的異常時我們可以如下配置:
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut-ref="dataAccessOperation"
throwing="dataAccessEx"
method="doRecoveryActions"/>
...
</aop:aspect>
其中doRecoveryActions方法定義如下且必須有名為dataAccessEx參數:
public void doRecoveryActions(DataAccessException dataAccessEx) {...}
After (finally) advice
切點返回后執行如下增強方法(無論正常返回還是異常退出):
<aop:aspect id="afterFinallyExample" ref="aBean">
<aop:after
pointcut-ref="dataAccessOperation"
method="doReleaseLock"/>
...
</aop:aspect>
Around advice 環繞增強方法
該類型的Advice環繞著連接點,該增強方法可以選擇調用或者不掉用連接點方法。
<aop:aspect id="aroundExample" ref="aBean">
<aop:around
pointcut-ref="businessService"
method="doBasicProfiling"/>
...
</aop:aspect>
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
如上所示,我們使用aop:around
來定義環繞增強,方法定義的第一個參數必須為ProceedingJoinPoint類,在Around增強方法中,需要調用processed方法才會執行真正的切點方法,否則則會放棄執行切點方法。
Advice 參數
有時我們需要獲取到目標方法的參數信息,而Spring AOP為我們提供了方便的獲取方式,接下來我們就來了解下如何通過XML配置來獲取目標方法的參數信息。
我們定義如下代碼
package com.aop.learn.service;
public interface StudentQueryService {
/**
* 根據姓名和年齡查詢學生信息,
* 假設無重復現象
*/
Student queryStdent(String name,int age);
//...
}
package com.aop.learn.service.impl;
public class StudentQueryServiceImpl implements StudentQueryService {
public Student queryStdent(String name, int age) {
// do something
// query database or create a new object
}
}
切面例子(攔截器)
package com.aop.learn.interceptor;
public class InterceptorSample {
public Object interceptorMethod(ProceedingJoinPoint call, String name, int age) throws Throwable {
// do something
Object result = call.proceed();
//do something
}
}
配置信息如下
<!--定義bean信息-->
<bean id="aspectSample",class="com.aop.learn.interceptor.InterceptorSample">
<!--配置切面信息-->
<aop:config>
<aop:aspect ref="aspectSample">
<aop:pointcut id="pointsample"
expression="execution(* com.aop.learn.service.StudentQueryService.queryStdent(..))
and args(name, age)"/>
<aop:around pointcut-ref="pointsample"
method="interceptorMethod"/>
</aop:aspect>
</aop:config>
如上配置信息,在expression語句中增加了args(name,age)
,該表達式定義了變量名為name和age的參數,該參數名必須和增強方法中名字一致,這樣我們就能在增強方法中獲取目標方法的參數信息了。
五、總結
本文我們主要學習了如何使用XML配置方式來完成Spring AOP的使用。文章中我們學習了很多配置標簽,完成了切面定義、切點定義以及多種增強方法的定義。至此我們已經掌握了Spring AOP的基本知識點,可在具體的項目開發中使用。不過學了那么多的知識點,我們還是需要一定的實踐來進行消化吸收。接下來我們將構造例子用來鞏固學習到的知識點。