一.什麼是面向切面程式設計
- AOP簡介
AOP為Aspect Oriented Programming的縮寫,意為:
面向切面程式設計,通過
預編譯方式和運作期間動态代理實作程式功能的統一維護的一種技術。
- 為什麼使用AOP程式設計範式?
分離功能性需求和非功能性需求
集中處理某一關注點
侵入性少,增強代碼可讀性及可維護性
- AOP應用場景
權限控制、緩存控制、事務控制、分布式追蹤、異常處理等
- 舉個栗子
如果你要在Service層的某些特定方法需加上權限驗證,使用OOP思想的話隻能在方法内部添加驗證身份的代碼,例如
public void insert() {
checkUserAdmin.check(); //加入權限驗證方法
repository.insert(); //調用dao層插入資料庫一條記錄
}
這樣看起來功能是實作了,但如果service層有很多insert和delete方法呢?這樣插入代碼的方式不易于我們去統一管理,且修改了原代碼,具有侵入性。
那麼使用了AOP之後呢?你可以建一個切面類,對要進行權限驗證的方法進行切入。
即在程式運作時,動态地将代碼切入到類的指定方法或位置上的思想,就是面向切面程式設計。
二.AOP常用術語
- 要想使用面向對象程式設計的思想,首先要了解幾個專有名詞
• Target:目标類,即需要被代理的類。例如:UserService
• Joinpoint(連接配接點):所謂連接配接點是指那些可能被攔截到的方法。例如:所有的方法
• PointCut 切入點:已經被增強的連接配接點。例如:addUser()
• Advice 通知/增強,增強代碼。例如:after、before
• Weaving(織入):是指把增強advice應用到目标對象target來建立新的代理對象proxy的過程.
• Proxy 代理類
• Aspect(切面): 是切入點pointcut和通知advice的結合
三.Advice-五種增強方式
- 例如在執行某個特定方法的時候,我們可以選擇不同的增強方式(如前置通知/增強,在方法運作前執行),達到我們織入後的不同效果。
前置通知:在我們執行目标方法之前運作(@Before)
@Pointcut("within(com.example.demo.Service.*)")
public void matchType(){}
@Before("matchType()") //可在此加入JoinPoint列印切點資訊
public void before(JoinPoint joinPoint){
System.out.println("------【前置通知】------" + joinPoint);
}
後置通知:在我們目标方法運作結束之後 ,不管有沒有異常(@After)
@After(value="execution(* com.example.aspectJ.demo1.ProductDao.findAll(..))")
public void after(){
System.out.println("最終通知==================");
傳回通知:在我們的目标方法正常傳回值後運作(@AfterReturning)
@AfterReturning(value="execution(* com.example.aspectJ.demo1.ProductDao.update(..))" ,returning = "result")
public void afterReturning(Object result){ //通過returning屬性,定義方法傳回值作為參數
System.out.println("後置通知========="+result);
}
異常通知:在我們的目标方法出現異常後運作(@AfterThrowing)
//通過設定throwing屬性,可以設定發生異常對象參數
@AfterThrowing(value = "execution(* com.example.aspectJ.demo1.ProductDao.findOne(..))",throwing = "e")
public void afterThrowing(Throwable e){
System.out.println("抛出異常通知"+e.getMessage());
}
環繞通知:動态代理, 需要手動執行joinPoint.procced()(其實就是執行我們的目标方法執行之前相當于前置通知, 執行之後就相當于我們後置通知(@Around)
@Around(value = "execution(* com.example.aspectJ.demo1.ProductDao.delete(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("環繞前通知");
Object obj = joinPoint.proceed(); //執行目标方法
System.out.println("環繞後通知");
return obj;
}
四.SpringAOP使用詳解
切面表達式
- 下面是一張思維導圖總結
excution表達式
- execution(
- 修飾符pattern
- 傳回值pattern
- 描述包名
- 方法名(參數)
- 方法抛出異常pattern
)
- 代碼示例
@Pointcut("execution(public * com.example.controller.*Controller.*(..))") public void match(){} @Before("match()") public void before(){ //前置通知... }
within表達式
-
//比對StudentService類裡所有方法 @Pointcut("within(com.example.service.StudentService)") public void matchType(){} //比對com.example包及子包下所有類方法 @Pointcut("within(com.example..*)") public void matchPackage(){}
對象比對
-
/*public class serviceImpl implements service*/ //比對AOP對象的目标對象為指定類型方法,即serviceImpl的aop代理對象方法 @Pointcut("this(com.example.serviceImpl)") public void thisDemo(){} //比對實作service接口的目标對象(非aop代理後的對象)方法,這裡指的就是serviceImpl的方法 @Pointcut("target(com.example.service)") public void targetDemo(){} //比對所有以Service結尾的bean中方法 @Pointcut("bean(*Service)") public void beanDemo(){}
參數比對
-
//比對任何以find開頭且隻有一個Long參數的方法 @Pointcut("execution(* *..find*(Long))") public void argsDemo1(){} //比對任何隻有一個Long參數的方法 @Pointcut("args(Long)") public void argsDemo2(){} //比對任何以find開頭且第一個參數為Long的方法 @Pointcut("execution(* *..find*(Long,..))") public void argsDemo3(){} //比對第一個參數為Long的方法 @Pointcut("args(Long,..)") public void argsDemo4(){}
注解比對
-
//比對方法注解有@AdminOnly的方法 @Pointcut("@annotation(com.example.security.AdminOnly)") public void annoDemo(){} //比對注解有@Test1的類下所有方法,要求注解的RetentionPolicy級别為CLASS @Pointcut("@within(com.example.annotation.Test1)") public void annoWithinDemo(){} //比對注解有@Test2類下所有方法,要求注解的RetentionPolicy級别為RUNTIME @Pointcut("@target(com.example.repository.Test2)") public void annoTargetDemo(){} //比對傳入參數類具有@Test3的注解的方法(例如student實體類有注解@Test3,隻要方法傳入student類就會被攔截) @Pointcut("@args(org.example.repository.Test3)") public void annoArgsDemo(){}
至于AOP的實作原理,這裡暫時不講,有興趣的可以去了解下jdk的動态代理,AOP就是基于此實作的。
更多内容,歡迎通路我的:
515code部落格 bilibili視訊 Github