【直呼内行】理性分析注解优缺点,通过技术点和案例完美应用注解

🍺满怀忧思,不如先干再说!

注解是一个很重要的技术,通过注解可以将一些细节代码写到第三方类中,在业务代码内不会存在业务之外的"冗余代码",如常用的Spring,SpringBoot,MyBatis,JDK也自带了很多主机供我们使用。但是同时它也存在很多争议

  • 大量的注解不好调试代码
  • 往往需要通过反射配合才能实现功能,而反射执行效率又比较低
  • 有的小伙伴还认为注解往往只是关注具备什么功能,而不关注怎么实现的,知其然不知所以然不利于技术提升
    总之,各有各的道理,但是请记住,一个技术不会适用于任何场景,注解的正确使用方法往往是站在一个架构的高度来审视,用途基本在于:架构师或者高级开发对系统中常用的功能进行封装,之后在开发过程中如果需要使用该功能则直接使用注解修饰即可。注解往往适用于通用而非专用的功能之上

注解概念

注解【Annotation】是Java5引入的新机制,也被称为元数据,在代码中添加信息提供了一种形式化的方法,可以理解为给代码打一个标记,允许我们可以在稍后的某个时刻非常方便地使用这些数据。

注解可以使用在类、方法和属性【成员变量】上,注解也是对来自像C#之类的其他语言对Java造成的语言特性压力所做出的一种回应

注解特点

Java中的注解可以使用在类、构造器、方法、成员变量、参数等位置上。和 Javadoc 不同,Java 注解可以通过反射获取注解内容。在编译器生成类文件时,注解可以存在于字节码中【注意是可以,不是一定,是可以控制的】。Java 虚拟机可以保留注解内容,在运行时可以通过反射机制获取到注解内容 。 当然它也支持自定义 Java 注解

注解重要的是现有的Java注解和第三方注解的作用和使用方法,以及自定义注解

注解的好处:

  • 可以读懂别人的代码,特别是框架的代码中使用了很多注解
  • 让编程更加简洁,代码更加清晰,比如使用Spring、MyBatis等框架都要使用大量注解
  • 让别人直呼内行,实现该功能只需要调用这个注解就行啦,真的很方便

注解分类

在这里插入图片描述

Java定义了一套注解,元注解在 java.lang.annotation 包,标准注解在 java.lang中:

  • @Override:使用在方法上,判断是否是子类重写父类的方法
  • @Deprecated:使用在方法或类上,标记为过时或弃用方法【类】,被该注解标识的方法【类】一般都不建议再使用,老的API中有许多过时方法
  • @SuppressWarnings:使用在方法上,禁止警告注解,代码不规范或者某变量未使用,代码冗余,过长等编辑器都会警告,有些洁癖公司不允许出现警告,这时可以利用该注解清除

作用在其他注解的注解(或者说 元注解)是:自定义注解需要到这四个

  • @Retention:标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
  • @Documented:标记这些注解是否包含在用户文档中。
  • @Target:标记这个注解应该是哪种 Java 成员。
  • @Inherited:标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

从 Java 7 开始,额外添加了 3 个注解:

  • @SafeVarargs:Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
  • @FunctionalInterface:Java 8 开始支持,标识一个匿名函数或函数式接口。
  • @Repeatable:Java 8 开始支持,标识某注解可以在同一个声明上使用多次。自定义注解需要使用到,也就是重复注解

Java内置注解

简单使用一下常见的三个标准注解:@Override、@Deprecated、@SuppressWarnings

声明父类:

public class Person {
    private String name;
    private Integer age;
	// 说话方法
    public void say() {
    }
}

子类:

  • 继承父类,通过 @Override 标记
  • 部分技能已经生疏,可以通过 @Deprecated 标记为过时,不建议使用,可能【翻车】
  • 声明 str变量但是没有使用,可以通过 @SuppressWarnings 注解,该注解有值,需要填入警告类型,unused就是未使用的意思,可以填写多个值
public class Student extends Person{

    // 重写注解
    @Override
    public void say() {
        System.out.println("我是学生");
    }
    // 过时注解,大学毕业之后篮球技能过时了不会了
    @Deprecated
    public String skill() {
        return "篮球";
    }

    // 排除警告注解,比如声明变量没有使用,编辑器就会报黄【警告提示】,添加该注解写上 unused,就是禁止【未使用】警告
    @SuppressWarnings({"unused","rawtypes"})
    public void warning() {
        String str;
    }
}

SuppressWarnings所有值:

作用
all抑制所有警告
boxing抑制装箱、拆箱操作时候的警告
cast抑制映射相关的警告
dep-ann抑制启用注释的警告
deprecation抑制过期方法警告
fallthrough抑制确在switch中缺失breaks的警告
finally抑制finally模块没有返回的警告
hiding抑制相对于隐藏变量的局部变量的警告
incomplete-switch忽略不完整的 switch 语句
nls忽略非nls格式的字符
null忽略对null的操作
rawtypes使用generics时忽略没有指定相应的类型
restriction抑制禁止使用劝阻或禁止引用的警告
serial忽略在serializable类中没有声明serialVersionUID变量
static-access抑制不正确的静态访问方式警告
synthetic-access抑制子类没有按最优方法访问内部类的警告
unchecked抑制没有进行类型检查操作的警告
unqualified-field-access抑制没有权限访问的域的警告
unused抑制没被使用过的代码的警告

小贴士:合理使用 @SuppressWarnings 注解可以方便调试和运维

自定义注解

自定义注解并可以使用自定义注解是迈向更高一层的重要表现,我们要明白为什么要自定义注解,什么时候自定义什么样的注解比较合适才是重中之重。

自定义注解的使用场景

自定义注解步骤

  • 通过 @interface 关键字创建一个注解类,注解类中可以包含方法也可以不包含方法,这个方法可以认为是注解的参数
  • 使用元注解对自定义注解的功能和范围进行限制

元注解:

元注解,就是定义注解的注解,也就是说这些元注解是的作用就是专门用来约束其它注解的注解。

元注解主要有五个:@Target,@Retention,@Documented,@Inherited,@Repeatable,其中 @Repeatable 是Java8新增的可重复注解。

@Target:用于指定注解的使用范围,通过 ElemenetType 枚举值决定

  • ElementType.TYPE:类、接口、注解、枚举
  • ElementType.FIELD:字段、枚举常量
  • ElementType.METHOD:方法
  • ElementType.PARAMETER:形式参数
  • ElementType.CONSTRUCTOR:构造方法
  • ElementType.LOCAL_VARIABLE:局部变量
  • ElementType.ANNOTATION_TYPE:注解
  • ElementType.PACKAGE:包
  • ElementType.TYPE_PARAMETER:类型参数
  • ElementType.TYPE_USE:类型使用
    在这里插入图片描述

@Retention:用于指定注解的保留策略,通过 RetentionPolicy 枚举值决定

  • RetentionPolicy.SOURCE:注解只保留在源码中,在编译时会被编译器丢弃
  • RetentionPolicy.CLASS:(默认的保留策略) 注解会被保留在Class文件中,但不会被加载到虚拟机中,运行时无法获得
  • RetentionPolicy.RUNTIME:注解会被保留在Class文件中,且会被加载到虚拟机中,可以在运行时通过反射获得
    在这里插入图片描述

@Documented:用于将注解包含在javadoc中,默认情况下,javadoc是不包括注解的,但如果使用了@Documented注解,则相关注解类型信息会被包含在生成的文档中,该注解没有参数

在这里插入图片描述

@Inherited: 允许子类继承父类中的注解
在这里插入图片描述

@Repeatable:Java8中引入的元注解,用于声明标记的注解为可重复类型注解,可以在同一个地方多次使用
在这里插入图片描述

发现所有的元注解上的@Target注解值为 ElementType.ANNOTATION_TYPE,意为该注解的作用范围在注解上

案例:

需求:自定义一个名为 MyClassAnnonation 类级别的无参注解,定义一个使用在属性上名为 Name 的带属性注解

代码实现:

MyClassAnnonation:

package com.stt.annontation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// ElementType.TYPE:这个注解可以使用在 类、接口、注解、枚举上
@Target(ElementType.TYPE)
// RetentionPolicy.RUNTIME:注解被保留在Class文件中,也会被加载到JVM中
@Retention(RetentionPolicy.RUNTIME)
public @interface MyClassAnnonation {

}

Name:

package com.stt.annontation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

// 可以使用在字段上
@Target(ElementType.FIELD)
public @interface Name {
    // value带小括号,是一个方法,有些文章写value是参数,我感觉是不准确的
    String value();
    // 因为注解中可以写成员变量【属性】
    String job = "程序猿";
}

Student类测试自定义注解:

package com.stt.annontation;

// 使用类注解
@MyClassAnnonation
public class Student {
	// 使用Name注解
    @Name(value = "添甄")
    private String name;

    public String getName() {
        return name;
    }
}

错误应用1:比如@Name注解我们限制只能使用在字段上,应用在方法上就会报错

在这里插入图片描述

错误应用2:比如@Name注解需要传参数,未传参数也会报错
在这里插入图片描述

深入一下:

这里介绍注解的三个小知识点:

  • 巧用value方法
  • 默认值
  • 基本数据类型不能使用包装类

value属性:当注解中有value方法时,使用注解赋值时,value方法可以不写,直接写具体的值即可

在这里插入图片描述

不禁习惯性的思考,是不是只有一个方法时就可以省略属性名直接填值,所以我们将value属性名改成name试一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G6FpCWJU-1680832587316)(./img/11-找不到value.png)]

发现提示找不到value方法,说明默认会根据value进行匹配,这里说巧用value方法

默认值:我们可以通过default给注解参数默认值,如给value默认值为添甄
在这里插入图片描述

建议在自定义注解时,合理分配默认值,spring、MyBatis等框架的注解每个属性都设置了默认值

在这里插入图片描述
基本数据类型不能使用包装类:比如声明一个年龄,Integer无效,只能使用int
在这里插入图片描述

以上就是自定义注解时的三个小知识点,大家知道,使用时避免踩坑。说到这不禁觉得我是真滴细

注解应用

案例1:实现Junit的单元测试

需求:通过注解实现类似于Junit框架的@Test注解类似的功能,在程序运行时有@MyTest的注解的方法都执行

分析:

  • 创建名为MyTest的注解,限制使用在方法上
  • 因为程序运行时要检测哪些方法上有@MyTest注解,所以该注解的保留机制应该是运行时期仍然保存
  • 运行时通过反射扫描哪些方法上包含@MyTest注解,获取到之后逐一执行

代码实现:

注解:

package com.stt.annontation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
    
}

方法标记:

package com.stt.annontation;

import java.lang.reflect.Method;

public class MyTestMain {

    @MyTest
    public void test1() {
        System.out.println("==========test1==========");
    }

    @MyTest
    public void test2() {
        System.out.println("==========test2==========");
    }

    @MyTest
    public void test3() {
        System.out.println("==========test3==========");
    }

    public void test4() {
        System.out.println("==========test4==========");
    }

    public static void main(String[] args) {
        // 1、创建测试类对象,通过反射运行方法时需要使用
        MyTestMain testMain = new MyTestMain();
        // 2、获取类对象
        Class<MyTestMain> clazz = MyTestMain.class;
        // 3、获取所有方法
        Method[] methods = clazz.getMethods();
        // 4、遍历所有的方法
        for (Method method : methods) {
            // 5、判断哪些方法上有 @MyTest 注解
            if(method.isAnnotationPresent(MyTest.class)) {
                // 6、触发执行,指明通过(1)创建的对象调用
                try {
                    method.invoke(testMain);
                } catch (Exception e) {
                    System.out.println("运行出错!");
                }
            }
        }
    }
}

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Ozr8Fi0-1680832587320)(./img/15-MyTest运行结果.png)]

test4方法没有添加注解,所以就没有运行,发现运行顺序1、3、2,其实这个运行顺序是随机的,如果想要控制执行顺序,可以在注解中添加一个order值来实现,如下:

修改注解:添加order方法,值为int类型,默认值为0

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
    int order() default 0;
}

修改测试:在使用注解时添加顺序,在使用时获取到该注解,通过Stream根据order值排序之后再注意调用执行,就可以控制顺序

public class MyTestMain {

    @MyTest(order = 1)
    public void test1() {
        System.out.println("==========test1==========");
    }

    @MyTest(order = 2)
    public void test2() {
        System.out.println("==========test2==========");
    }

    @MyTest(order = 3)
    public void test3() {
        System.out.println("==========test3==========");
    }

    public void test4() {
        System.out.println("==========test4==========");
    }

    public static void main(String[] args) {
        // 1、创建测试类对象,通过反射运行方法时需要使用
        MyTestMain testMain = new MyTestMain();
        // 2、获取类对象
        Class<MyTestMain> clazz = MyTestMain.class;
        // 3、获取所有方法
        Method[] methods = clazz.getMethods();
        List<Method> invokeMethods = new ArrayList<>();
        // 4、遍历所有的方法
        for (Method method : methods) {
            // 5、判断哪些方法上有 @MyTest 注解
            if(method.isAnnotationPresent(MyTest.class)) {
                // 将方法添加到集合中
                invokeMethods.add(method);
            }
        }
        // 6、根据order值排序
        List<Method> methodList = invokeMethods.stream()
                .sorted(((o1, o2) -> o1.getAnnotation(MyTest.class).order() - o2.getAnnotation(MyTest.class).order()))
                .collect(Collectors.toList());
        // 7、根据排序后的方法执行,结果永远是1,2,3
        methodList.forEach(method -> {
            try {
                method.invoke(testMain);
            } catch (Exception e) {
                System.out.println("运行错误!");
            }
        });
    }
}

运行结果:
在这里插入图片描述

这样是不就非常哇塞!

Spring中的类似注解:

@Order:就可以控制配置类的加载顺序
在这里插入图片描述

@DependsOn:指定当前bean所依赖的beans。任何被指定依赖的bean都由Spring保证在当前bean之前创建
在这里插入图片描述

案例2:实现日志记录

需求:在SpringBoot程序中,通过自定义注解 + aop实现日志记录功能,在请求接口时,自动将操作日志记录到数据库

分析:

  • 自定义Log注解,使用在类和方法上,并在运行时可以获取到
  • 通过aop配置获取哪些方法上定义了Log注解,并获取到相关信息
  • 将信息整合到日志实体类中,存储到MySQL
  • 为方便操作数据库和实体类,引入mybatis-plus和lombok
  • 贴出核心代码,springboot配置文件和service、mapper就不贴出了

依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.7.5</version>
    </dependency>
    <!-- SpringBoot 拦截器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
        <version>2.7.5</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.24</version>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.2</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.31</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba.fastjson2</groupId>
        <artifactId>fastjson2</artifactId>
        <version>2.0.19</version>
    </dependency>
</dependencies>

数据库:

CREATE TABLE `sys_log` (
  `oper_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志主键',
  `title` varchar(50) DEFAULT '' COMMENT '模块标题',
  `business_type` int(11) DEFAULT '0' COMMENT '业务类型(0其它 1新增 2修改 3删除)',
  `method` varchar(100) DEFAULT '' COMMENT '方法名称',
  `request_method` varchar(10) DEFAULT '' COMMENT '请求方式',
  `operator_type` int(11) DEFAULT '0' COMMENT '操作类别( 0、用户端   1、平台管理端)',
  `oper_name` varchar(50) DEFAULT '' COMMENT '操作人员',
  `oper_url` varchar(255) DEFAULT '' COMMENT '请求URL',
  `oper_ip` varchar(128) DEFAULT '' COMMENT '主机地址',
  `oper_location` varchar(255) DEFAULT '' COMMENT '操作地点',
  `oper_param` varchar(2000) DEFAULT '' COMMENT '请求参数',
  `json_result` varchar(2000) DEFAULT '' COMMENT '返回参数',
  `status` int(11) DEFAULT '0' COMMENT '操作状态(1正常 0异常)',
  `error_msg` varchar(2000) DEFAULT '' COMMENT '错误消息',
  `oper_time` datetime DEFAULT NULL COMMENT '操作时间',
  PRIMARY KEY (`oper_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='操作日志记录';

实体类:

package com.stt.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.time.LocalDateTime;

@Data
public class SysLog {

    @TableId(value = "oper_id", type = IdType.AUTO)
    private Long operId;

    private String title;

    private Integer businessType;

    private String method;

    private String requestMethod;

    private String operName;

    private String operUrl;

    private String operIp;

    private String operLocation;

    private String operParam;

    private String jsonResult;

    private Integer status;

    private String errorMsg;

    private LocalDateTime operTime;
}

自定义注解:

package com.stt.annontation;

import java.lang.annotation.*;

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

    /**
     * 日志标题
     */
    public String title() default "";

    /**
     * 操作类型
     */
    public BusinessTypeEnum businessType() default BusinessTypeEnum.OTHER;
}

BusinessTypeEnum枚举:记录什么类型的操作

package com.stt.annontation;

public enum BusinessTypeEnum {
    /**
     * 其它
     */
    OTHER,

    /**
     * 新增
     */
    INSERT,

    /**
     * 修改
     */
    UPDATE,

    /**
     * 删除
     */
    DELETE,

    /**
     * 授权
     */
    GRANT,
}

AOP配置:

package com.stt.annontation;

import com.alibaba.fastjson2.JSON;
import com.stt.entity.SysLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Map;

@Component
@Aspect
public class LogConfig {

    private static final Logger log = LoggerFactory.getLogger(LogConfig.class);
    // 引入日志Service,用于存储数据进数据库
    @Autowired
    private ISysLogService sysLogService;

    // 配置织入点-xxx代表自定义注解的存放位置,如:com.stt.annontation.Log
    @Pointcut("@annotation(com.stt.annontation.Log)")
    public void logPointCut() {}

    /**
     * 处理完请求后执行此处代码
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult)
    {
        handleLog(joinPoint, controllerLog, null, jsonResult);
    }

    /**
     * 如果处理请求时出现异常,在抛出异常后执行此处代码
     *
     * @param joinPoint 切点
     * @param e 异常
     */
    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e)
    {
        handleLog(joinPoint, controllerLog, e, null);
    }

    /**
     * 日志处理
     */
    protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult)
    {
        try {
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            // 获取当前的用户
            String userName = "添甄";
            // *========数据库日志=========*//
            SysLog sysLog = new SysLog();
            sysLog.setStatus(1);
            // 请求的地址
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            assert requestAttributes != null;
            HttpServletRequest request = requestAttributes.getRequest();
            String ip = getIpAddr(request);
            sysLog.setOperIp(ip);
            sysLog.setOperUrl(request.getRequestURI());
            sysLog.setOperName(userName);

            if (e != null) {
                sysLog.setStatus(0);
                sysLog.setErrorMsg(e.getMessage().substring(0,2000));
            }
            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            sysLog.setMethod(className + "." + methodName + "()");
            // 设置请求方式
            sysLog.setRequestMethod(request.getMethod());
            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, controllerLog, sysLog, jsonResult, request);
            // 保存数据库
            sysLog.setOperTime(LocalDateTime.now());
            // 将处理好的日至对象存储进数据库
            sysLogService.save(sysLog);
        } catch (Exception exp) {
            // 记录本地异常日志
            log.error("==前置通知异常==");
            log.error("异常信息:{}", exp.getMessage());
            exp.printStackTrace();
        }
    }
	// 获取操作ip地址
    public static String getIpAddr(HttpServletRequest request) {
        if (request == null) {
            return "unknown";
        }
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Forwarded-For");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
	// 获取注解信息
    public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysLog sysLog, Object jsonResult, HttpServletRequest request) throws Exception {
        // 设置action动作
        sysLog.setBusinessType(log.businessType().ordinal());
        sysLog.setTitle(log.title());
    }

    private void setRequestValue(JoinPoint joinPoint, SysLog sysLog, HttpServletRequest request) throws Exception {
        String requestMethod = sysLog.getRequestMethod();
        if (RequestMethod.PUT.name().equals(requestMethod) || RequestMethod.POST.name().equals(requestMethod)) {
            String params = argsArrayToString(joinPoint.getArgs());
            sysLog.setOperParam(params.substring(0,2000));
        } else {
            Map<?, ?> paramsMap = (Map<?, ?>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            sysLog.setOperParam(paramsMap.toString().substring(0,2000));
        }
    }
	// 解析方法参数信息
    private String argsArrayToString(Object[] paramsArray) {
        StringBuilder params = new StringBuilder();
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object o : paramsArray) {
                if (o != null && !isFilterObject(o)) {
                    try {
                        Object jsonObj = JSON.toJSON(o);
                        params.append(jsonObj.toString()).append(" ");
                    } catch (Exception e) {
                        log.error(e.getMessage());
                    }
                }
            }
        }
        return params.toString().trim();
    }

    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }
}

Controller:在需要记录日志的请求方法上添加Log注解

package com.stt.controller;

import com.stt.annontation.BusinessTypeEnum;
import com.stt.annontation.Log;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("test")
public class TestController {

    @Log(title = "查询列表",businessType = BusinessTypeEnum.OTHER)
    @GetMapping("list")
    public String list() {
        return "返回数据列表";
    }

    @Log(title = "添加数据",businessType = BusinessTypeEnum.INSERT)
    @PostMapping
    public String save() {
        return "数据添加成功";
    }

    @Log(title = "修改数据",businessType = BusinessTypeEnum.UPDATE)
    @PutMapping
    public String update() {
        return "修改数据成功";
    }

    @Log(title = "删除数据",businessType = BusinessTypeEnum.DELETE)
    @DeleteMapping
    public String delete() {
        return "删除数据成功";
    }
}

启动项目之后请求接口效果如下:
在这里插入图片描述

请求对应接口,就会添加一条日志进库,这就是通过自定义注解+aop实现的,把这个注解给公司小伙伴使用都直呼哇塞呢!

重复注解

自JDK5以来注解开始变得越来越流行,在各个框架中广泛应用,在上边我们也通过两个案例实现了自定义注解,并投入使用,不过注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。比如:

我有两组过滤方式,一个方法上不能写两个相同的注解
在这里插入图片描述
就有老程序员绕开这个机制,写一个大注解,里边包含一个小注解数组实现,如下
在这里插入图片描述
JDK 8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。在JDK 8中使用@Repeatable注解定义重复注解
在这里插入图片描述
所以发现,其实和原本解决可重复注解的原理基本一致,将多个注解放到一个容器中,只不过Java8的这种方式将放入容器的动作隐藏起来了,编码使用注解时更方便,更直观,当我们通过反射获取注解时,发现获取到的其实是MyFilters注解
在这里插入图片描述
总结:

  • 如果需要两个相同的注解,有两种方式
  • 使用Java8之前的写法,声明一个大注解,包含需要重复的注解数组
  • 使用Java8之后的写法,仍然需要声明一个大注解,不过通过 @Repeatable 注解在需要重复的注解声明上指定大注解
  • 两者原理相同,通过反射获取的注解类型都是大注解类型

类型注解

类型注解在开发时使用较少,从Java 8开始,注解已经能应用于任何类型。这其中包括new操作符、类型转换、instanceof检查、泛型类型参数,以及implements和throws子句。这里,我们举了一个例子,这个例子中类型为String的变量name不能为空,所以我们使用了@NonNull对其进行注解

@NotNull String name = "石添的编程哲学";

我们也可以在集合的泛型上使用类型注解

List<@NotNull String> names = new ArrayList();

利用好对类型的注解非常有利于我们对程序进行分析。这两个例子中,通过这一工具我们可以确保getName不返回空,names列表中的元素总是非空值。这会极大地帮助你减少代码中不期而至的错误,不过用的比较少,增加了代码量你懂的

总结

  • 注解如同标记,为类,方法,变量,参数打上不同的标记会对应添加上不同的功能或约束
  • 注解的创建就是@interface,比接口多个个@符号
  • 注解主要给编译器及工具类型的软件用,在运行时class文件中不存在注解
  • 程序运行时需要依靠反射识别注解,由于反射比较慢,所以注解使用时也需要谨慎计较时间成本。涉及到算法的代码不建议使用注解
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

石添的编程哲学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值