Springboot AOP 使用小結
- 引言
- 搭建工程
- POM依賴
- 添加配置
- 編寫兩個用于測試的接口檔案
- 編寫幾個自定義注解
- 編寫兩個切面配置
-
- 1. 用于注解比對的Aspect
-
- 關于注解比對的文法
- 2. 用于包路徑比對的Aspect
-
- 關于包路徑比對的文法簡記
- 測試結果
引言
為了簡化應用層開發人員的工作複雜性,我一般會把一些通用的,複雜的,跟具體業務沒有特别關系的邏輯封裝在架構層。AOP用的就會比較多。有一段時間,總是會出現自己設計的AOP不能準确比對的問題。特意做了一點研究,為了增強記憶,友善日後應用,做個小結。
搭建工程
搭建一個普通的Springboot工程即可。為了看到效果,我們使用Logger列印日志,配置好日志列印級别。
logging:
level:
com.jiezhi.aspect.demo: debug
POM依賴
預設的工程沒有aop子產品,要單獨添加。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
添加配置
可以在啟動類添加,也可以在專用的配置檔案上面添加。
編寫兩個用于測試的接口檔案
com.jiezhi.aspect.demo.business.user.web.controller.UserController
package com.jiezhi.aspect.demo.business.user.web.controller;
import com.jiezhi.aspect.demo.annotations.OnMethod;
import com.jiezhi.aspect.demo.annotations.OnType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Chris Chan
* Create on 2021/8/29 17:18
* Use for: 測試接口
* Explain:
*/
@OnType
@RestController
@RequestMapping("api/user")
public class UserController {
@OnMethod
@GetMapping("test")
public String test() {
return "Call api/user/test success.";
}
@GetMapping("test1")
public String test1() {
return "Call api/user/test1 success.";
}
}
com.jiezhi.aspect.demo.business.worker.web.controller.WorkerController
package com.jiezhi.aspect.demo.business.worker.web.controller;
import com.jiezhi.aspect.demo.annotations.OnMethod;
import com.jiezhi.aspect.demo.annotations.OnType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Chris Chan
* Create on 2021/8/29 17:18
* Use for: 測試接口
* Explain:
*/
@RestController
@RequestMapping("api/worker")
public class WorkerController {
@OnMethod
@GetMapping("test")
public String test() {
return "Call api/worker/test success.";
}
@GetMapping("test1")
public String test1() {
return "Call api/worker/test1 success.";
}
}
編寫幾個自定義注解
OnType 用于類上的注解
OnMethod 用于方法上的注解
package com.jiezhi.aspect.demo.annotations;
import java.lang.annotation.*;
/**
* @author Chris Chan
* Create on 2021/8/29 17:07
* Use for:
* Explain:
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface OnMethod {
}
編寫兩個切面配置
1. 用于注解比對的Aspect
com.jiezhi.aspect.demo.config.AnnotationAspect
package com.jiezhi.aspect.demo.config;
import com.jiezhi.aspect.demo.annotations.OnMethod;
import com.jiezhi.aspect.demo.annotations.OnType;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @author Chris Chan
* Create on 2021/8/29 17:10
* Use for: 注解比對切面
* Explain:
*/
@Component
@Aspect
@Order(0)
public class AnnotationAspect {
private Logger logger = LoggerFactory.getLogger(AnnotationAspect.class);
@Pointcut("@within(com.jiezhi.aspect.demo.annotations.OnType)")
public void cutOnType() {
}
@Pointcut("@annotation(com.jiezhi.aspect.demo.annotations.OnMethod)")
public void cutOnMethod() {
}
/**
* 比對方法上的注解
*
* @param joinPoint
* @param onMethod
*/
@Before("@annotation(onMethod)")
public void before(JoinPoint joinPoint, OnMethod onMethod) {
logger.debug("接口調用OnMethod: {}", joinPoint.getSignature().getName());
}
/**
* 比對類上的注解
*
* @param joinPoint
* @param onType
*/
@Before("@within(onType)")
public void before(JoinPoint joinPoint, OnType onType) {
logger.debug("接口調用OnType: {}", joinPoint.getSignature().getName());
}
/**
* 比對類和方法上的注解
*
* @param joinPoint
*/
@Before("cutOnType() || cutOnMethod()")
public void before(JoinPoint joinPoint) {
Object target = joinPoint.getTarget();
Class<?> targetClass = target.getClass();
OnType onType = targetClass.getDeclaredAnnotation(OnType.class);
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
OnMethod onMethod = method.getDeclaredAnnotation(OnMethod.class);
logger.debug("接口調用OnType&OnMethod: {} ,OnType: {},OnMethod: {}", ((MethodSignature) joinPoint.getSignature()).getMethod().getName(), onType, onMethod);
}
}
關于注解比對的文法
@annotation(注解名)是一種比較簡便的使用模式,不用定義切點,還可以直接擷取到注解。不過隻能比對方法上面的注解;
@within功能同上,比對的是類上面的注解;
如果要同時比對方法和類上面的注解,不寫切點就不行了,需要參照第三種方式,先寫切點,然後進行邏輯處理。最終需要的注解,要在内部用邏輯自己擷取。
2. 用于包路徑比對的Aspect
com.jiezhi.aspect.demo.config.CommonAspect
package com.jiezhi.aspect.demo.config;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* @author Chris Chan
* Create on 2021/8/29 17:10
* Use for: 包名比對切面
* Explain:
*/
@Component
@Aspect
@Order(1)
public class CommonAspect {
private Logger logger = LoggerFactory.getLogger(CommonAspect.class);
/**
* 第一個參數是權限修飾符比對
* 第二個參數是傳回值類型比對
* 第三部分(最後一段之前)是包路徑比對 *表示任意包
* 第四部分(最後一段)是類比對 *表示所有類
* 第五部分(括号内)是參數清單比對 ..表示所有參數
* ..表示比對多重
*/
@Pointcut("execution(public * com.jiezhi.aspect.demo.business.*.web.controller.*Controller.*(..))")
public void cutController() {
}
@Pointcut("execution(public * com.jiezhi.aspect.demo..*Control*.*(..))")
public void cutController1() {
}
/**
* 比對Controller
*
* @param joinPoint
*/
@Before("cutController1()")
public void before(JoinPoint joinPoint) {
logger.debug("接口調用 路徑比對1: {}", joinPoint.getSignature().getName());
}
}
關于包路徑比對的文法簡記
包路徑最後一節是類名,*比對任何類,同時*也可以在類名中比對任何字元串;
..比對任意多段路徑;
括号内是參數比對,..比對任意參數表,具體細節所用較少,暫時沒有過細研究。
測試結果
各種情況都測試過,符合預期效果。達成此種結果,基本上就可以靈活運用了。