Java 基础 —— 注解 Annotation
简介#
- Annotation 是从 JDK 5.0 引入的。
- 注解使得我们可以以编译器验证的格式存储程序的额外信息。注解可以生成描述符文件,甚至是新的类定义,并且有助于减轻编写“样板”代码的负担。
比如,我们项目中常常使用的 lombok
包中的注解,@Data
、@NoArgsConstructor
、@AllArgsConstructor
等注解,大大简化了代码,省了很多操作。
基本注解#
Java 提供了几个基本注解,这些内置注解它们都位于 java.lang
包下。
@Override
作用于子类中的方法,表示该方法一定要重写父类中的方法,可以有效避免子类方法名误写错误;@Deprecated
表示某类、方法等已过时,当它们被使用时,编译器将会给出警告;@SuppressWarnings
镇压警告,被修饰的程序元素及其子元素取消显示指定的编译器警告,supress
有抑制、废止的含义;@SafeVarags
Java 7 新增的,抑制堆污染警告;@FunctionalInterface
:Java 8 中加入用于表示类型声明为函数式接口
注解示例#
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
<span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">id</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> -<span class="hljs-number">1</span>;
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">msg</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">"Hi"</span>;
}
说明:
- 通过关键字
@interface
定义,并不是接口; - 注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字;
- 这里的示例上使用了一些注解,称为「元注解」,后面会详细介绍,稍安勿躁,这里仅仅是 show 一下自定义的注解长什么样;
注意点:
- 如果一个注解内仅仅只有一个名字为
value
的属性时,应用这个注解时可以直接接属性值填写到括号内; - 有一种情况是一个注解没有任何属性,在应用这个注解的时候,括号都可以省略,不包含任何元素的注解称为标记注解(
marker annotation
); - 在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组;
元注解#
JDK 除了在 java.lang
下提供了 4 个基本的 Annotation 之外,还在 java.lang.annotation
包下提供了 4 个 Meta Annotation
——元注解。这 4 个注解用于修饰其他的注解。元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。
@Retention#
Retention 的英文意为保留期的意思。当 @Retention
应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
RetentionPolicy.SOURCE
注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视;RetentionPolicy.CLASS
注解只被保留到编译进行的时候,它并不会被加载到 JVM 中;RetentionPolicy.RUNTIME
注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们;
@Documented#
它的作用是能够将注解中的元素包含到 Javadoc 中去。
@Target#
@Target
指定了注解运用的地方,规定了只能张贴到方法上、类上、方法参数上等等。@Target 有下面的取值:
ElementType.TYPE
可以给一个类型进行注解,比如类、接口、枚举ElementType.METHOD
可以给方法进行注解ElementType.FIELD
可以给属性进行注解ElementType.CONSTRUCTOR
可以给构造方法进行注解ElementType.LOCAL_VARIABLE
可以给局部变量进行注解ElementType.PACKAGE
可以给一个包进行注解ElementType.PARAMETER
可以给一个方法内的参数进行注解ElementType.ANNOTATION_TYPE
可以给一个注解进行注解
@Inherited#
Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
自定义注解#
使用 @interface
自定义注解时,自动继承了 java.lang.annotation.Annotation
接口。
一般我们使用注解,常常和验证器配合着使用。另外一种就是利用 AOP 结合使用。下面我们结合具体的示例来加深一下注解的使用。
@Length 注解#
下面,我们自定义一个注解,用来实现限制字段的长度。
@Target({ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {LengthValidator.class}) // 与约束注解关联的验证器
public @interface Length {
// 约束注解验证时的输出消息
String message() default "限制字段值的范围";
<span class="hljs-comment">// 约束注解在验证时所属的组别</span>
Class<?>[] groups() <span class="hljs-keyword">default</span> {};
<span class="hljs-comment">// 约束注解的有效负载</span>
Class<? <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Payload</span>>[] payload() <span class="hljs-keyword">default</span> {};
<span class="hljs-type">int</span> <span class="hljs-title function_">min</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-number">0</span>;
<span class="hljs-type">int</span> <span class="hljs-title function_">max</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-number">100</span>;
}
接着,我们定义对应的验证器:
public class LengthValidator implements ConstraintValidator<Length, Integer> {
private Integer min;
private Integer max;
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">initialize</span><span class="hljs-params">(Length constraintAnnotation)</span> {
<span class="hljs-built_in">this</span>.min = constraintAnnotation.min();
<span class="hljs-built_in">this</span>.max = constraintAnnotation.max();
}
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isValid</span><span class="hljs-params">(Integer value, ConstraintValidatorContext context)</span> {
<span class="hljs-keyword">if</span> (value == <span class="hljs-literal">null</span>) {
<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
<span class="hljs-keyword">if</span> (value >= min && value <= max) {
<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
<span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
}
}
注意点:
- 只有当注解中有
value
成员变量时,才可以不传key
,因为它默认是将值赋给value
成员变量的; - 成员变量没有使用
default
定义默认值,在使用注解时,就必须要对它进行赋值,有默认值则可以不必赋值; - 如果只有一个参数成员,一般参数名为
value
; but does not contain a groups parameter
注解需要定义成员变量groups
xxx contains Constraint annotation, but does not contain a payload parameter.
注解需要定义成员变量Payload
提取注解信息#
AnnotatedElement 接口是所有程序元素(如 Class、Method、Constructor 等)的父接口,所以程序通过反射获取了某个类的 AnnotatedElement 对象(如 Class、Method、Constructor 等)之后,程序就可以调用该对象的如下 3 个方法来访问 Annotation 信息:
getAnnotation(Class<T> annotationClass)
返回该程序元素上指定类型的注释,如果不存在,则返回 null,比如Length length = field.getAnnotation(Length.class);
通过反射获取到该字段上使用的@Length
注解的详细信息;Annotation[] getAnnotations()
返回该程序元素上的所有注释,例如Annotation annotations = Class.forName("Test").getMethod("info").getAnnotations();
表示获取Test
类中info
方法上的注释;boolean isAnnotatinPresent(Class<?extends Annotation> annotationClass)
判断该元素上是否存在指定类型的注释,如果存在则返回 true,否则返回 false,例如methodA.isAnnotationPresent(Length.class)
,表示判断方法 methodA 上是否用了@Length
注释;
为了获得程序中的程序元素(如 Class 、Method 等),必须使用反射知识。
// 获取某个对象的类
Class cl = obj.getClass();
// 获取某个类中定义的所有属性
Fields[] fields = cl.getDeclaredFields();
编译时处理 Annotation#
如果希望让程序中的 Annotation 在运行时起一定的作用,只有通过某种配套的工具对 Annotation 中的信息进行访问和处理,访问和处理 Annotation 的工具统称 APT(Annotation ProcessingTool)。
Annotation 处理器在处理 Annotation 时可以根据源文件中的 Annotation 生成额外的源文件和其他的文件(文件的具体内容由 Annotation 处理器的编写者决定),APT 还会编译生成的源代码文件和原来的源文件,将它们一起生成 class 文件。
我们可以在 Java 源文件中放置一些 Annotation,然后使用 APT 工具就可以根据该 Annotation 生成另一份 XML 文件,这就是 Annotation 的作用。
参考#
- On Java8 —— 第二十三章 注解
- csdn-frank909- 秒懂,Java 注解 (Annotation)你可以这样学
- 啥?听说你还在手写复杂的参数校验?
- 使用 spring validation 完成数据后端校验
- csdn-Bean Validation——自定义注解
生命不息,折腾不止!关注 「Coder 魔法院」,祝你 Niubilitiy !🐂🍺