本文介紹Spring AOP,面向切面編程。在權限驗證、保存日誌、安全檢查和事務控制等多個應用場景之下都會依賴該技術。以下是在自己學習過程中的一些總結,如有錯誤,還望指正。
面向切面的定義
面向切面編程(AOP),全稱 Aspect Oriented Programming。是基於面向對象編程之上的編程思想。指的是在程序運行期間,將某段代碼動態的切入到指定方法的指定位置進行運行的編程方式。
動態代理可以非耦合動態插入代碼,我們之前介紹過,JDK動態代理相關內容請移步:JDK動態代理
AOP的幾個術語
切入點:我們真正需要執行日誌記錄的地方。連接點:每一個方法的每一個位置都是一個連接點。通過使用切入點表達式在衆多的連接點中選出我們感興趣的地方。
案例入門
1. 切面類
@Aspect
@Componet
public class AspectClass {
/**
@Before:目標方法執行之前 前置通知
@After:目標方法結束之後 後置通知
@AfterReturning:在目標方法正常返回之後 返回通知
@AfterThrowing:在目標方法拋出異常之後 異常通知
@Around:環繞通知 環繞通知
對比動態代理:
try{
@Before
method.invoke(obj,args);
@AfterReturning
}catch(e){
@AfterThrowing
}finally{
@After
}
*/
/**
指明要切入那個類的那個方法
execution(訪問修飾符 返回值類型 方法簽名)
/
@Before("execution(public String cn.joncy.MyNeedProxyClass.method1(String))")
public static void methodStart(){
do something
}
/*
切某個類下的所有方法 *
*/
@AfterReturning("execution(public String cn.joncy.MyNeedProxyClass.*(String))")
public static void methodReturn(){
do something
}
@AfterThrowing("execution(public String cn.joncy.MyNeedProxyClass.*(String))")
public static void methodException(){
do something
}
@After("execution(public String cn.joncy.MyNeedProxyClass.*(String))")
public static void methodEnd(){
do something
}
}
2. 測試執行
@Test
public void test(){
// 此處傳入的是接口類型
NeedProxyClass npc = ioc.getBean(NeedProxyClass.class);
npc.method1("low");
}
AOP使用細節
1. IOC容器中保存代理對象
從ioc容器中拿到目標對象,參數傳遞的是被代理類的接口,因爲接口是被代理類和代理類產生關聯的東西。AOP底層是動態代理,容器中保存的組件是它的代理對象(com.sun.proxy.$Proxyxxx)
2. 切入點表達式
’ * ’ :
可以匹配一個或者多個字符:execution(public String cn.joncy.MyNeed*y.*(String ))
匹配任意一個參數:第一個是String 第二個隨意:execution(public String cn.joncy.MyNeed*.*(String ,*))
只能匹配一層路徑
權限位置要麼寫上,要麼不要寫,不能寫*
‘..’:
匹配任意多個參數,任意類型參數
匹配任意多層路徑:execution(public String cn.*.MyNeed*.*(String ,)) 一層
execution(public String cn..MyNeed*.*(String ,)) 多層
最精確匹配:
execution(public String cn.joncy.MyNeedProxyClass.method1(String))
最模糊匹配:
execution(* *(..))
execution(* *.*(..)) 雙點不能直接寫
切入點表達式還支持:&& 、|| 、!
3. 通知的執行順序
正常執行:@Before => @After => @AfterReturning
異常執行:@Before => @After => @AfterThrowing
4. JoinPoint的使用
爲通知方法加上JoinPoint參數,該參數封裝了當前目標方法的詳細信息
//獲取方法參數
Object[] args = joinPoint.getArgs();
System.out.println(Arrays.asList(args));
//獲取方法名
Signature signature joinPoint.getSignature();
String name signature.getName();
接收返回值結果和異常信息
@AfterReturning(value="execution(public String cn.joncy.MyNeedProxyClass.*(String))",returning="result")
// result的類型可以變得更加具體 比如變爲int String等
public static void methodReturn(JoinPoint joinPoint,Object result){
do something
}
@AfterThrowing(value="execution(public String cn.joncy.MyNeedProxyClass.*(String))",throwing="exception")
// 此處是最大範圍的異常,可以縮小比如接數組越界或空指針異常等
public static void methodException(JoinPoint joinPoint,Exception exception){
do something
}
Spring對通知方法的訪問修飾符、返回值類型要求不嚴格,唯一有要求的就是通知方法的參數,因爲Spring需要知道這些參數都是什麼,才能幫我們調用
可以抽取可重用的切入點表達式,此表達式空實現,其他通知方法的切入點表達式的value引用該方法簽名
5. 環繞通知
環繞通知是其他四個通知的集合體,也是最常用的通知,是最類似動態代理的通知
@Around(execution(public String cn.joncy.MyNeedProxyClass.method1(String)))
public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
Object[] args = pjg.getArgs();
Object proceed = null;
try{
// 此處添加內容 == 前置通知
proceed = pjp.proceed(args);
// 此處添加內容 == 返回通知
}catch(Exception e){
// 此處添加內容 == 異常通知
e.printStack();
}finally{
// 此處添加內容 == 後置通知
}
// 這個值決定了執行方法的最終返回值
return proceed;
}
環繞通知的執行順序:
環繞前置 => 普通前置 => 目標方法執行 => 環繞正常返回/出現異常(拋出異常) => 環繞後置 => 普通後置 => 普通返回/異常
6. 多切面執行順序
如圖,只需注意在一個切面中環繞前置要比普通前置先執行,環繞隻影響當前切面,所以如果切面二存在環繞通知,兩個切面都存在普通通知,執行順序爲:
切面二環繞前置=>切面二普通前置=>切面一普通前置=>目標方法=>切面一後置=>切面一返回=>切面一環繞返回=>切面一環繞後置=>切面一後置=>切面一返回