Spring之AOP深入淺出
目錄
基本概念
* 切面類;把切面應用到目標函式的過程叫做織入; * joinPoint(連線點):指哪些目標函式可以被攔截; * pointcut(切入點):指對joinPoint中的那些目標函式進行切入; * advice(通知):在某個特點的pointcut(切入點)上需要執行的動作,如日誌記錄、許可權驗證等具體要應用帶切入點的程式碼 * aspect(切面):由切點和通知相結合而成,定義通知應用到那些切入點上; * weaving(織入):把切面的程式碼織入(應用)到目標函式的過程; * 織入的方式:動態織入和靜態織入; * 動態織入:執行時動態講要增強對的程式碼織入目標類中,這樣往往是用過動態代理技術完成的,如JDK動態代理和CGLIB代理;Spring AOP是動態代理; * 靜態織入:AspectJ採用的是靜態織入的方式,編譯期間織入;先將aspect類編譯成class位元組碼後,在java目標類編譯時織入。
-
萬用字元 (.. + *)
.. 匹配方法定義中的任意數量的引數,此外還匹配類定義中的任意數量包;
//任意返回值,任意名稱,任意引數的公共方法
execution(public * *(..))
//匹配com.cn.dao包及其子包中所有類中的所有方法
within(com.cn.dao..*)
+ 匹配給定類的任意子類
//匹配實現了DaoUser介面的所有子類的方法
within(com.cn.dao.DaoUser+)
* 匹配任意數量的字元
//匹配com.cn.service包及其子包中所有類的所有方法 within(com.cn.service..*) //匹配以set開頭,引數為int型別,任意返回值的方法 execution(* set*(int))
-
型別簽名表示式
為了方便型別(如介面、類名、包名)過濾方法,Spring AOP 提供了within關鍵字。其語法格式如下:
within(<type name>)
type name 則使用包名或者類名替換即可,例如:
//匹配com.zejian.dao包及其子包中所有類中的所有方法 @Pointcut("within(com.cn.dao..*)") //匹配UserDaoImpl類中所有方法 @Pointcut("within(com.cn.dao.UserDaoImpl)") //匹配UserDaoImpl類及其子類中所有方法 @Pointcut("within(com.cn.dao.UserDaoImpl+)") //匹配所有實現UserDao介面的類的所有方法 @Pointcut("within(com.cn.dao.UserDao+)")
-
方法簽名表示式
如果想根據方法簽名進行過濾,關鍵字execution可以幫到我們,語法表示式如下:
//scope :方法作用域,如public,private,protect
//returnt-type:方法返回值型別
//fully-qualified-class-name:方法所在類的完全限定名稱
//parameters 方法引數
execution(<scope> <return-type> <fully-qualified-class-name>.*(parameters))
對於給定的作用域、返回值型別、完全限定類名以及引數匹配的方法將會應用切點函式指定的通知,例如:
//匹配UserDaoImpl類中的所有方法
@Pointcut("execution(* com.cn.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl類中的所有公共的方法
@Pointcut("execution(public * com.cn.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl類中的所有公共方法並且返回值為int型別
@Pointcut("execution(public int com.cn.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl類中第一個引數為int型別的所有公共的方法
@Pointcut("execution(public * com.cn.dao.UserDaoImpl.*(int , ..))")
-
其他指示符
-
bean:
Spring AOP擴充套件的,AspectJ沒有對於指示符,用於匹配特定名稱的Bean物件的執行方法;
//匹配名稱中帶有後綴Service的Bean。
@Pointcut("bean(*Service)")
private void myPointcut1(){}
-
this
用於匹配當前AOP代理物件型別的執行方法;請注意是AOP代理物件的型別匹配,這樣就可能包括引入介面也型別匹配;
//匹配了任意實現了UserDao介面的代理物件的方法進行過濾
@Pointcut("this(com.cn.spring.springAop.dao.UserDao)")
private void myPointcut2(){}
-
target
用於匹配當前目標物件型別的執行方法;
//匹配了任意實現了UserDao介面的目標物件的方法進行過濾
@Pointcut("target(com.cn.spring.springAop.dao.UserDao)")
private void myPointcut3(){}
-
@within
用於匹配所以持有指定註解型別內的方法;請注意與within是有區別的, within是用於匹配指定型別內的方法執行;
//匹配使用了MarkerAnnotation註解的類(注意是類)
@Pointcut("@within(com.cn.spring.annotation.MarkerAnnotation)")
private void myPointcut4(){}
-
@annotation
根據所應用的註解進行方法過濾;
//匹配使用了MarkerAnnotation註解的方法(注意是方法)
@Pointcut("@annotation(com.cn.spring.annotation.MarkerAnnotation)")
private void myPointcut5(){}
-
邏輯運算子
這裡最後說明一點,切點指示符可以使用運算子語法進行表示式的混編,如and、or、not(或者&&、||、!),如下一個簡單例子:
//匹配了任意實現了UserDao介面的目標物件的方法並且該介面不在com.cn.dao包及其子包下
@Pointcut("target(com.cn.spring.springAop.dao.UserDao) !within(com.cn.dao..*)")
private void myPointcut6(){}
//匹配了任意實現了UserDao介面的目標物件的方法並且該方法名稱為addUser
@Pointcut("target(com.cn.spring.springAop.dao.UserDao)&&execution(* com.cn.spring.springAop.dao.UserDao.addUser(..))")
private void myPointcut7(){}
-
通知函式
-
前置通知@Before
前置通知通過@Before註解進行標註,並可直接傳入切點表示式的值,該通知在目標函式執行前執行,注意JoinPoint,是Spring提供的靜態變數,通過joinPoint 引數,可以獲取目標物件的資訊,如類名稱,方法引數,方法名稱等,,該引數是可選的。
@Before("doAspectWithin()")
public void beforeAspect(JoinPoint joinPoint) {
LOGGER.info("Begin to run...");
System.out.println(joinPoint.getTarget().getClass());
}
-
後置通知@AfterReturning
通過@AfterReturning註解進行標註,該函式在目標函式執行完成後執行,並可以獲取到目標函式最終的返回值returnVal,當目標函式沒有返回值時,returnVal將返回null,必須通過returning = “returnVal”註明引數的名稱而且必須與通知函式的引數名稱相同。請注意,在任何通知中這些引數都是可選的,需要使用時直接填寫即可,不需要使用時,可以完成不用宣告出來。
/**
* 後置通知,不需要引數時可以不提供
*/
@AfterReturning(value="execution(* com.cn.spring.springAop.dao.UserDao.*User(..))")
public void AfterReturning(){
System.out.println("我是後置通知...");
}
/**
* 後置通知
* returnVal,切點方法執行後的返回值
*/
@AfterReturning(value="execution(* com.cn.spring.springAop.dao.UserDao.*User(..))",returning = "returnVal")
public void AfterReturning(JoinPoint joinPoint,Object returnVal){
System.out.println("我是後置通知...returnVal+"+returnVal);
}
-
異常通知 @AfterThrowing
該通知只有在異常時才會被觸發,並由throwing來宣告一個接收異常資訊的變數,同樣異常通知也用於Joinpoint引數,需要時加上即可.
@AfterThrowing(value = "execution(* com.cn.smart.controller.HelloWorldController.*(..))", throwing = "e")
public void afterThrowable(Throwable e) {
LOGGER.error("Run failed, for " + e.getMessage());
}
-
最終通知 @After
該通知有點類似於finally程式碼塊,只要應用了無論什麼情況下都會執行。
/**
* 無論什麼情況下都會執行的方法
* joinPoint 引數
*/
@After("execution(* com.cn.spring.springAop.dao.UserDao.*User(..))")
public void after(JoinPoint joinPoint) {
System.out.println("最終通知....");
}
-
環繞通知@Around
環繞通知既可以在目標方法前執行也可在目標方法之後執行,更重要的是環繞通知可以控制目標方法是否指向執行,但即使如此,我們應該儘量以最簡單的方式滿足需求,在僅需在目標方法前執行時,應該採用前置通知而非環繞通知。案例程式碼如下第一個引數必須是ProceedingJoinPoint,通過該物件的proceed()方法來執行目標函式,proceed()的返回值就是環繞通知的返回值。同樣的,ProceedingJoinPoint物件也是可以獲取目標物件的資訊,如類名稱,方法引數,方法名稱等等。 --------------------- 本文來自 zejian_ 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/javazejian/article/details/56267036?utm_source=copy
@Around("execution(* com.cn.spring.springAop.dao.UserDao.*User(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("我是環繞通知前....");
//執行目標函式
Object obj= (Object) joinPoint.proceed();
System.out.println("我是環繞通知後....");
return obj;
}
-
通知傳遞引數
待續。。。
-
Aspect優先順序
待續。。。
-
Demo
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface LogAspect {
//執行的操作型別,例如:Add
public String operationType() default "";
//執行的具體操作名稱:新增使用者
public String operationName() default "";
}
切面類:
@Aspect //切面類註解
@Component
public class SystemLogAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(SystemLogAspect.class);
//使用了LogAspect註解的方法會被切
@Pointcut("@annotation(com.cn.smart.annotation.LogAspect)")
public void doAspectAnnotation() {
}
//所有的該包下面的所有的方法(目標方法)都會被加入切面
@Pointcut("execution(* com.cn.smart.controller.*.*(..))")
public void doAcpectExecution() {
}
//
@Pointcut("within(com.cn.smart.controller.*)")
public void doAspectWithin() {
}
//環繞通知,即在執行proceed()方法前後執行橫切關注點;帶名的;
@Around("doAspectAnnotation()")
public Object aroundAspect(final ProceedingJoinPoint joinPoint) throws Throwable {
Long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
LOGGER.info("Run time: {}", System.currentTimeMillis() - startTime);
return result;
}
//後置通知
@After("doAcpectExecution()")
public void afterAspect() {
LOGGER.info("Run finished!!!");
}
@Before("doAspectWithin()")
public void beforeAspect(JoinPoint joinPoint) {
LOGGER.info("Begin to run...");
System.out.println(joinPoint.getTarget().getClass());
}
@AfterThrowing(value = "execution(* com.cn.smart.controller.HelloWorldController.*(..))", throwing = "e")
public void afterThrowable(Throwable e) {
LOGGER.error("Run failed, for " + e.getMessage());
}
@AfterReturning(value = "execution(* com.cn.smart.controller.HelloWorldController.*(..))", returning = "result")
public void afterReturning(Object result) {
LOGGER.info("Run return:{}", result);
}
}
controller層的程式碼:
@RestController
@RequestMapping("/aop")
public class HelloWorldController {
@LogAspect(operationName = "test", operationType = "get")
@GetMapping(value = "/test/annotation")
public String testSpringAop() {
System.out.println("Begin to test Spring-aop annotation...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("data", "Hello Spring-Aop annotation demo");
return jsonObject.toJSONString();
}
}