spring AOP動態代理使用和配置方式(手打)
理論的東西,隨便看下吧,
AOP是OOP的延續,是Aspect Oriented Programming的縮寫,意思是面向切面程式設計。可以通過預編譯方式和執行期動態代理實現在不修改原始碼的情況下給程式動態統一新增功能的一種技術。AOP實際是GoF設計模式的延續,設計模式孜孜不倦追求的是呼叫者和被呼叫者之間的解耦,AOP可以說也是這種目標的一種實現。
我們現在做的一些非業務,如:日誌、事務、安全等都會寫在業務程式碼中(也即是說,這些非業務類橫切於業務類),但這些程式碼往往是重複,複製——貼上式的程式碼會給程式的維護帶來不便,AOP就實現了把這些業務需求與系統需求分開來做。這種解決的方式也稱代理機制。
先來了解一下AOP的相關概念,《Spring參考手冊》中定義了以下幾個AOP的重要概念,結合以上程式碼分析如下:
- 切面(Aspect):官方的抽象定義為“一個關注點的模組化,這個關注點可能會橫切多個物件”,在本例中,“切面”就是類TestAspect所關注的具體行為,例如,AServiceImpl.barA()的呼叫就是切面TestAspect所關注的行為之一。“切面”在ApplicationContext中<aop:aspect>來配置。
- 連線點(Joinpoint) :程式執行過程中的某一行為,例如,UserService.get的呼叫或者UserService.delete丟擲異常等行為。
- 通知(Advice) :“切面”對於某個“連線點”所產生的動作,例如,TestAspect中對com.spring.service包下所有類的方法進行日誌記錄的動作就是一個Advice。其中,一個“切面”可以包含多個“Advice”,例如ServiceAspect。
- 切入點(Pointcut) :匹配連線點的斷言,在AOP中通知和一個切入點表示式關聯。例如,TestAspect中的所有通知所關注的連線點,都由切入點表示式execution(* com.spring.service.*.*(..))來決定。
- 目標物件(Target Object) :被一個或者多個切面所通知的物件。例如,AServcieImpl和BServiceImpl,當然在實際執行時,Spring
AOP採用代理實現,實際AOP操作的是TargetObject的代理物件。
- AOP代理(AOP Proxy) :在Spring AOP中有兩種代理方式,JDK動態代理和CGLIB代理。預設情況下,TargetObject實現了介面時,則採用JDK動態代理,例如,AServiceImpl;反之,採用CGLIB代理,例如,BServiceImpl。強制使用CGLIB代理需要將 <aop:config>的 proxy-target-class屬性設為true。
通知(Advice)型別:
- 前置通知(Before advice):在某連線點(JoinPoint)之前執行的通知,但這個通知不能阻止連線點前的執行。ApplicationContext中在<aop:aspect>裡面使用<aop:before>元素進行宣告。例如,TestAspect中的doBefore方法。
- 後置通知(After advice):當某連線點退出的時候執行的通知(不論是正常返回還是異常退出)。ApplicationContext中在<aop:aspect>裡面使用<aop:after>元素進行宣告。例如,ServiceAspect中的returnAfter方法,所以Teser中呼叫UserService.delete丟擲異常時,returnAfter方法仍然執行。
- 返回後通知(After return advice):在某連線點正常完成後執行的通知,不包括丟擲異常的情況。ApplicationContext中在<aop:aspect>裡面使用<after-returning>元素進行宣告。
- 環繞通知(Around advice):包圍一個連線點的通知,類似Web中Servlet規範中的Filter的doFilter方法。可以在方法的呼叫前後完成自定義的行為,也可以選擇不執行。ApplicationContext中在<aop:aspect>裡面使用<aop:around>元素進行宣告。例如,ServiceAspect中的around方法。
- 丟擲異常後通知(After throwing advice):在方法丟擲異常退出時執行的通知。ApplicationContext中在<aop:aspect>裡面使用<aop:after-throwing>元素進行宣告。例如,ServiceAspect中的returnThrow方法。
注:可以將多個通知應用到一個目標物件上,即可以將多個切面織入到同一目標物件。
使用Spring AOP可以基於兩種方式,一種是比較方便和強大的註解方式,另一種則是中規中矩的xml配置方式。
先說註解,使用註解配置Spring AOP總體分為兩步,第一步是在xml檔案中宣告啟用自動掃描元件功能,同時啟用自動代理功能(同時在xml中新增一個UserService的普通服務層元件,來測試AOP的註解功能):
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-lazy-init="true">
<!-- 啟用元件掃描功能,在包cn.ysh.studio.spring.aop及其子包下面自動掃描通過註解配置的元件 -->
<context:component-scan base-package="cn.whb.test.springAop"/>
<!-- 啟用自動代理功能 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- spring2.x後 -->
<!-- 目標物件 -->
<bean id="userLoginImpl" class="cn.whb.test.aop.UserLoginImpl"/>
</beans>
package cn.whb.test.springAop;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 切面
* @todo TODO
* @author wanghb
* @date 2016-4-15
*/
@Component
@Aspect
public class LogAdvice {
private final static Log log = LogFactory.getLog(LogAdvice.class);
//配置切入點,該方法無方法體,主要為方便同類中其他方法使用此處配置的切入點
@Pointcut("execution(* saveUser*(..))")
public void aspect(){}
/*
* 配置前置通知,使用在方法aspect()上註冊的切入點
* 同時接受JoinPoint切入點物件,可以沒有該引數
*/
@Before("aspect()")
public void beforelog(JoinPoint joinPoint){
if(log.isInfoEnabled())
log.info("before===" + joinPoint);
System.out.println("之前的事情");
}
@After("aspect()")
public void afterlog(JoinPoint joinPoint){
if(log.isInfoEnabled())
log.info("afterlog===" + joinPoint);
System.out.println("之後的事情");
}
//配置環繞通知,使用在方法aspect()上註冊的切入點
@Around("aspect()")
public void around(JoinPoint joinPoint){
long start = System.currentTimeMillis();
try {
//執行 連線點 的方法(比如save()方法)
((ProceedingJoinPoint) joinPoint).proceed();
long end = System.currentTimeMillis();
if(log.isInfoEnabled()){
log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms!");
}
} catch (Throwable e) {
long end = System.currentTimeMillis();
if(log.isInfoEnabled()){
log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " + e.getMessage());
}
}
}
//配置後置返回通知,使用在方法aspect()上註冊的切入點
@AfterReturning("aspect()")
public void afterReturn(JoinPoint joinPoint){
if(log.isInfoEnabled()){
log.info("afterReturn " + joinPoint);
}
}
//配置丟擲異常後通知,使用在方法aspect()上註冊的切入點
@AfterThrowing(pointcut="aspect()", throwing="ex")
public void afterThrow(JoinPoint joinPoint, Exception ex){
if(log.isInfoEnabled()){
log.info("afterThrow " + joinPoint + "\t" + ex.getMessage());
}
}
}
以上是通過註解的方式,下面是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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-lazy-init="true">
<!-- 啟用元件掃描功能,在包cn.ysh.studio.spring.aop及其子包下面自動掃描通過註解配置的元件 -->
<context:component-scan base-package="cn.whb.test.springAop"/>
<!-- 啟用自動代理功能 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- spring2.x後 -->
<!-- 目標物件 -->
<bean id="userLoginImpl" class="cn.whb.test.aop.UserLoginImpl"/>
<!-- 通知 -->
<bean id="logAdvice" class="cn.whb.test.springAop.LogAdvice"/>
<aop:config>
<aop:aspect id="logAspect" ref="logAdvice">
<!-- 切入點 -->
<aop:pointcut id="beforePointCut" expression="execution(* saveUser*(..))"/>
<aop:pointcut id="afterPointCut" expression="execution(* saveUser*(..))"/>
<!-- 織入(通知作用於切入點) -->
<aop:before method="beforelog" pointcut-ref="beforePointCut"/>
<aop:after method="afterlog" pointcut-ref="afterPointCut"/>
</aop:aspect>
</aop:config>
</beans>
注意:
需要匯入的包
匯入cglib-nodep-2.1_3.jar包(不能是cglib-nodep-2.2.jar,或者匯入asm-2.2.3.jar和cglib-2.2.jar )
以下圖片的jar並不是所有都是的。