1. 基本介绍
注解(Annotation),也叫元数据,是JDK1.5引入的新特性。注解就是在代码中的特殊标记,这些标记可以在代码编译、加载、运行的时候被读取,并执行相应的处理。
注解在开发中是非常常见的,比如Junit、Spring框架提供的一系列注解,如@Controller、@Service等注解。除了框架实现的注解,Java也提供了许多基本注解。
2. 基本注解
在java.lang包下提供了5个基本注解: @Override, @Deprecated,@SuppressWarnings, @SafeVarargs,@FunctionalInterface .
2.1 @Override
@Override 注解的定义如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
该注解定义的位置是方法.标记方法属于重写父类的方法,这可以确保子类重写了父类的方法,避免出现低级错误。
例如,在IDEA中重写Object的toString 方法如果toString方法名拼写错误的话,就会报错.
IDEA会提示该方法不是重写父类的方法.
2.2 @Deprecated
@Deprecated的定义如下
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
@Deprecated是用于标志已过时的类或方法等地方,当其他程序使用已过时的类或方法,方法时编译器会给出警告,建议不要使用.
例如:在IDEA中使用了过时的方法就会有中划线标注
2.3 @SuppressWarnings
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
@SuppressWarnings注解 用于抑制编译器警告,有选择的关闭编译器对类、方法、成员变量、变量初始化的警告。
被该注解修饰的元素以及该元素的所有子元素取消显示编译器警告.
value数组接收的值如下:
- deprecation:使用了不赞成使用的类或方法时的警告;
- unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型;
- fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
- path:在类路径、源文件路径等中有不存在的路径时的警告;
- serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
- finally:任何 finally 子句不能正常完成时的警告;
- all:关于以上所有情况的警告。
2.4 @SafeVarargs
@SafeVarargs也是用来抑制编译器警告的注解。 Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
2.5 @FunctionalInterface
@FunctionalInterface是用于声明函数式接口的.
什么是函数式接口?如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法)
接口体内只能声明常量字段和抽象方法,并且被隐式声明为public,static,final。
接口里面不能有私有的方法或变量
这个注解保证这个接口只有一个抽象方法,注意这个只能修饰接口。
总结:
Java原生注解大多数用于“标记”和“检查”。有@Override、@Deprecated、@FunctionanlInterface。
3. 元注解
元注解(元Annotation )是用来修饰注解的,常见的元注解有@Retention和@Target
3.1 @Retention
@Retention的注解定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
@Retention注解可以理解为设置注解的生命周期。@Retention注解传入的参数是RetentionPolicy枚举,该枚举有三个常量,分别是SOURCE、CLASS和RUNTIME。默认是CLASS.
RetentionPolicy注解定义与三个枚举常量的作用如下:
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
* 注解只保留到源代码,编译器编译时会丢弃
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
* 这是默认的行为, 注解会被记录到字节码文件中.但是不会在JVM运行时保留
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*注解会被记录到字节码文件中.并且在JVM运行时保留,所以注解可以通过反射获取
*
*/
RUNTIME
}
一般来说,如果要在编译期间处理注解相关的逻辑,只要自定义的注解中@Retention注解指定为CLASS级别,然后就需要继承AbstractProcessor并实现process方法来进行相关处理。比如Lombok的注解实现就用AnnotationProcessor继承了AbstractProcessor。
.java文件到class文件被JVM加载的过程如下图所示,其中的注解抽象语法树就是用于解析注解,然后做处理的逻辑。
一般来说,我们自己定义的注解都是RUNTIME级别的,因为大多数情况下是根据运行时环境去做一些处理的。而RUNTIME(运行时),是指Java文件经过javac编译后变成.class文件,然后放入到JVM,在JVM运行的期间。
3.2 @Target
@Target注解定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
@Target注解表示注解可以修饰在哪些程序单元,包括方法、成员变量、类等地方.接收传入参数ElementType枚举.ElementType定义的常量如下:
3.3 @Documented
@Documented注解用于指定被修饰的注解将被javadoc工具提取成文档,如果定义注解类时使用了这个注解修饰,则所有使用该注解修饰的程序单元,将会包含该注解说明。
3.4 @Inherited
@Inherited注解指定修饰的注解将具有继承性——如果某个类使用了注解@Xxx,则其子类将自动被注解@Xxx修饰
4. 自定义注解
4.1 基本介绍
自定义注解的格式如下:
元注解
public @interface 注解名称{
属性列表
}
没有任何成员变量的注解称为标记注解。注解的本质就是一个接口,该接口默认继承Annotation接口
public interface MyAnnotation extends java.lang.annotation.Annotation {}
注解的属性定义格式就是相当于在接口中定义接口的抽象方法.属性的返回值类型有:
- 基本数据类型
- String
- 枚举
- 注解
- (基本数据类型/String/枚举/注解)的数组类型
自定义注解的一些注意事项:
- 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
- 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
比如@SuppressWarnings注解
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
- 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
一般来说,我们自己定义的注解都是RUNTIME级别的,因为大多数情况下是根据运行时环境去做一些处理的,而Java反射是获取运行时信息的重要手段,所以我们平时开发中经常自定义注解配合Java反射机制来优化我们的代码。
4.2 注解相关的反射方法
- boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判断该元素是否被annotationClass注解修饰
- T getAnnotation(Class annotationClass):获取 该元素上annotationClass类型的注解,如果没有返回null
- Annotation[] getAnnotations():返回该元素上所有的注解
- T[] getAnnotationsByType(Class annotationClass):返回该元素上指定类型所有的注解
- Annotation[] getDeclaredAnnotations():返回直接修饰该元素的所有注解
- T[] getDeclaredAnnotationsByType(Class annotationClass):返回直接修饰该元素的所有注解
4.2 示例
使用反射技术将自定义注解的基本信息注入到对象上
1.自定义注解MyAnnotation
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
int id();
String name();
String gender() default "male";
}
2.在实体类上添加注解
@MyAnnotation(id = 1, name = "张三")
public class User {
private int id;
private String name;
private String gender;
}
3.获取注解的信息注入到对象中
public class AnnotationTest {
public static void main(String[] args) {
User user = new User();
Class<? extends User> userClass = user.getClass();
//通过反射得到类上的注解
MyAnnotation myAnnotation = userClass.getAnnotation(MyAnnotation.class);
int id = myAnnotation.id();
String name = myAnnotation.name();
String gender = myAnnotation.gender();
//将注解的值注入到对象中
user.setId(id);
user.setName(name);
user.setGender(gender);
System.out.println(user);
}
}
控制台输出:
User{id=1, name='张三', gender='male'}
拓展:
一个自定义注解配合Java反射机制的例子: 应用Java反射模拟实现Spring的IoC容器
深入学习注解:深入理解Java注解类型(@Annotation)