注解(Annotation)为我们在代码中添加信息提供了一种形式化的方法,我们可以在稍后某个时刻方便地使用这些数据(通过解析注解来使用这些数据)。
注解是那些插入到源代码中用于工具处理的标签。这些标签可以在源码层次上进行处理,或者可以通过编译器将它们纳入到类文件中。
注解不会改变对编写的程序的编译方式。
为了能够受益于注解,需要选择一个处理工具,然后向你的处理工具可以理解的代码中插入注解,之后运用该处理工具。
注解的作用
注解常见的作用有以下几种:
- 生成文档。这是最常见的,也是java最早提供的注解。常用的有@see @param @return等;
- 跟踪代码依赖性,实现替代配置文件功能。如Spring中的基于注解配置。作用是减少配置;
- 在编译时进行格式检查,如@Override放在方法前,如果这个方法并不是覆盖了超类方法,则编译时就能检查出;
标准注解
JDK5.0定义了7个注解接口。其中三个是规则接口,可以用它们来注释你的源代码中的项。其他四个是元注释,用于描述注解接口的行为属性。
正规注解
@Deprecated注解可以被添加到任何不在鼓励使用的条目上。当使用一个已过时的条目时,编译器将会发出警告。
@SuppressWarning注释会告知编译器阻止特殊类型的警告信息。
@Override注释只能应用到方法上,编译器会阻止具有这种注释的方法去覆盖一个来自于超类的方法。
元注解
@Target元注释
@Target元注释可以应用于一个注释,以限制该注释可以应用到哪些条目上。
下表显式了所有可能的取值情况,它们属于枚举类型ElementType。可以指定任意数量的元素类型,用括号括起来。
元素类型 | 注释使用场合 |
ElementType ANNOTATION_TYPE | 注释类型声明 |
ElementType PACKAGE | 包 |
ElementType TYPE | 类(包括enum)及接口(包括注解类型) |
ElementType METHOD | 方法 |
ElementType CONSTRUCTOR | 构造器 |
ElementType FIELD | 成员域(包括enum常量) |
ElementType PARAMETER | 方法或构造器参数 |
ElementType LOCAL_VARIABLE | 本地变量 |
一条没有@Target限制的注解可以应用与任何条目上。
编译器只检查是否将一条注解应用到了某个允许的条目上,否则会导致一个编译器错误。
例如Spring中的注释@Controller定义:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any
*/
String value() default "";
}
@Retention元注解
@Retention元注释用于指定一条注释应该保留多长时间,只能将其指定为表中的任意值,默认为RetentionPolicy.CLASS.
保留规则 | 描述 |
RetentionPolicy SOURCE | 不包括在类文件*.class中的注释 |
RetentionPolicy CLASS | 类文件中的注释,但是虚拟机不需要将它们载入 |
RetentionPolicy RUNTIME | 类文件中的注释,并有虚拟机载入,通过反射API可以获得它们 |
@Documentd元注解
@Documentd元注释为像Javadoc这样的文档工具提供了一些提示。
@Inherited元注解
@Inherited元注解只能应用与对类的注解。如果一个类具有继承注解,那么它的所有子类都自动具有同样的注解。
注解语法
一个注解是由一个注解接口@interface来定义的:
modifier @interface AnnotationName{
element declaration 1
element declaration 2
...
...
}
注解中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型,返回值类型只能是基本类型,Class,String,enum,注解类型,或由前面类型所组成的数组。可以通过default来声明参数的默认值。
每个元素具有下面这种形式:
type elementName();
或者
type elementName() default value;
例如:下面这个注解具有两个元素:assingedTo和severity。
public @interface BugReport{
String assignedTo() default "[none]";
int severity();
}
每个注解使用时都具有下面这种格式:
@AnnotationName(elementName1 = value1, elementName2 = value2, ... ...)
例如:
@BugReport(assignedTo = "Harry", severity=10)
元素的顺序无关紧要,下面这个注解和前面那个一样:
@BugReport(severity=10, assignedTo = "Harry")
如果某个元素的值并未指定,那么就使用声明的默认值。
默认值并不是和注解存储在一起的,它们是动态计算而来的。例如,如果将元素assignedTo的默认值更改为“[]”,然后重新编译BugReport接口,那么注解@BugReport(severity=10)将使用这个新的默认值。
简化注解的使用:
- 标记注解:
如果没有指定元素,要么是因为注解中没有任何元素,要么是因为所有元素都使用默认值,那么就不需要使用圆括号,例如:
<span style="font-size:10px;">@BugReport</span>
- 单值注解:
如果一个元素具有特殊的名字value,并且没有指定其他元素,那么就可以忽略掉这个元素名及等号这个符号,例如:
<span style="font-size:10px;">public @interface BugReport{
String value();
}</span>
那么,可以将这个注解写成如下形式:
<span style="font-size:10px;">@BugReport("Harry")</span>
所有的注解接口隐式地扩展自java.lang.annotation.Annotation接口,这个接口是一个正规接口,不是一个注解接口。
无法扩展注解接口,所有的注解接口都直接扩展自java.lang.annotation.Annotation。
一个项目可以具有多个注解,只要它们属于不同的类型即可。当注解一个特定项的时候,不能多次使用同一个注解类型,例如:
<span style="font-size:10px;">@BugReport(severity=11)
@BugReport(severity=12)
void myMethod(){... ...}</span>
就是一种编译期错误。
读取/使用类中定义的注解
示例:
<span style="font-size:10px;">package annotation;
import java.lang.*;
import java.util.*;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestA {
String name();
int id() default 0;
Class<Long> gid();
}
</span>
<span style="font-size:10px;">package annotation;
import java.util.*;
import java.lang.*;
@TestA(name="type", gid=Long.class)
public class UserAnnotation {
@TestA(name="param", id = 1, gid=Long.class)
private Integer age;
@TestA(name="construct", id = 2, gid=Long.class)
public UserAnnotation(){
}
@TestA(name="public method", id = 3, gid=Long.class)
public void a(){
}
@TestA(name="protected method", id = 4, gid= Long.class)
protected void b(){
}
@TestA(name="private method", id = 5, gid = Long.class)
private void c(){
}
}
</span>
<span style="font-size:10px;">package annotation;
import java.lang.*;
import java.util.*;
import java.lang.annotation.*;
import java.lang.reflect.*;
public class ParseAnnotation {
public static void parseTypeAnnotation() throws ClassNotFoundException{
Class clazz = Class.forName("annotation.UserAnnotation");
Annotation[] annotations = clazz.getAnnotations();
for(Annotation annotation : annotations){
TestA testA = (TestA)annotation;
System.out.println("id = " + testA.id() + " name = " + testA.name() + " gid = " + testA.gid());
}
}
public static void parseMethodAnnotation(){
Method[] methods = UserAnnotation.class.getDeclaredMethods();
for (Method method : methods){
boolean hasAnnotation = method.isAnnotationPresent(TestA.class);
if (hasAnnotation){
TestA testA = (TestA)method.getAnnotation(TestA.class);
System.out.println("method = " + method.getName() + " id = " + testA.id() + " name = " + testA.name() + " gid = " + testA.gid());
}
}
}
public static void parseConstructAnnotation(){
Constructor[] constructors = UserAnnotation.class.getConstructors();
for (Constructor constructor : constructors){
boolean hasAnnotation = constructor.isAnnotationPresent(TestA.class);
if (hasAnnotation){
TestA testA = (TestA)constructor.getAnnotation(TestA.class);
System.out.println("constructor = " + constructor.getName() + " id = " + testA.id() + " name = " + testA.name() + " gid = " + testA.gid());
}
}
}
public static void main(String[] args) throws ClassNotFoundException{
parseTypeAnnotation();
parseMethodAnnotation();
parseConstructAnnotation();
}
}
</span>
执行结果:
<span style="font-size:10px;">id = 0 name = type gid = class java.lang.Long
method = c id = 5 name = private method gid = class java.lang.Long
method = b id = 4 name = protected method gid = class java.lang.Long
method = a id = 3 name = public method gid = class java.lang.Long
constructor = annotation.UserAnnotation id = 2 name = construct gid = class java.lang.Long</span>