SpringBoot中应用SpringAOP实现记录日志功能

SpringBoot中应用SpringAOP实现记录日志功能

 

1.背景

需要把所有访问controller的请求方法、请求参数、返回值类型都保存到数据库表中,可以利用SpringAOP切面编程来实现。

2.实现步骤

  • 首先添加依赖,只要引入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 表示它是一个切面。

    image-20210610194124947

     

    针对这个切面类,来展开说明@Aspect切面类的编程。

    • Spring AOP 支持的切点指示器

      image-20210610195144668

    当我们查看上面展示的这些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包和它的子包下的方法

 

 

posted @ 2021-06-10 20:14  肖恩雷  阅读(576)  评论(0编辑  收藏  举报