Springboot AOP 使用小结
引言
为了简化应用层开发人员的工作复杂性,我一般会把一些通用的,复杂的,跟具体业务没有特别关系的逻辑封装在框架层。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>
添加配置
可以在启动类添加,也可以在专用的配置文件上面添加。
@EnableAsync
编写两个用于测试的接口文件
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());
}
}
关于包路径匹配的语法简记
包路径最后一节是类名,*匹配任何类,同时*也可以在类名中匹配任何字符串;
..匹配任意多段路径;
括号内是参数匹配,..匹配任意参数表,具体细节所用较少,暂时没有过细研究。
测试结果
各种情况都测试过,符合预期效果。达成此种结果,基本上就可以灵活运用了。