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

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

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

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

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

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

这里写目录标题

  • 注解概念
  • 注解特点
  • 注解的好处:
  • 注解分类
  • Java内置注解
  • 自定义注解
    • 自定义注解步骤
    • 元注解:
    • 案例:
    • 深入一下:
  • 注解应用
    • 案例1:实现Junit的单元测试
    • 案例2:实现日志记录
  • 重复注解
  • 类型注解
  • 总结

注解概念

注解【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{// 重写注解@Overridepublic void say() {System.out.println("我是学生");}// 过时注解,大学毕业之后篮球技能过时了不会了@Deprecatedpublic 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试一下:

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

发现提示找不到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 {@MyTestpublic void test1() {System.out.println("==========test1==========");}@MyTestpublic void test2() {System.out.println("==========test2==========");}@MyTestpublic 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("运行出错!");}}}}
}

运行结果:

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

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,3methodList.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,用于存储数据进数据库@Autowiredprivate 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)@PostMappingpublic String save() {return "数据添加成功";}@Log(title = "修改数据",businessType = BusinessTypeEnum.UPDATE)@PutMappingpublic String update() {return "修改数据成功";}@Log(title = "删除数据",businessType = BusinessTypeEnum.DELETE)@DeleteMappingpublic 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文件中不存在注解
  • 程序运行时需要依靠反射识别注解,由于反射比较慢,所以注解使用时也需要谨慎计较时间成本。涉及到算法的代码不建议使用注解