SpringBoot - 自定义注解
SpringBoot 如何自定义一个注解
一、注解
在Java中注解是一种用来标注类、方法、类属性、参数的元数据(metadata,描述数据的数据),始自Java1.5版本。
1.注释与注解
注释是给程序员看的,用于给其它程序员传递被注释代码的相关信息,会被编译器忽略,不会影响代码逻辑。
注解是给编译器、JVM看的,可以被编译器打包进class文件,被JVM保存,JVM在运行时可以获取到注解内容,配合其它‘工具’使用时可以影响代码逻辑。
2.注解的理解
在SpringBoot中,SpringBoot自带的注解大大简化了配置的过程、节省了代码工作量。
自定义注解一般配合AOP使用。注解给被标注对象打一个标记,AOP根据这个标记找到需要切入的点,然后实现自定义的业务逻辑。
3.注解的定义
使用@interface自定义注解。
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
String value() default "hello";
}
Test是此注解的名称,@Retention是修饰注解的元注解,value是注解的参数(默认值是hello)。
二、元注解
Java中定义了四个元注解,用来修饰其他注解。
1.@Retention
指明注解怎么保存;描述注解的声明周期(即被标注的注解在什么范围内有效)。有三种可选,可选值在枚举类RetentionPolicy中。
public enum RetentionPolicy {
SOURCE, //注解只保存在源文件中,在编译时会被编译器忽略
CLASS, //注解保存到Class文件中,但在JVM加载Class文件时遗弃
RUNTIME //注解保存到Class文件中,并且会被JVM加载
}
2.@Target
指明注解可以作用于哪些对象。其参数可填单个值或数组,有十种可选,可选值在枚举类ElementType中。
PS:
不使用@Target时默认注解可作用于任何地方。
public enum ElementType {
TYPE, //类、接口、注解、枚举类
FIELD, //字段,包括枚举值
METHOD, //方法
PARAMETER, //方法的参数
CONSTRUCTOR, //构造方法
LOCAL_VARIABLE, //局部变量
ANNOTATION_TYPE, //注解
PACKAGE, //包
TYPE_PARAMETER, //Java1.8支持,泛型
TYPE_USE //Java1.8支持,任何地方
}
3.@Inherited
指明某注解A所标注类的子类也会被A注解标注(换而言之注解可以继承给子类)。接口不受影响(实现接口的类也不会受此注解影响)。
4.@Documented
指明生成Javadoc时是此注解否会被记录,没有实际作用。
5.内置注解
Java中除了元注解,还自带了以下几个内置的注解,可直接使用:
- @Override,检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
- @Deprecated,标记过时方法。如果使用该方法,会报编译警告。
- @SuppressWarnings,指示编译器去忽略注解中声明的警告。
- @SafeVarargs,Java7开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
- @FunctionalInterface,Java8开始支持,标识一个匿名函数或函数式接口。
- @Repeatable,Java8开始支持,标识某注解可以在同一个声明上使用多次。
三、参数
在注解中可以定义参数及其默认值,参数类型可以是以下六种:
1.所有基本类型 2.String 3.enum 4.Class 5.annotation 6.以上五种类型的数组
@Retention(RetentionPolicy.RUNTIME) public @interface Test { int intValue() default 1; //基本数据类型 String value(); //String Class classValue() default Object.class; //class ElementType element_type() default ElementType.TYPE; //enum Retention target(); //annotation long[] array() default {1L, 2L}; //数组 }
注意:
1.()之前的是参数名;使用注解时没有默认值的参数必须赋值,key-value形式赋值,例:@Test(intValue = 2)。
2.如果注解中存在名为value的参数,且其他参数都有默认值,那么使用该注解时可以省略key,例:@Test(“aa”)。四、实践
本次实践内容:
使用自定义注解记录请求相关信息,将请求人uid、uname、请求的资源、执行的方法、返回码、返回信息、请求时间记录入库。1.定义
定义注解@ResultRecorder,该注解作用于Controller类,需要传入的参数是此Controller所操作的资源名。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface ResultRecorder { //RestFul中所操作的资源名,例:用户、订单、地址 String value(); }
2.实现
public class OperationLog { //操作日志entity private String uid; private String uname; private String resource; //注解@ResultRecorder中的参数值 private String method; //方法名,例:xxxController.xxxMethod() private Integer code; //方法返回状态码 private String message; //方法返回的信息 private LocalDateTime time; //请求开始时间 }
public class CommonPointcut { //切点类 @Pointcut("@within(ResultRecorder)") public void resultRecorder(){} }
@Aspect @Component public class ResultRecorderAspect { //切面类 @Resouce private OpeartionLogMapper mapper; @Around("CommonPointcut.resultRecorder()") public Object resultRecord(ProceedingJoinPoint joinPoint) { SysUser user = SystemUserHolder.getCurrent(); //获取用户信息,需自己实现 OperationLog operation = new OperationLog() .setTime(LocalDateTime.now()) //记录请求进入的时间 .setUid(user.getId) .setUname(user.getName); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); //获取方法签名 operation.setMethod(signature.toShortString()); //shortString格式为:xxxController.xxxMethod(..) ResultRecorder annotation = joinPoint.getTarget().getClass().getAnnotation(ResultRecorder.class); //获取注解 operation.setResource(annotation.value()); //获取注解中参数value的值 try { Object result = joinPoint.proceed(); //执行Aspect之后的流程 if (result instanceof BaseResponse) { //项目所有Controller中方法的返回值都是之前定义的统一返回类 BaseResponse response = (BaseResponse) result; operation.setCode(response.getCode().code()); //提取BaseResponse中的code、message operation.setMessage(response.getMessage()); } else { operation.setCode(500); operation.setMessage("Can't recognize the method's response."); } return result; } catch (Throwable throwable) { operation.setCode(500); operation.setMessage(throwable.getMessage()); throw throwable; //异常要抛出给下一流程 } finally { mapper.save(operation); //入库 System.out.println(operation); //打印入库的数据 } } }
PS:
实际项目中请勿用System.out.println来打印数据!3.结果
在CommonController类上做实验:
@ResultRecorder("公共资源") //自定义注解 @RestController @RequestMapping("/common") public class CommonController { @GetMapping public BaseResponse<Boolean> annotationTest(){ System.out.println("Executing annotationTest()..."); return new BaseResponse<>(true); } }
PS:
实际项目中请勿用System.out.println来打印数据!
实验结果:五、总结
1.注解(@interface)由元注解和注解参数构成,使用注解时若某个参数无默认值,则必须给它赋值。
2.注解本身并不能影响代码逻辑,需要配合AOP使用。
3.一般而言项目开发中基本没有自定义注解的需求,常用的注解是Spring自带的各类注解。