注解是java语言用于工具处理的标注 从JVM的角度来看,注解本身对代码逻辑没有任何影响,想要实现的功能完全由程序代码决定
注解的分类(按注解的生命周期):
SOURCE:形如@Override等,不会被编译到.class文件中,在编译期由编译期进行覆写方法的检查,编译后就被忽略了。CLASS:还有一些会被编译到.class,但JVM加载.class文件时不会把这类注解放到内存中,一般被一些底层库使用。RUNTIME:运行期注解,最常见的注解,会被加载到JVM中,在运行时可以获取注解信息。注解的参数: 支持的参数类型:基本类型、String、Class以及枚举的数组。
配置参数必须是常量,大部分注解会有名为value的参数,对此参数赋值,可以只写常量,相当于省略了value参数。
例如:@Service(value=“TestServiceName”)可以写成@Service(“TestServiceName”)
Java语言使用@interface语法来定义注解(Annotation)
// 看一看@RestController注解源码 @Target(ElementType.TYPE) //表示@RestController可以定义在类上 @Retention(RetentionPolicy.RUNTIME) //运行期注解,可以在运行时获取注解信息并处理 @Documented // 表示该注解会出现在javadoc文档中 // RestController集成了@Controller和@ResponseBody,所以使用@RestController相当于同时使用了Controller和responseBody两个注解 @Controller @ResponseBody public @interface RestController { @AliasFor(annotation = Controller.class) String value() default ""; }@Target、@Retention、@Documented都是用于修饰其他注解的元注解 我们使用这些元注解来自定义注解。
Java内置元注解说明
@Retention(RetentionPolicy) 用来标识注解的生命周期(如果@Retention不存在,默认为CLASS) RetentionPolicy.SOURCE 仅编译期 RetentionPolicy.CLASS (默认) 仅class文件 RetentionPolicy.RUNTIME 运行期 通常我们自定义的Annotation都是RUNTIME所以,务必加上@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType) 可以定义Annotation能够被应用于哪些注解 ELementType.TYPE 类或接口 ELementType.FIELD 字段 ElementType.METHOD 方法 ElementType.PARAMETER 方法参数 ElementType.CONSTRUCTOR 构造方法
@Target可以接收ElementType数组:
@Target([ ElementType.METHOD, ElementType.FIELD ])表示被修饰的注解可以应用于方法和字段上
@Documented 标识注解是否成为api的一部分
@Inherited(了解,应用不广泛) 使用@Inherited定义子类是否可继承父类定义的Annotation,例如B继承了A,A添加了注解C(该注解被@Inherited修饰),那么B也添加了注解C(可以获取注解C上的信息) @Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效(类),并且仅针对class的继承,对interface的继承无效
@Repeatable (了解,应用不广泛,可以定义注解是否可以重复) 注解默认只能直接修饰某个元素一次,如果修饰了多次那么会出现编译异常,如果想要注解在某个元素上出现多次,可以使用@Repeatable
自定义注解小结
Java使用@interface定义注解;可定义多个参数和默认值,核心参数使用value名称;必须设置@Target来指定Annotation可以应用的范围;应当设置@Retention(RetentionPolicy.RUNTIME)便于运行期读取该Annotation。单纯标注注解并不会对程序逻辑有任何影响,注解的功能由读取它并执行逻辑的代码决定
注解定义后也属于class,所有注解都继承自java.lang.annotation.Annotation 读取java注解,需要使用反射API
// @Report注解示例 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Report { int type() default 0; String level() default "info"; String value() default ""; }Java提供使用反射API读取Annotation的方法包括:
判断注解是否存在于Class\FIeld\Method\Constructor: Class.isAnnotationPresent(Class)Field.isAnnotationPresent(Class)Method.isAnnotationPresent(Class)Constructor.isAnnotationPresent(Class)例如:判断@Report是否存在于User类
User.class.isAnnotationPresent(Report.class); 使用反射API读取注解信息 XXX.getAnnotation(Class)例如:获取User类中定义的@Report注解
// 当注解是一种接口,获取之后可以调用接口方法得到定义的值 Report report = User.class.getAnnotation(Report.class); int type = report.type(); String level = report.level();读取方法、字段和构造方法的Annotation和Class类似。
(方法参数上的注解,不感兴趣可以忽略这个部分) 但要读取方法参数的Annotation就比较麻烦一点,因为方法参数本身可以看成一个数组,而每个参数又可以定义多个注解,所以,一次获取方法参数的所有注解就必须用一个二维数组来表示。例如,对于以下方法定义的注解:
public void hello(@NotNull @Range(max=5) String name, @NotNull String prefix) { }要读取方法参数的注解,我们先用反射获取Method实例,然后读取方法参数的所有注解:
// 获取Method实例: Method m = ... // 获取所有参数的Annotation: Annotation[][] annos = m.getParameterAnnotations(); // 第一个参数(索引为0)的所有Annotation: Annotation[] annosOfName = annos[0]; for (Annotation anno : annosOfName) { if (anno instanceof Range) { // @Range注解 Range r = (Range) anno; } if (anno instanceof NotNull) { // @NotNull注解 NotNull n = (NotNull) anno; } }读取注解时一般可以先判断注解是否存在,再读取注解,或者直接读取,不存在则返回null
JVM不会对自定义的注解添加任何逻辑,怎样处理完全由Java代码决定
下边定义一个注解@Range用来校验对象的字段长度是否符合自定义约束
Range.class
package com.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author sunhongmin * @Date 2020-10-21 * @Describe 字段长度约束,默认0-255 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Range { int min() default 0; int max() default 255; }RangeTestDemo.class
package com.annotation; import java.lang.reflect.Field; class User { /** * 最大长度为10 */ @Range(max = 10) public String name; /** * 使用默认值255 */ @Range public String address; public User() { } public User(String name, String address) { this.name = name; this.address = address; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } } public class RangeTestDemo { public static void checkRange(User user) throws IllegalAccessException { Field[] declaredFields = user.getClass().getDeclaredFields(); for (Field field : declaredFields) { // 如果标注了Range注解 if (field.isAnnotationPresent(Range.class)) { // 获取注解 Range range = field.getAnnotation(Range.class); // 获取field值 Object value = field.get(user); if (value instanceof String) { String str = (String) value; if (str.length() < range.min() || str.length() > range.max()) { throw new IllegalArgumentException("Invalid field:{" + field.getName() + "},value:{" + str + "},Limit range [" + range.min() + "," + range.max() + "]"); } } } } } public static void main(String[] args) throws IllegalAccessException { User user = new User("张大彪啊啊啊啊啊啊啊asdasdasdasda", "地址在哪呢在哪在哪在哪在哪在哪在哪在哪在哪在哪在哪在哪在哪"); RangeTestDemo.checkRange(user); } }执行结果: 全文结束
参考链接: 廖雪峰的官方网站