Spring 學習之路(九):Spring 中的AOP(二):事務通知
阿新 • • 發佈:2019-02-10
AspectJ
- 目前,spring 框架中我們可以使用基於 AspectJ 註解或者是基於XML配置的 AOP(主流是使用 AspectJ ,簡單,方便)
如何配置AspectJ
- 簡單理解,AspectJ 就是一個支援 aop 的第三方元件,spring 提供了很好的支援,我們只需要將 對應的 jar 包加入我們的專案即可(對應 jar 包可以在我的原始碼下載)
如圖:
配置檔案中宣告 使用 AspectJ 註解
- 引入aop名稱空間
- 使 AspectJ 註解生效
- 引入aop名稱空間
AspectJ 註解工作流程
- 在 Spring 中宣告 AspectJ 切面, 只需要在 IOC 容器中將切面宣告為 Bean 例項. 當在 Spring IOC 容器中初始化 AspectJ 切面之後, Spring IOC 容器就會為那些與 AspectJ 切面相匹配的 Bean 建立代理
在 AspectJ 註解中, 切面只是一個帶有 @Aspect 註解的 Java 類
通知是標註有某種註解的簡單的 Java 方法
- 對以上流程不清楚的話,我們直接看程式碼
前置通知
//把該類宣告為一個切面:需要把該類放入到ioc容器中,然後再宣告為一個切面
@Aspect
@Component
public class LogginAspect {
// 宣告該方法是一個前置通知,在目標方法開始之前執行
@Before("execution(void com.zc.cris.beans.spring.aop.impl.Chinese.*(String))")
public void beforeMethod(JoinPoint joinPoint) {
// 獲取方法簽名和引數集合
System.out.println(joinPoint.getSignature().getName() + "-----" + Arrays.asList(joinPoint.getArgs()));
System.out.println("我是方法的前置通知");
}
- 測試程式碼
@Test
void testProxy() {
//建立ioc容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//獲取bean
People bean = context.getBean(People.class);
// System.out.println(bean);
// System.out.println(bean.getClass().getName());
//System.out.println(bean instanceof Chinese); //false
//使用bean
bean.eat("筷子");
bean.say("中文");
}
console:
後置通知
// 宣告該方法是一個後置通知,在目標方法執行後執行(無論目標方法是否發生異常)
// 且後置通知無法訪問目標方法的返回值
@After("execution(* com.zc.cris.beans.spring.aop.impl.*.*(String))")
public void afterMethod(JoinPoint joinPoint) {
System.out.println("我是方法的後置通知");
}
- 測試程式碼同上
console:
返回通知
// 宣告該方法為返回通知:方法正常執行結束後執行的程式碼
// 返回 通知是可以訪問到方法的返回值的!
/*切點表示式表示執行任意類的任意方法. 第
一個 * 代表匹配任意修飾符及任意返回值, 第二個 * 代表任意類的物件,
第三個 * 代表任意方法, 引數列表中的 .. 匹配任意數量的引數
*/
@AfterReturning(value = "execution(public void com.zc.cris.beans.spring.aop.impl.*.*(..))", returning = "result")
public void afterRetruning(JoinPoint joinPoint, Object result) {
System.out.println("我是方法的返回通知" + joinPoint.getSignature().getName() + "^^^^"
+ Arrays.asList(joinPoint.getArgs() + "我是方法的返回值" + result));
}
- 測試程式碼同上
console:
異常通知
//目標方法出現異常才會指定的程式碼
//可以訪問到異常物件,且可以指定出現特定的異常(NullPointException)才會執行
@AfterThrowing(value = "execution(public void com.zc.cris.beans.spring.aop.impl.*.*(..))",
throwing="e")
public void afterThrowing(JoinPoint joinPoint, Exception e) {
System.out.println("我是目標方法發生異常才執行的通知:"+e.getMessage());
}
- 測試程式碼同上
console:
- 通過以上四種通知型別的應用我們大致瞭解Spring 的aop通過 AspectJ 元件是如何完成的,讓我們再 修改之前的代理類,加深理解
環繞通知
//環繞通知:必須攜帶 ProceedingJoinPoint 型別的引數
//環繞通知類似於動態代理的全過程:ProceedingJoinPoint 型別的引數可以決定目標方法的執行,
//環繞通知必須要有返回值,返回值其實就是目標方法的返回值
@Around(value = "execution(public void com.zc.cris.beans.spring.aop.impl.*.*(..))")
public Object around(ProceedingJoinPoint pjt) {
Object result = null;
String methodName = pjt.getSignature().getName();
try {
//前置通知
System.out.println("我是環繞通知的前置通知!!!!!");
//執行目標方法
result = pjt.proceed();
//返回通知
System.out.println("我是環繞通知的後置通知");
} catch (Throwable e) {
//異常通知
System.out.println("我是環繞通知的異常通知"+e.getMessage());
throw new RuntimeException(e);
}
//後置通知
System.out.println("我是環繞通知的後置通知");
return result;
}
console:
切面的優先順序
- 假如我們現在有兩個切面類,一個負責引數驗證,一個負責日誌記錄,那麼我們如何確定這兩個切面類誰先執行,誰後執行呢?
//使用 @Order(1) 註解指定切面的優先順序,數字越小,優先順序越高
@Order(1)
@Aspect
@Component
public class ValidationAspect {
@Before(value = "execution(* com.zc.cris.beans.spring.aop.impl.*.*(..))")
public void validate(JoinPoint joinPoint) {
System.out.println("------- validation-----"+ Arrays.asList(joinPoint.getArgs()));
}
}
@Order(2)
//把該類宣告為一個切面:需要把該類放入到ioc容器中,然後再宣告為一個切面
@Aspect
@Component
public class LogginAspect {
console:
切面表示式的重用
- 通過上面的測試,我們發現每個通知的註解裡都需要寫相同的切面表示式,這明顯不符合我們的風格,著名程式設計大師馬丁·富勒 就曾經說過,程式碼有很多種壞味道,而重複是最壞的一種,事實上通過一個小小的註解就可以搞定
/*
* 定義一個方法,專門用來宣告切入點表示式,一般的,該方法中不需要再寫任何程式碼
* 使用@Pointcut 註解來宣告
* 後面的其他通知直接使用該方法名來引用當前的切入點表示式即可
*/
@Pointcut("execution(* com.zc.cris.beans.spring.aop.impl.*.*(String))")
public void declaredJointPointExpresson() {};
// 宣告該方法是一個前置通知,在目標方法開始之前執行
@Before("declaredJointPointExpresson()")
@Before(value = "com.zc.cris.beans.spring.aop.impl.LogginAspect.declaredJointPointExpresson()")
- 我們通過@Pointcut 註解對切面表示式進行了重構,當前類的通知或者其他包的類的通知,都可以使用,以達到簡潔,高效的目的
通過xml配置檔案來配置spring的事務通知(不推薦,看完程式碼你就知道為什麼了,瞭解即可)
- applicationContext.aopXML.xml
<bean id="chinese" class="com.zc.cris.beans.spring.aop.impl.xml.Chinese"></bean>
<bean id="validationAspect" class="com.zc.cris.beans.spring.aop.impl.xml.ValidationAspect"></bean>
<bean id="logginAspect" class="com.zc.cris.beans.spring.aop.impl.xml.LogginAspect"></bean>
<!-- 配置aop -->
<aop:config>
<!-- 定義切入點表示式 -->
<aop:pointcut expression="execution(* com.zc.cris.beans.spring.aop.impl.xml.*.*(String))" id="pointCut"/>
<!-- 定義一個切面物件 -->
<aop:aspect ref="logginAspect" order="2">
<!-- 定義各種通知 -->
<aop:before method="beforeMethod" pointcut-ref="pointCut"/>
<aop:after method="afterMethod" pointcut-ref="pointCut"/>
<aop:after-returning method="afterRetruning" returning="result" pointcut-ref="pointCut"/>
<aop:after-throwing method="afterThrowing" throwing="e" pointcut-ref="pointCut"/>
<!-- <aop:around method="around"/> -->
</aop:aspect>
<aop:aspect ref="validationAspect" order="1">
<aop:before method="validate" pointcut-ref="pointCut"/>
</aop:aspect>
</aop:config>
取消我們aop切面類上的所有切面註解,然後進行測試發現console列印的和之前測試一毛一樣