• Springboot使用AOP+自定义注解方式实现日志记录


    Springboot使用AOP+自定义注解方式实现日志记录

    在Spring框架中,可以使用AOP配合自定义注解可以方便的实现用户操作的监控,来简化我们的代码。

    什么是自定义注解?

    了解自定义注解之前必须了解四个元注解,什么是元注解?元注解指作用于注解之上的元数据或者元信息,简单通俗的讲,元注解就是注解的注解。

    Java5.0定义的元注解(java.lang.annotation)
    • 1.@Target
    @Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、
    类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。
    在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
    
    作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
    
    取值(ElementType)有:
        1.CONSTRUCTOR:用于描述构造器
        2.FIELD:用于描述域
        3.LOCAL_VARIABLE:用于描述局部变量
        4.METHOD:用于描述方法
        5.PACKAGE:用于描述包
        6.PARAMETER:用于描述参数
        7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
    
    示例:注解Table 可以用于注解类、接口(包括注解类型) 或enum声明,而注解NoDBColumn仅可用于注解类的成员变量。
    @Target(ElementType.TYPE)
    public @interface Table {
        /**
         * 数据表名称注解,默认值为类名称
         * @return
         */
        public String tableName() default "className";
    }
    
    @Target(ElementType.FIELD)
    public @interface NoDBColumn {
    
    }
    
    • 2.@Retention
    @Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;
    编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class
    在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
    
    作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
    
    取值(RetentionPoicy)有:
           1.SOURCE:在源文件中有效(即源文件保留)
        2.CLASS:在class文件中有效(即class保留)
        3.RUNTIME:在运行时有效(即运行时保留)
    Retention meta-annotation类型有唯一的value作为成员,它的取值来自java.lang.annotation.RetentionPolicy的枚举类型值。
    
    示例:Column注解的的RetentionPolicy的属性值是RUTIME,这样注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Column {
        public String name() default "fieldName";
        public String setFuncName() default "setField";
        public String getFuncName() default "getField"; 
        public boolean defaultDBValue() default false;
    }
    
    
    • 3.@Documented
    @Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。
    Documented是一个标记注解,没有成员。
    
    示例:
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Column {
        public String name() default "fieldName";
        public String setFuncName() default "setField";
        public String getFuncName() default "getField"; 
        public boolean defaultDBValue() default false;
    }
    
    • 4.@Inherited
    @Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,
    则这个annotation将被用于该class的子类。
    
    注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
    
    当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。
    如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的
    annotation类型被发现,或者到达类继承结构的顶层。
    
    示例:
    @Inherited
    public @interface Greeting {
        public enum FontColor{ BULE,RED,GREEN};
        String name();
        FontColor fontColor() default FontColor.GREEN;
    }
    
    自定义注解
    使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。
    在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。
    方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。
    可以通过default来声明参数的默认值。
    
    定义注解格式:
      public @interface 注解名 {定义体}
    
    注解参数的可支持数据类型:
    
        1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
        2.String类型
        3.Class类型
        4.enum类型
        5.Annotation类型
        6.以上所有类型的数组
    
    示例:
    /**
     * 水果名称注解
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface FruitName {
        String value() default "";
    }
    

    使用AOP+自定义注解方式实现日志记录

    1、定义一个方法级别的@Log注解,用于标注需要监控的方法:
    package cn.pconline.common.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @Description 日志注解
     * @Author jie.zhao
     * @Date 2019/8/7 15:47
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Log {
        String value() default "";
    }
    
    
    2、定义切面和切点

    定义一个LogAspect类,使用@Aspect标注让其成为一个切面,切点为使用@Log注解标注的方法,使用@Around环绕通知。

    关于日志表保存根据自己的业务实现即可

    package cn.pconline.common.aspect;
    
    import cn.pconline.common.utils.HttpContextUtil;
    import cn.pconline.common.utils.IpUtil;
    import cn.pconline.config.authentication.JwtUtil;
    import cn.pconline.config.properties.AuthProperties;
    import cn.pconline.modules.sys.entity.SysLog;
    import cn.pconline.modules.sys.service.SysLogService;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.shiro.SecurityUtils;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    
    /**
    * @Description AOP 记录用户操作日志
    * @Author jie.zhao
    * @Date 2019/8/7 15:47
    */
    @Slf4j
    @Aspect
    @Component
    public class LogAspect {
    
        @Autowired
        private AuthProperties authProperties;
    
        @Autowired
        private SysLogService logService;
    
        @Pointcut("@annotation(cn.pconline.common.annotation.Log)")
        public void pointcut() {
            // do nothing
        }
    
        @Around("pointcut()")
        public Object around(ProceedingJoinPoint point) throws Throwable {
            Object result;
            long beginTime = System.currentTimeMillis();
            // 执行方法
            result = point.proceed();
            // 获取 request
            HttpServletRequest request = HttpContextUtil.getHttpServletRequest();
            // 设置 IP 地址
            String ip = IpUtil.getIpAddr(request);
            // 执行时长(毫秒)
            long time = System.currentTimeMillis() - beginTime;
            if (authProperties.isOpenAopLog()) {
                // 保存日志
                String token = (String) SecurityUtils.getSubject().getPrincipal();
                String username = "";
                if (StringUtils.isNotBlank(token)) {
                    username = JwtUtil.getUsername(token);
                }
    
                SysLog log = new SysLog();
                log.setUsername(username);
                log.setIp(ip);
                log.setTime(time);
                logService.saveLog(point, log);
            }
            return result;
        }
    }
    
    
    3、使用方法
    @RestController
    public class TestController {
    
        @Log("新增用户")
        @GetMapping("/one")
        public void addUser(String name) { }
    }
    
    

    参考文档:

    https://mrbird.cc/Spring-Boot-AOP log.html

    https://www.cnblogs.com/peida/archive/2013/04/24/3036689.html

    -------------已经触及底线 感谢您的阅读-------------
  • 相关阅读:
    android中设置快捷键方法setShortcut参数的说明
    LayoutInflater
    Java读取文本文件中文乱码问题 .转载
    eclipse+ADT 进行android应用签名详解
    实验课表
    程序员技术练级攻略
    超过4000长度的字符串如何添加到oracle数据库中
    Hive sql创建表以及插入分区表
    Hive Sql 时间格式化处理
    oracle如何查询分区表所占空间大小
  • 原文地址:https://www.cnblogs.com/cnsyear/p/12777975.html
Copyright © 2020-2023  润新知