SpringBoot通过自定义注解和AOP实现日志管理

导入相关依赖和配置文件

首先pom.xml添加如下依赖,然后日志的我这里用的是lombox自带的日志,在对应的类上面加入@Slf4j注解就可用使用了。

<!--aop依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--lombok依赖-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

然后配置我们SpringBoot的yml文件

spring:
  datasource:
    url: jdbc:mysql://localhost:3307/crm?serverTimezone=Hongkong&characterEncoding=utf-8&allowPublicKeyRetrieval=true&useSSL=false
    password: 123456
    username: root
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    filter: stat
  redis:
    # redis数据库索引(默认为0),我们使用索引为3的数据库,避免和其他数据库冲突
    database: 3
    host: localhost    # redis服务器地址(默认为localhost)
    port: 6379    # redis端口(默认为6379)
    password:    # redis访问密码(默认为空)
    jedis:    # 集成redis一些命令操作
      pool:    # 连接池
        max-active: 8    #最大连接数
        max-wait: -1    #最大阻塞等待时间(负数表示没限制)
        max-idle: 8    #最大空闲
        min-idle: 0    #最小空闲
        timeout: 20000  #连接超时时间
mybatis:
  type-aliases-package: com.study.model.pojo
  mapper-locations: classpath:mapper/*/*.xml
pagehelper:
  helperDialect: mysql
  reasonable: true
  supportMethodsArguments: true
  params: count=countSql
logging:
  level:
    com.study.model.mapper : debug #定义日志的打印级别
  file: logs/crmsystem.log #存储日志文件的位置

定义自定义注解

首先我们先来了解一下自定义注解所需要的俩个注解(他们本身也是注解)

@Target

@Target:这个注解就是表明该注解类能够作用的范围,也就是能够注解在哪,比如 类、方法、参数等。下面是他的一些参数:
  @Target(ElementType.TYPE):接口、类、枚举、注解
  @Target(ElementType.FIELD):字段、枚举的常量
  @Target(ElementType.METHOD):方法
  @Target(ElementType.PARAMETER):方法参数
  @Target(ElementType.CONSTRUCTOR):构造函数
  @Target(ElementType.LOCAL_VARIABLE):局部变量
  @Target(ElementType.ANNOTATION_TYPE):注解
  @Target(ElementType.PACKAGE):包
里面的参数是可以多选的,使用方法比如@Target({ElementType.METHOD,ElementType.TYPE})

@Retention

@Retention:这个注解是保留说明,也就是表明这个注解所注解的类能在哪里保留,他有三个参数
  RetentionPolicy.SOURCE:这种类型的Annotations只在源代码级别保留,编译时就会被忽略
  RetentionPolicy.CLASS:这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略
  RetentionPolicy.RUNTIME: 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。

自定义注解

package com.study.aop.annotation;

import java.lang.annotation.*;

@Target({ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

    /**
     * 描述
     * @return
     */
    String description() default "";

}

定义切面类

@Aspect

  @Aspect注解告诉Spring这是个切面类

@Pointcut

切点,用于定义哪个方法会被拦截,
  1、execution(): 表达式主体。
  2、第一个*号:表示返回类型,号表示所有的类型。
  3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包 ,com.sample.service.impl包、子孙包下所有类的方法。
  4、第二个
号:表示类名,号表示所有的类。
  5、
(…):最后这个星号表示方法名,*号表示所有的方法, 后面括弧里面表示方法的参数,两个句点表示任何参数。

@Before

  前置通知,表示在目标方法执行之前执行

@After

  后置通知,在目标方执行完成后执行,如果目标方法异常,则后置通知不再执行,则跳转到@AfterThrowing所在的方法处

@Around

  环绕通知,相当于前置通知和后置通知的结合体,但是环绕通知必须有返回参数,不然会没有数据,然后环绕通知必须执行proceed()方法,也就是调用的那个方法,不然会报错

@AfterReturning

  目标方法返回后执行,也就是环绕通知或者后置通知之后,如果发生异常不执行

@AfterThrowing

  在方法抛出异常后调用通知

自定义切面类

package com.study.aop.ascept;

import com.study.aop.annotation.MyAnnotation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.text.DateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;

@Component
@Aspect
@Slf4j
public class MyAscept {

    /**
     * 切入点:有MyAnnotation注解的为切入点
     *     1、execution(): 表达式主体。
     *     2、第一个*号:表示返回类型,*号表示所有的类型。
     *     3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包
     *             ,com.sample.service.impl包、子孙包下所有类的方法。
     *     4、第二个*号:表示类名,*号表示所有的类。
     *     5、*(..):最后这个星号表示方法名,*号表示所有的方法,
     *              后面括弧里面表示方法的参数,两个句点表示任何参数。
     *              
     * 在这里是结合自定义注解一起使用,在指定包里面的类有MyAnnotation注解的方法都会打印日志
     */
    @Pointcut("execution(* com.study.controller..*.*(..)) && @annotation(com.study.aop.annotation.MyAnnotation)")
    public void Pointcut(){

    }


    /**
     * 前置通知:在目标方法执行之前执行
     * @param jp
     */
    /*@Before("Pointcut()")
    public void Before(JoinPoint jp){
        StringBuilder headers = new StringBuilder();
        HttpServletRequest request = this.getThisRequst();
        String begintime = DateFormat.getDateTimeInstance().format(new Date());//开始时间
        String ipaddress = request.getRemoteAddr();//IP地址
        String request_url = request.getRequestURI();//请求URL
        String request_model = request.getMethod();//请求方式
        String method_name = jp.getSignature().getName();//方法名
        String project_url = jp.getSignature().getDeclaringTypeName();//包+类路径
        String class_method = project_url + "." + method_name;//完整路径
        MyAnnotation annotationvalue = ((MethodSignature)jp.getSignature()).getMethod().getAnnotation(MyAnnotation.class);//自定义注解的值
        String param_names =  Arrays.toString(((MethodSignature) jp.getSignature()).getParameterNames());//参数名称
        String param_values = Arrays.toString(jp.getArgs());//参数值
        //获取请求头
        Enumeration<String> enumeration = request.getHeaderNames();
        while (enumeration.hasMoreElements()) {
            String name = enumeration.nextElement();
            String value = request.getHeader(name);
            headers.append(name + ":" + value).append(",");
        }
        log.info("Before ----------------------------------------------------------->");
        log.info("开始时间:{}",begintime);
        log.info("IP地址:{}",ipaddress);
        log.info("请求URL:{}",request_url);
        log.info("请求头信息:{}",headers);
        log.info("请求方式 {}", request_model);
        log.info("自定义注解的值:{}",annotationvalue.description());
        log.info("参数名称:{}",param_names);
        log.info("参数值:{}",param_values);
        log.info("完整路径:{}",class_method);
    }*/


    /**
     * 后置通知:在目标方执行完成后执行,如果目标方法异常,则后置通知不再执行
     * @param jp
     */
    /*@After("Pointcut()")
    public void After(JoinPoint jp){
        String endtime = DateFormat.getDateTimeInstance().format(new Date());//结束时间
        log.info("结束时间:{}",endtime);
        log.info("After ----------------------------------------------------------->");
    }*/


    /**
     * 环绕通知:相当于前置通知和后置通知的结合体,但是环绕通知必须有返回参数
     */
    @Around("Pointcut()")
    public Object Around(ProceedingJoinPoint pjp) throws Throwable {
        StringBuilder headers = new StringBuilder();
        HttpServletRequest request = this.getThisRequst();
        String begintime = DateFormat.getDateTimeInstance().format(new Date());//开始时间
        String ipaddress = request.getRemoteAddr();//IP地址
        String request_url = request.getRequestURI();//请求URL
        String request_model = request.getMethod();//请求方式
        String method_name = pjp.getSignature().getName();//方法名
        String project_url = pjp.getSignature().getDeclaringTypeName();//包+类路径
        String class_method = project_url + "." + method_name;//完整路径
        MyAnnotation annotationvalue = ((MethodSignature)pjp.getSignature()).getMethod().getAnnotation(MyAnnotation.class);//自定义注解的值
        String param_names =  Arrays.toString(((MethodSignature) pjp.getSignature()).getParameterNames());//参数名称
        String param_values = Arrays.toString(pjp.getArgs());//参数值
        //获取请求头
        Enumeration<String> enumeration = request.getHeaderNames();
        while (enumeration.hasMoreElements()) {
            String name = enumeration.nextElement();
            String value = request.getHeader(name);
            headers.append(name + ":" + value).append(",");
        }
        log.info("Around Before ----------------------------------------------------------->");
        log.info("开始时间:{}",begintime);
        log.info("IP地址:{}",ipaddress);
        log.info("请求URL:{}",request_url);
        log.info("请求头信息:{}",headers);
        log.info("请求方式 {}", request_model);
        log.info("自定义注解的值:{}",annotationvalue);
        log.info("参数名称:{}",param_names);
        log.info("参数值:{}",param_values);
        log.info("完整路径:{}",class_method);
        //执行要执行的方法,o就是执行方法的返回值
        Object result = pjp.proceed();
        String endtime = DateFormat.getDateTimeInstance().format(new Date());//结束时间
        log.info("{}方法的返回值: {}", method_name,result);
        log.info("结束时间:{}",endtime);
        log.info("Around After ----------------------------------------------------------->");
        return result;
    }


    /**
     * 目标方法返回后执行,如果发生异常不执行
     */
    @AfterReturning(value = "Pointcut()")
    public void AfterReturning(JoinPoint jp){
        String methodname = jp.getSignature().getName();//方法名
        String projecturl = jp.getSignature().getDeclaringTypeName();//包+类路径
        String class_method = projecturl + "." + methodname;//完整路径
        log.info("{} is destroy",class_method);
    }


    /**
     * 目标发生异常时执行
     * @param jp
     * @param e
     */
    @AfterThrowing(value = "Pointcut()",throwing = "e")
    public void AfterThrowing(JoinPoint jp, Exception e){
        String methodname = jp.getSignature().getName();//方法名
        String projecturl = jp.getSignature().getDeclaringTypeName();//包+类路径
        String class_method = projecturl + "." + methodname;//完整路径
        log.error("{}位置发生异常,异常的信息为{}",class_method,e.getMessage());
    }


    /**
     * 获取request对象
     * @return
     */
    public HttpServletRequest getThisRequst(){
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        return request;
    }

}

使用

在对应的方法上加上自己的自定义注解就可以使用了
在这里插入图片描述
日志打印结果
在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值