SpringBoot中应用SpringAOP实现记录日志功能
1.背景
需要把所有访问controller的请求方法、请求参数、返回值类型都保存到数据库表中,可以利用SpringAOP切面编程来实现。
-
首先添加依赖,只要引入SpringAOP相关的jar包依赖,我们就可以开始相关的Aspet的编程了
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
-
创建一个pojo实体类,用来保存日志记录相关信息
@Data @AllArgsConstructor @NoArgsConstructor public class OperationLog { private Integer id; //返回值 private String returnValue; //返回值类型 private String returnClass; //操作人 private String operateUser; //操作时间 private String operateTime; //参数值 , 键值对形式 private String paramAndValue; //操作的类 private String operateClass; //操作的方法 private String operateMethod; //操作耗时 private Long costTime; }
-
编写自定义注解@OperateLog,在controller类中使用该注解的所有方法都会被记录到数据库中
@Target(ElementType.METHOD) @Documented @Retention(RetentionPolicy.RUNTIME) public @interface OperateLog { }
-
创建一个OperateAdvice的处理类
@Component @Aspect @Slf4j public class OperateAdvice { @Around("execution(* com.sean.springaop.controller.*.*(..)) && @annotation(operateLog)" ) public Object insertLogAround(ProceedingJoinPoint pjp , OperateLog operateLog) throws Throwable { log.info("*********************************** 记录日志 [start] ****************************** "); OperationLog op = new OperationLog(); //todo:从session中获取登陆用户信息 op.setOperateUser(RandomUtil.randomNumbers(8)); //获取输入参数 String args = pjp.getArgs().toString(); //获取类名 String className = pjp.getTarget().getClass().getName(); //获取方法名 String methodName = pjp.getSignature().getName(); op.setOperateClass(className); op.setOperateMethod(methodName); op.setParamAndValue(args); Class returnType = pjp.getSignature().getDeclaringType(); String declaredTypeName = pjp.getSignature().getDeclaringTypeName(); long startTime = System.currentTimeMillis(); Object object = pjp.proceed();//放行 long endTime = System.currentTimeMillis(); op.setCostTime(endTime - startTime); if(object != null) { //获取返回值类型 op.setReturnClass(object.getClass().getName()); //获取返回值 op.setReturnValue(object.toString()); }else { op.setReturnValue("void"); op.setReturnClass("java.lang.object"); } //todo:将该对象insert到数据库中,这里使用log打印该对象数据 log.info(op.toString()); log.info(" *********************************** 记录日志 [end] ****************************** "); return null; } }
这个类,我们使用了注解
@Component
表明它将作为一个Spring Bean 被装配,使用注解@Aspect
表示它是一个切面。针对这个切面类,来展开说明@Aspect切面类的编程。
-
Spring AOP 支持的切点指示器
当我们查看上面展示的这些spring支持的指示器时,注意只有execution指示器是唯一的执行匹配,而其他的指示器都是用于限制匹配的。这说明execution指示器是我们在编写切点定义时最主要使用的指示器,在此基础上,我们使用其他指示器来限制所匹配的切点。
-
5种通知类型
-
注解 | 通知 |
---|---|
@Before | 通知方法会在目标方法调用之前执行 |
@After | 通知方法会在目标方法返回或异常后调用 |
@AfterRetunibg | 通知方法会在目标方法返回之后执行 |
@AfterThrowing | 通知方法会在目标方法抛出异常后执行 |
@Around | 通知方法会将目标方法封装起来 |
-
execution表达式
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?) execution(方法修饰符(可选) 返回类型 类路径 方法名 参数 异常模式(可选))
除了返回类型,方法名还有参数之外,其他都是可选的
ret-type-pattern:可以为*表示任何返回值,全路径的类名等.
name-pattern:指定方法名,代表所以,set,代表以set开头的所有方法. parameters pattern:指定方法参数(声明的类型), ()匹配没有参数; (..)代表任意多个参数; ()代表一个参数,但可以是任意型; (,String)代表第一个参数为任何值,第二个为String类型。
下面给几个例子:
1)execution(public * *(..))——表示匹配所有public方法 2)execution(* set*(..))——表示所有以“set”开头的方法 3)execution(* com.xyz.service.AccountService.*(..))——表示匹配所有AccountService接口的方法 4)execution(* com.xyz.service.*.*(..))——表示匹配service包下所有的方法 5)execution(* com.xyz.service..*.*(..))——表示匹配service包和它的子包下的方法