Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。Java 语言中的类、方法、变量、参数和包等都可以被标注。
- 注解和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。
- 注解也支持自定义,即自定义Java标注。
通俗的说注解就是给所标注的对象打标签,对所标注对象进行描述、限制、验证等一系列操作。
注解基本概念
日常开发中新建Java类,我们使用class、interface比较多,而注解和它们一样,也是一种类的类型,修饰符为 @interface
。
创建注解
我们通过IDEA创建一个注解:
这样一个注解就被创建出来了:
package com.example.MyFirstJavaWeb.annotation;
public @interface MyFirstAnnotation {
}
注解属性
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以无形参的方法
形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
public @interface MyFirstAnnotation {
int id() default 1;
String msg() default "hello Annotation";
}
当然注解属性也可以拥有默认值,这种情况下,在使用注解时可以不用显式赋值。
使用注解
使用注解也十分简单,我们在要使用的位置直接打注解即可:
@MyFirstAnnotation(id=1,msg="MyFirstAnnotation")
public class AnnotationFather {
}
当然此时注解内没有任何逻辑,没有作用效果,需要我们添加逻辑。这里先简单介绍下创建语法,然后聊聊注解相关的概念我们再去实际的去写一段逻辑。
注解的本质
注解的本质就是一个Annotation接口:
/**Annotation接口源码*/
public interface Annotation {
boolean equals(Object obj);
int hashCode();
Class<? extends Annotation> annotationType();
}
通过以上源码,我们知道注解本身就是Annotation接口的子接口,也就是说注解中其实是可以有属性和方法,但是接口中的属性都是static final的,对于注解来说没什么意义,而我们定义接口的方法就相当于注解的属性,也就对应了前面说的为什么注解只有属性成员变量,其实他就是接口的方法,这就是为什么成员变量会有括号,不同于接口我们可以在注解的括号中给成员变量赋值
元注解
元注解顾名思义我们可以理解为注解的注解,它是作用在注解中,方便我们使用注解实现想要的功能。元注解分别有@Retention、 @Target、 @Document、 @Inherited和@Repeatable
(JDK1.8加入)五种
Retention
Retention 的英文意为保留期的意思。当 @Retention
应用到一个注解上的时候,它解释说明了这个注解的的存活时间,它的取值如下:
- RetentionPolicy.SOURCE :注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
- RetentionPolicy.CLASS :注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
- RetentionPolicy.RUNTIME :注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
Retention其实明确了该注解的作用域,在什么时候才能得到使用。这里我们让我们的注解一直可以被保留到运行时:
@Retention(RetentionPolicy.RUNTIME)
public @interface MyFirstAnnotation {
int id() default 1;
String msg() default "hello Annotation";
}
Documented
顾名思义,这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去,我们也将这个元注解增加到我们的注解中去:
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyFirstAnnotation {
int id() default 1;
String msg() default "hello Annotation";
}
Target
Target 是目标的意思,@Target
指定了注解运用的地方。当一个注解被 @Target
注解时,这个注解就被限定了运用的场景。类比到标签,原本标签是你想张贴到哪个地方就到哪个地方,但是因为 @Target
的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等等。
一般比较常用的是ElementType.TYPE类型,这里我们给我们的注解也加上限定注释目标的元注解:
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
public @interface MyFirstAnnotation {
int id() default 1;
String msg() default "hello Annotation";
}
Inherited
Inherited的英文意思是继承,但是这个继承和我们平时理解的继承大同小异,一个被@Inherited
注解了的注解修饰了一个父类,如果他的子类没有被其他注解修饰,则它的子类也继承了父类的注解。我们给自己的注解加上这个元注解:
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Inherited
public @interface MyFirstAnnotation {
int id() default 1;
String msg() default "hello Annotation";
}
可以理解为,父类Father注解了MyFirstAnnotation,那么Son继承Father也自动继承了它的注解。
@MyFirstAnnotation(id=1,msg="MyFirstAnnotation")
public class AnnotationFather {
}
AnnotationChildren 继承了AnnotationFather类
public class AnnotationChildren extends AnnotationFather{
public static void main(String[] args){
//获取Son的class对象
Class<AnnotationChildren> sonClass = AnnotationChildren.class;
// 获取Son类上的注解MyTestAnnotation可以执行成功
MyFirstAnnotation annotation = sonClass.getAnnotation(MyFirstAnnotation.class);
System.out.println(annotation.annotationType());
}
}
我们看下打印结果:
Repeatable
Repeatable的英文意思是可重复的。顾名思义说明被这个元注解修饰的注解可以同时作用一个对象多次,但是每次作用注解又可以代表不同的含义。多次注解的方法依赖容器注解的使用,首先定义一个容器注解:
package com.example.MyFirstJavaWeb.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Inherited
public @interface Volume
{
MyFirstAnnotation[] value(); //注解的属性
}
按照规定,它里面必须要有一个 value 的属性,属性类型是一个被 @Repeatable 注
解过的注解数组,也就是我们的自定义注解:
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Inherited
@Repeatable(Volume.class)
public @interface MyFirstAnnotation {
int id() default 1;
String msg() default "hello Annotation";
}
我们的自定义注解的Repeatable元注解正是使用了容器注解:@Repeatable(Volume.class)
,使用的时候可以这样:
@MyFirstAnnotation(id=1,msg="MyFirstAnnotation")
@MyFirstAnnotation(id=2,msg="MyFirstAnnotation2")
@MyFirstAnnotation(id=3,msg="MyFirstAnnotation3")
public class AnnotationFather {
}
我们可以这么理解,AnnotationFather 想使用不同属性值的MyFirstAnnotation注解,但是一次只能用一个,所以它求助于Repeatable,通过引入容器注解解决它的需求。
Java预置注解
通过以上内容我们大致定义了一个没有任何实现的注解,其实Java内部已经预定义好了各种注解可以直接使用:
@Deprecated
:@Deprecated
所标注内容,不再被建议使用。@Override
:@Override
只能标注方法,表示该方法覆盖父类中的方法。@SuppressWarnings
:@SuppressWarnings
所标注内容产生的警告,编译器会对这些警告保持静默。
这些预置注解都是可以直接使用的,当然引入第三方依赖以及框架依赖后预置可使用的注解数量会大大增多。
自定义注解的使用
当我们自定义一个注解的之后,不光是把注解的标签打到目标的类或方法或其它元素上,我们还期望在运行时获取到它的属性值来进行相应的操作,这才是一个完整的闭环。
定义注解
定义注解的部分其实从上边的流程我们已经处理的比较清晰了,这里我们定义一个简单的人员注解:
package com.example.MyFirstJavaWeb.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Inherited
public @interface PersonAnnotation {
String name();
int age() ;
String sex();
}
再定义一个给属性使用的注解:
package com.example.MyFirstJavaWeb.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.FIELD)
@Inherited
public @interface NameAnnotation {
int value() default 1;
}
以及一个给方法使用的注解:
package com.example.MyFirstJavaWeb.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.METHOD)
@Inherited
public @interface PlayGameAnnotation {
int value() default 1;
}
标注注解
定义好后,我们给AnnotationFather进行注解标记:
package com.example.MyFirstJavaWeb.javabean;
import com.example.MyFirstJavaWeb.annotation.NameAnnotation;
import com.example.MyFirstJavaWeb.annotation.PersonAnnotation;
import com.example.MyFirstJavaWeb.annotation.PlayGameAnnotation;
@PersonAnnotation(name = "tml",age=27,sex = "男")
public class AnnotationFather {
@NameAnnotation
private String name="张三";
private int age=20;
private String sex="女";
@PlayGameAnnotation
public void playGame(){
}
}
提取注解
使用注解,也就是提取注解的方法如下:
/**是否存在对应 Annotation 对象*/
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
return GenericDeclaration.super.isAnnotationPresent(annotationClass);
}
/**获取 Annotation 对象*/
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
Objects.requireNonNull(annotationClass);
return (A) annotationData().annotations.get(annotationClass);
}
/**获取所有 Annotation 对象数组*/
public Annotation[] getAnnotations() {
return AnnotationParser.toArray(annotationData().annotations);
}
提取注解的方式如下:
package com.example.MyFirstJavaWeb.javabean;
import com.example.MyFirstJavaWeb.annotation.NameAnnotation;
import com.example.MyFirstJavaWeb.annotation.PersonAnnotation;
import com.example.MyFirstJavaWeb.annotation.PlayGameAnnotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws NoSuchMethodException {
/**
* 获取类注解属性
*/
Class<AnnotationFather> fatherClass = AnnotationFather.class;
boolean annotationPresent = fatherClass.isAnnotationPresent(PersonAnnotation.class);
if(annotationPresent){
PersonAnnotation annotation = fatherClass.getAnnotation(PersonAnnotation.class);
System.out.println(annotation.name());
System.out.println(annotation.age());
System.out.println(annotation.sex());
}
/**
* 获取属性和方法注解
*/
try {
Field name = fatherClass.getDeclaredField("name");
boolean annotationPresent1 = name.isAnnotationPresent(NameAnnotation.class);
if(annotationPresent1){
NameAnnotation annotation = name.getAnnotation(NameAnnotation.class);
System.out.println(annotation.value());
}
Method play = AnnotationFather.class.getDeclaredMethod("playGame");
boolean annotationPresent2 = play.isAnnotationPresent(PlayGameAnnotation.class);
if (annotationPresent2){
PlayGameAnnotation annotation = play.getAnnotation(PlayGameAnnotation.class);
System.out.println(annotation.value());
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
打印的结果如下:
注解处理【APT】
和提取注解类似,提取注解后可以使用一些业务代码逻辑来进行处理。之后在应用到这些的时候详细说说这方面内容。说白了处理逻辑都是可以自定义的,所以注解具体是什么取决于你怎么使用它。
注解的应用
注解主要针对的是编译器和其它工具软件(SoftWare tool)。,注解一般有如下的用途:
- 编译检查: 编译器可以利用注解来检测出错误或者警告信息,打印出日志。
- 编译自动处理【插入式注解】: 软件工具可以用来利用注解信息来自动生成代码、文档或者做其它相应的自动处理,例如loombook,
- 运行时处理【反射】: 某些注解可以在程序运行的时候接受代码的提取,自动做相应的操作,例如使用注解进行参数化的配置
- 生成帮助文档:通过给 Annotation 注解加上 @Documented 标签,能使该 Annotation 标签出现在 javadoc 中
当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)
总结一下
注解是什么?注解就类似一个标签,打在类、方法或者属性上。描述、验证以及限制这些目标,在运行时可以通过反射获取注解的属性值,又能有动态配置的感觉。而其在编译阶段的作用又有编译检查和自动生成代码的作用,最后还有生成帮助文档的作用。总而言之注解就是方便我们编码的工具,有了注解很多固定逻辑可以被反复复用,例如一个属性值的最大、最小范围等都可以用注解标识,而免去了大量的逻辑代码。注解的大量使用在Spring,那个时候估计还要拿出来深入研究一番