文章目录
- 1. 什么是Java注解
- 2. 基本语法
- 2.1 声明注解和元注解
- 2.2 @Target
- 2.3 @Retention
- 2.4 注解元素及其数据类型
- 2.5 编译器对默认值的限制
- 2.6 注解不支持继承
- 2.6 快捷方式(value)
- 3. Java内置注解与其它元注解
- 4. 注解和反射机制
1. 什么是Java注解
Java注解(Annotation)又称为Java标注,是JDK5引入的一种机制,Java语言中的类,方法,变量,参数和包等都可以标注。鉴于目前大部分框架(如Spring)都使用了注解简化代码并提高编码的效率,因此掌握并深入理解注解对于一个Java工程师是来说是很有必要的事。
实际上Java注解没有大家想的那么难,跟普通修饰符(public static void)的使用方法并没有多大区别,下面的例子是常见的注解。
public class AnnotationDemo {
//@Test注解修饰方法testA
@Test
public void testA(){
System.out.println("testA.....");
}
//一个方法上可以拥有多个不同的注解
@Deprecated
@SuppressWarnings("all")
public void testB(){
}
}通过在方法上使用@Test注解后,在运行该方法时,测试框架会自动识别该方法并单独调用,@Test实际上是一种标记注解,起标记作用,运行时告诉测试框架该方法为测试方法。而对于@Deprecated和@SuppressWarnings(“all”),则是Java本身内置的注解,在代码中,可以经常看见它们,但这并不是一件好事,毕竟当方法或是类上面有@Deprecated注解时,说明该方法或是类都已经过期不建议再用,@SuppressWarnings 则表示忽略指定警告,比如@SuppressWarnings(“all”),这就是注解的最简单的使用方式,有兴趣了解SuppressWarnings中值的可以参考下图:

2. 基本语法
2.1 声明注解和元注解
我们先来看看前面的Test注解是如何声明的:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Test {
}我们使用了@interface来声明Test注解。并使用了Target注解传入ElementType.METHOD参数来标明@Test只能用在方法上,@Retention(RetentionPolicy.RUNTIME)则用来表示该注解生存期是运行时,可以被JVM加载识别。从代码上看注解的定义很像接口的定义,确实如此,毕竟在编译后也会生成Test.class文件。对于@Target和@Retention是由Java提供的元注解,什么是元注解呢?就是标记其他注解的注解,下面会分别介绍。
2.2 @Target
@Target注解用来约束注解可以应用的地方(如方法,类,字段),ElementType 是 Enum 枚举类型,它用来指定 Annotation 的类型。其定义如下:
public enum ElementType {
//类,接口(包括注解类型)和枚举声明
TYPE,
//字段声明,包括枚举常量
FIELD,
//方法声明
METHOD,
//参数声明
PARAMETER,
// 构造器函数声明
CONSTRUCTOR,
//局部变量声明
LOCAL_VARIABLE,
//注解类型声明
ANNOTATION_TYPE,
//包声明
PACKAGE,
/**
* 标明注解可以用于类型参数声明(1.8新加入)
* @since 1.8
*/
TYPE_PARAMETER,
/**
* 类型使用声明(1.8新加入)
* @since 1.8
*/
TYPE_USE
}注意:
当注解未指定Target值时,则此注解可以作用在任何元素之上,多个值可以使用{}并用逗号隔开。如下:
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})新增的两种ElementType
在Java8中ElementType新增了两个枚举成员,TYPE_PARAMETER和TYPE_USE。在java8之前注解只能标注在一个声明(如类,字段,方法)上。新增的TYPE_PARAMETER可以用来标注类型参数,而TYPE_USE则可以用于标注任意类型(不包括class)。如下所示
//TYPE_PARAMETER 标注在类型参数上
class D<@Parameter T> { }
//TYPE_USE则可以用于标注任意类型(不包括class)
//用于父类或者接口
class Image implements @Rectangular Shape { }
//用于构造函数
new @Path String("/usr/bin")
//用于强制转换和instanceof检查,注意这些注解中用于外部工具,它们不会对类型转换或者instanceof的检查行为带来任何影响。
String path=(@Path String)input;
if(input instanceof @Path String)
//用于指定异常
public Person read() throws @Localized IOException.
//用于通配符绑定
List<@ReadOnly ? extends Person>
List<? extends @ReadOnly Person>
@NotNull String.class //非法,不能标注class
import java.lang.@NotNull String //非法,不能标注import这里主要说明一下TYPE_USE,类型注解用来支持在Java的程序中做强类型检查,配合第三方插件工具(如Checker Framework),可以在编译期检测出runtime error(如UnsupportedOperationException、NullPointerException异常),避免异常延续到运行期才发现,从而提高代码质量,这就是类型注解的主要作用。总之Java 8 新增加了两个注解的元素类型ElementType.TYPE_USE 和ElementType.TYPE_PARAMETER ,通过它们,我们可以把注解应用到各种新场合中。
2.3 @Retention
@Retention用来约束注解的生命周期,分别有三个字,源码级别(SOURCE),类文件级别(CLASS)和运行时级别(RUNTIME)。其含义如下:
package java.lang.annotation;
public enum RetentionPolicy {
//Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了
SOURCE,
//编译器将Annotation存储于类对应的.class文件中。但不会加载到JVM中。默认行为
CLASS,
// 编译器将Annotation存储于class文件中,并且可由JVM读入,因此运行时我们可以获取。
RUNTIME
}- SOURCE:若 Annotation 的类型为 SOURCE,则意味着:Annotation 仅存在于编译器处理期间,编译器处理完之后,该 Annotation 就没用了。 例如," @Override" 标志就是一个 Annotation。当它修饰一个方法的时候,就意味着该方法覆盖父类的方法;并且在编译期间会进行语法检查!编译器处理完后,"@Override" 就没有任何作用了。
- CLASS:注解在class文件中可用,但会被JVM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中),请注意,当注解未定义Retention值时,默认值是CLASS,如Java内置注解,@Override、@Deprecated、@SuppressWarnning等。
- RUNTIME:注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息),如SpringMvc中的@Controller、@Autowired、@RequestMapping等。
2.4 注解元素及其数据类型
通过上述对@Test注解的定义,我们了解了注解定义的过程,由于@Test注解没有定义其他元素,所以@Test也称为标记注解(marker annotation),但在自定义注解中,一般都会包含一些元素以表示某些值,方便处理器使用,这点在下面的例子将会看到:
@Retention(RetentionPolicy.RUNTIME)//保存到运行时
@Target(ElementType.TYPE) //只能作用于类上
public @interface MyAnnotation {
String name() default "jerry";
int age();
}上述定义了一个名为MyAnnotation 的注解,该注解主要给类添加属性,与前面Test注解不同的是,我们声明一个String类型的name元素,其默认值为jerry,但是必须注意到对应任何元素的声明应采用方法的声明方式,同时可选择使用default提供默认值。
@MyAnnotation 使用方式如下:
@MyAnnotation(age = 25)
public class User {
}关于注解支持的元素数据类型除了上述的String,还支持如下数据类型
- 基本类型:byte,short,int,long,boolean,float,double,char
- String
- Class
- Enum
- Annotation
- 上述类型的数组
注意:
倘若使用了其他数据类型,编译器将会丢出一个编译错误,注意,声明注解元素时可以使用基本类型但不允许使用任何包装类型,同时还应该注意到注解也可以作为元素的类型,也就是嵌套注解。
2.5 编译器对默认值的限制
编译器对元素的默认值要求非常高:
- 首先要求元素不能有
不确定的值,也就是说元素要么有默认值,要么在使用注解时提供默认值。 - 对于非基本类型的元素,无论是在源码中声明,还是在使用注解时提供定义默认值,都不能以
null作为值,这就是限制。 - 为了绕开限制,我们可以根据注解定义一些特殊的值,例如空字符串或者负数,表示某个元素不存在。
2.6 注解不支持继承
注解是不支持继承的,因此不能使用关键字extends来继承某个@Annotation。但注解在编译后,编译器会自动继承java.lang.annotation.Annotation接口,这里我们反编译前面定义的MyAnnotation注解
package com.example.day10;
import java.lang.annotation.Annotation;
//反编译后的代码
public interface MyAnnotation extends Annotation
{
public abstract String name();
public abstract int age();
}虽然反编译后发现MyAnnotation 注解继承了Annotation接口,请记住,即使Java的接口可以实现多继承,但定义注解时依然无法使用extends关键字继承@interface。
2.6 快捷方式(value)
所谓的快捷方式就是注解中定义了名为value的元素,并且在使用该注解时,如果该元素是唯一需要赋值的一个元素,那么此时无需使用key=value的语法。而只需在括号中给出value元素所需要的值即可,这可以应用于任何合法类型的元素,记住,这限制了元素名必须为value,简单案例如下
//定义注解
@Retention(RetentionPolicy.RUNTIME)//保存到运行时
@Target(ElementType.TYPE) //只能作用于类上
public @interface MyAnnotation {
String name() default "jerry";
int value();
}
//@MyAnnotation(25)//当只想给value赋值时,可以使用以下快捷方式
@MyAnnotation(name = "tom",value = 9)
//当name也需要赋值时必须采用key=value的方式赋值
class Person implements Serializable {
}3. Java内置注解与其它元注解
Java定义了一套内置注解,共有10个,6个在java.lang中,剩下的4个在java.lang.annotation中
作用在代码的注解是
- @Override:检查该方法是否是重写方法,如果发现其父类,或者实现的接口中没有该方法会编译错误,源码如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}- @Deprecated:标记过时方法,如果使用该方法,会报编译警告。源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}- @SuppressWarnings:指示编译器去忽略注解中声明的警告。
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}- @SafeVarargs:Java7开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告,源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}- @FunctionInterface:Java8开始支持,标识一个匿名函数或者函数式接口。源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}- @Repeatable :Java 8 开始支持,标识某注解可以在同一个声明上使用多次。源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
Class<? extends Annotation> value();
}作用在其他注解的注解(或者说 元注解)是:
- @Retention:标识这个注解怎么保存,是保存在源文件中,还是class文件中,又或者运行时可以通过反射访问。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}- @Target:标记这个注解可以修饰哪些 Java 成员。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}- @Documented :标记这些注解是否包含在用户文档中。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}接下来我们就用一个生成一个用户文档
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentA {
}
//没有使用@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentB {
}
@DocumentA
@DocumentB
public class DocumentDemo {
public void Documented(){
}
}有两种办法可以生成doc文档,一个是使用jdk自带的javadoc,另外一个是使用工具,我们这里使用idea的工具生成。
1. 在IDEA中选择工具(Tool)选项卡打开并选择Generate JavaDoc(生成javaDoc)

2.配置各种参数

第一步:选择生成JavaDoc文档的范围,我只对一个源文件生成Doc,所以选择文件。
第二步:输出目录最好新建一个文件夹,比较有条理和整洁
第三步:区域设置,决定文档的语言,简体中文就是zh_CN、繁体(台湾)zh_tw、繁体(香港)zh-hk、英语(香港)en-hk、英语(美国)en-us、英语(英国)en-gb、英语(全球)en-ww
第四步:其他命令行参数:如果区域设置为中国,参数一般为
-encoding UTF-8 -charset UTF-8 主要意义是为了显示中文不出现乱码
第五步:设置完成后点击确定即可生成Doc文档
如下图:

可以发现使用@Documented元注解定义的注解(@DocumentA)将会生成到javadoc中,而@DocumentB则没有在doc文档中出现,这就是元注解@Documented的作用。
4. @Inherited - 如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}4. 注解和反射机制
前面经过反编译后,我们知道Java所有的注解都继承Annotation接口,也就是说Java使用Annotation接口代表注解元素,该接口是所有Annotation类型的父接口。同时为了运行时能准确获取到注解的相关信息,Java在java.lang.reflect 反射包下新增了AnnotatedElement接口,它主要用于表示目前正在 JVM 中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,如反射包的Constructor类、Field类、Method类、Package类和Class类都实现了AnnotatedElement接口。
下面是AnnotatedElement中相关的API方法,以上5个类都实现以下的方法
返回值 | 方法名称 | 说明 |
|
| 该元素如果存在指定类型的注解,则返回这些注解,否则返回null |
|
| 该元素如果存在指定类型的注解,则返回这些注解,否则返回null |
|
| 如果指定类型的注解存在此元素上,则返回true,否则返回false |
|
| 返回直接存在于此元素上的所有注解,注意,不包括父类的注解 |
简单案例演示如下:
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentA {
}
//没有使用@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentB {
}
@DocumentA
class testA{
}
//继承了A类
@DocumentB
public class DocumentDemo extends testA {
public static void main(String[] args) {
//获取指定类型的注解
DocumentA annotation = testA.class.getAnnotation(DocumentA.class);
System.out.println(annotation);
//获取所有的注解,包括从父类继承
Annotation[] annotation1 = DocumentDemo.class.getAnnotations();
System.out.println(Arrays.toString(annotation1));
//获取所有的注解,不包括从父类继承
Annotation[] annotation2 = DocumentDemo.class.getDeclaredAnnotations();
System.out.println(Arrays.toString(annotation2));
//判断注解DocumentA是否在该元素上
boolean b=DocumentDemo.class.isAnnotationPresent(DocumentA.class);
System.out.println(b);
}
}测试结果:

















