1,引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2,配置日志切面
@Aspect
@Component
@Slf4j
@Order(1)
public class LogOperationAop {
//定义了一个切入点
@Pointcut("@annotation(com.XX.XX.annotion.OpLog)")
public void methodAspect() {
}
//定义了一个前置通知,这个通知对刚刚上面我们定义的切入点中的所有方法有效
@Before("methodAspect()")
public void doAudit(JoinPoint joinPoint) throws NoSuchMethodException {
//目标对象
Class<?> clazz = joinPoint.getTarget().getClass();
//方法签名
String method = joinPoint.getSignature().getName();
//方法参数
Object[] thisArgs = joinPoint.getArgs();
//参数类型
Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
//方法
Method thisMethod = clazz.getMethod(method, parameterTypes);
//自定义日志接口,检查指定的对象引用不为空
OpLog methodAnnotation = Objects.requireNonNull(AnnotationUtils.findAnnotation(thisMethod, OpLog.class));
AuditModuleEnum module = methodAnnotation.module();
String format = methodAnnotation.format();
//与模板参数的值比对
Object[] spEls = SpElUtil.spElEval(methodAnnotation.spElArgs(), thisArgs);
String summary = MessageFormatter.arrayFormat(format, spEls).getMessage();
String detail = null;
if (thisArgs.length > 0) {
detail = objectMapper.writeValueAsString(thisArgs[0]);
}
//将日志保存进数据库
service.recordLog(module, summary, detail);
}
@Before("methodAspect()")
public void doAudit(JoinPoint joinPoint) throws NoSuchMethodException {
System.out.println("Beforeadvice");
}
@After(value = "methodAspect()")
public void after() {
System.out.println("After advice");
}
@AfterReturning(value = "methodAspect()")
public void afterReturning() {
System.out.println("AfterReturning advice");
}
@AfterThrowing(value = "methodAspect()")
public void afterThrowing() {
System.out.println("AfterThrowing advice");
}
@Around("methodAspect()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("环绕通知开始");
System.out.println("目标对象 = " + proceedingJoinPoint.getTarget());
System.out.println("方法签名 = " + proceedingJoinPoint.getSignature());
System.out.println("方法参数 = " + proceedingJoinPoint.getArgs());
Object proceed=proceedingJoinPoint.proceed();//放行
System.out.println( "目标方法执行完毕");
return proceed;
}
}
注意:
1,前置和后置通知的方法参数都是 JoinPoint
2,环绕通知的方法参数是 ProceedingJoinPoint
3,匹配当前执行方法持有指定注解
//自定义aop生效注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME) //指定运行时生效
public @interface OpLog {
AuditModuleEnum module() default AuditModuleEnum.UNKNOWN;
String format() default "";
String [] spElArgs() default "";
}
业务层:
//使用切面注解
@ApiOperation(value = "新增")
@RequestMapping(path = "/add", method = RequestMethod.POST)
@OpLog(module = AuditModuleEnum.METADATA, format = "新增,{}", spElArgs = {"[0].toString()"})
public void add(@Valid @RequestBody param param) {
service.add(param);
}
4,注解
类注解:
@Aspect 将一个类定义为一个切面类
@Component 申明是个spring管理的bean
@order(i) 标记切面类的处理优先级,i值越小,优先级别越高.PS:可以注解类,也能注解到方法上
方法注解:
@Pointcut定义一个方法为切点里面的内容为一个表达式,下面详细介绍
@Before 在切点前执行方法,内容为指定的切点
@After 在切点后,return前执行,
@AfterReturning在切入点,return后执行,如果想对某些方法的返回参数进行处理,可以在这操作
@Around 环绕切点,在进入切点前,跟切点后执行
@AfterThrowing 在切点后抛出异常进行处理
@order(i) 标记切点的优先级,i越小,优先级越高
@Pointcut("execution( com.javacode2018.aop.demo9.test1.Service1.*(…))")*
表达式标签(10种)
execution:用于匹配方法执行的连接点
within:用于匹配指定类型内的方法执行
this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也* 类型匹配
target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配
args:用于匹配当前执行的方法传入的参数为指定类型的执行方法
@within:用于匹配所以持有指定注解类型内的方法
@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
@args:用于匹配当前执行的方法传入的参数持有指定注解的执行
@annotation:用于匹配当前执行方法持有指定注解的方法
5,更多优秀文章:
https://zhuanlan.zhihu.com/p/153317556
https://blog.csdn.net/qq_36826506/article/details/81436819