Java注解

揭开Java注解的面纱

什么是注解?

从JDK1.5被引入到Java,它提供了一种机制,这种机制允许在编写代码的同时可以直接编写元数据。

介绍

注解就是代码的元数据,它们包含了代码自身的信息。
注解可以被用在包、类、方法、变量、参数上。
自Java8开始,有一种注解几乎可以被放在代码的任何位置,叫做类型注解。
被注解的代码并不会直接被注解影响。这只会向第三系统提供关于自己的信息以用于不同的需求。
注解会被编译至class文件中,而且在运行时被处理程序提取出来用于业务逻辑。
当然,创建在运行时不可用的注解也是可能的,甚至可以创建只在源文件中可用,在编译时不可用的注解。

消费器

理解注解的目的以及如何使用它都会带来困难,因为注解本身并不包含任何功能逻辑,它们也不会影响自己注解的代码,那么,它们到底为什么而存在呢?
注解消费器,它们是利用被注解代码并根据注解信息产生不同行为的系统或者应用程序。
例如,在Java自带的内建注解(元注解)中,消费器是执行被注解代码的JVM。
还有其他,如JUnit,消费器是读取,分析被注解代码的Junit处理程序,它还可以决定测试单元和方法执行顺序。
消费器使用Java中的反射机制来读取和分析被注解的源代码。

注解语法和元素

声明一个注解需要使用”@”作为前缀,这便向编译器说明,该元素为注解。

1
2
3
4
@Annotation
public void annotatedMethod(){
...
}

上述的注解名称为Annotation,它正在注解annotatedMethod方法。
编译器会处理它。注解可以以键值对的形式持有有很多元素,即注解的属性:

1
2
3
4
5
6
7
@Annoation(
info = "I am an annotation",
counter = "55"
)
public void annotatedMethod(){
...
}

如果注解只包含一个元素(或者只需要指定一个元素的值,其它使用默认),可以像这样声明:

1
2
3
4
@Annotation("I am annotation")
public void annotatedMethod(){
...
}

如果没有元素需要被指定,则不需要括号。多个注解可以使用在同一代码上,例如类:

1
2
3
@Annoation(info = "U a u O")
@Annoation2
class AnnotatedClass{...}

在什么地方使用

注解基本上可以在Java程序的每一个元素上使用:类、域、方法、包、变量,等待。
自Java8,诞生了通过类型注解的理念。
在此之前,注解是限于在前面讨论的元素的声明上使用。
从此,无论是类型哈市声明都可以使用注解,就像:

1
@MyAnnotation String str = "danibuiza";

使用案例

注解可以满足许多要求,最普遍的是:

  • 向编译器提供信息:注解可以被编译器用来根据不同的规则产生警告,甚至错误。一个例子是Java8中@FunctionalInterface注解,这个注解使得编译器校验被注解的类,检查它是否是一个正确的函数式接口。
  • 文档:注解可以被软件应用程序计算代码的质量例如:FindBugs、PMD或者自动生成报告,例如:用来Jenkins、Jira、Teamcity。
  • 代码生成:注解可以使用代码中展示的元数据信息来自动生成代码或者xml文件,一个不错的例子是JAXB。
  • 运行时处理:在运行时检查的注解可以用做不同的目的,像单元测试(Junit),依赖注入(Spring),校验,日志(Log4j),数据访问(Hibernate)等等。

内建注解

Java语言自带了一系列的注解。
这个清单只涉及了Java语言最核心的包,未包含标准JRE中所有包和库如JAXB或Servlet规范。
以下讨论到的注解中有一些被称之为Meta注解,它们的目的注解其他注解,并且包含关于其他注解的信息。
@Retention:这个注解在其他注解上,并用来说明如何存储已被标记的注解。这是一种元注解,用来标记注解并提供注解的信息。可能的值是:

  • SOURCE:表明这个注解会被编译器忽略,并只会保留在源代码中。
  • CLASS:表明这个注解会通过编译驻留在CLASS文件,但会被JVM在运行时忽略,正因为如此,其在运行时不可见。
  • RUNTIME:表示这个注解会被JVM获取,并在运行时通过反射获取。

@Target:这个注解用于限制某个元素可以被注解的类型。例如:

  • ANNOTATION_TYPE 表示该注解可以应用到其他注解上
  • CONSTRUCTOR 表示可以使用到构造器上
  • FIELD 表示可以使用到域或属性上
  • LOCAL_VARIABLE表示可以使用到局部变量上
  • METHOD可以使用到方法级别的注解上
  • PACKAGE可以使用到包声明上
  • PARAMETER可以使用到方法的参数上
  • TYPE可以使用到一个类的任何元素上

@Documented:被注解的元素将会作为Javadoc产生的文档中的内容。
注解都默认不会成为文档中的内容。
这个注解可以对其它注解使用。
@Inherited:在默认情况下,注解不会被子类继承。
被此注解会被所有子类继承。这个注解可以对类使用
@Deprecated:说明被标记的元素不应该再度使用。
这个注解会让编译器产生警告消息。
可以使用到方法,类和域上。
相应的解释和原因,包括另一个可取代的方法应该同时和这个注解使用。
@SuppressWarnings:说明编译器不会针对指定的一个或多个原因产生警告。
例如:如果我们不想因为存在尚未使用的私有方法而得到警告可以这样做。

1
2
3
4
@SuppressWarnings("unused")
private String myNotUsedMethod(){
...
}

通常,编译器会因为没调用该方法而产生警告;用了注解印制了这种行为。该注解需要一个或多个参数来指定抑制的警告类型。
@Override:向编译器说明被注解元素是重写的父类的一个元素。在重写父类元素的时候此注解并非强制性的,不过可以在重写错误时帮助编译器产生错误以提醒我们。
@SafeVarargs:断言方法或者构造器的代码不会对参数进行不安全的操作。在Java的后续版本中,使用这个注解时将会令编译器产生一个错误在编译期间防止潜在的不安全操作。

Java 8与注解

Java8带来了一些优势,同样注解框架的能力也得到了提升。
@Repeatable注解,关于类型注解的声明,函数式接口注解@FunctionInterface(与Lambda结合使用)。
*@Repeatable:说明该注解标识的注解可以多次使用到同一个元素的声明上。
看一个使用的例子。首先我们创造一个能容纳重复的注解的容器:

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface RepeatedValues{
CanBeRepeated[] value();
}

接着,创建注解本身,然后标记@Repeatable

1
2
3
4
5
6
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
@Repeatable(RepeatedValues.class)
public @interface CanBeRepeated{
String value();
}

最后,我们可以这样重复地使用:

1
2
3
4
5
6
7
@CanBeRepeated ("the color is green")
@CanBeRepeated ("the color is red")
@CanBeRepeated ("the color is blue")
public class RepeatableAnnotated
{


}

如果我们尝试去掉@Repeatable

1
2
3
4
5
6
7
8
9
10
11
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface CannotBeRepeated
{
String value();
}
@CanBeRepeated("info")
public class RepeatableAnnotatedWrong
{


}

自Java8开始,我们可以在类型上使用注解。
由于我们在任何地方都可以使用类型,包括new操作符,casting,implements,throw等等。
注解可以改善对Java代码的分析并且保证更加健壮的类型检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@SuppressWarnings("unused")
pubilc static void main(String[] args){
//type def
@TypeAnnotated
String cannotBeEmpty = null;

//type
List<@TypeAnnotated String> myList = new ArrayList<String>();

//values
String myString = new @TypeAnnotated String("this is annotated in java 8");
}

public void methodAnnotated(@TypeAnnotated int parameter){
System.out.println("do nothing");
}

@FunctionalInterfce:这个注解表示一个函数式接口元素。
函数式接口是一种只有一个抽象方法(非默认)的接口。
编译器会检查被注解元素,如果不符,就会产生错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SuppressWarnings("unused")
MyCustomInterface myFuncInterface = new MyCustomInterface()
{
@Override
public int doSomething(int param){
return param * 10;
}
};

@SuppressWarnings("unused")
MyCustomInterface myFuncInterfceLambdas = (x) -> (x * 10);
}

@FunctionalInterface
interface MyCustomInterface
{

int doSomething(int param);
}

这个注解可以被使用到类、接口、枚举和注解本身。
它的被JVM保留并在runtime可见,这个是它的声明:

1
2
3
4
@Documented
@Retention(value = RUNTIME)
@Target(value = TYPE)
public @interface FunctionalInterface

自定义注解

首先,定义一个注解:

1
public @interface CustomAnnotationClass

这样创建了一个新的注解类型名为CustomAnnotationClass。
关键字@interface说明这是一个自定义注解的定义。

之后,你需要为此注解定义一对强制性的属性,保留策略和目标。
还有一些其他属性可以定义,不过两个是最基本和重要的。
所以,我们为自定义的注解设置属性:

1
2
3
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CustomAnnotationClass implements CustomAnnotationMethod

在保留策略中RUNTIME告诉编译器这个注解应该被JVM保留,并且能通过反射在运行时分析。
通过TYPE我们又设置该注解可以被使用到任何类的元素上。
之后,我们定义两个注解的成员:

1
2
3
4
5
6
7
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CustomAnnotationClass
{
public String author() default "xxxx";
public String date();
}

以上我们仅定义了默认值为’xxxx’的author属性和没有默认值的date属性。
我们应强调所有的方法声明都不能有参数和throw语句。
这个返回值的类型被限制为之前提过的字符串、类、枚举,注解和存储这些类型的数组。
现在我们可以像这样使用刚创建的自定义注解:

1
2
3
4
5
@CustomAnnotationClass(date = "2014-05-05")
public class AnnotatedClass
{

...
}

在另一种类似的用法中我们可以创建一种注解方法的注解,使用Target METHOD:

1
2
3
4
5
6
7
8
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CustomAnnotationMethod
{
public String author() default "xxxx";
public String date();
public String description();
}

这种注解可以使用在方法声明上:

1
2
3
4
5
6
7
8
9
@CustomAnnotationMethod(date = "2016-07-21", descripton = "annotated method")
public String annotatedMethod(){
return "nothing niente";
}
@CustomAnnotationMethod(author = "Jimmy", date = "2016-07-21", description = "annotated method")
public String annotatedMethodFromAFriend()
{

return "nothing niente";
}

有很多其他属性可以用在自定义注解上,但是目标(Target)和保留策略(Retention Policy)是最重要的两个。

获取注解

Java反射API包含了许多方法在运行时从类、方法或者其它元素获取注解。
接口AnnotatedElement包含了大部分重要的方法,如下:

  • getAnnotations(): 返回该元素的所有注解,包括没有显示定义该元素上的注解。
  • isAnnotationPresent(annotation):检查传入的注解是否存在于当前元素。
  • getAnnotation(class):按照传入的参数获取指定类型的注解。返回null说明当前元素不带有此注解。
    class通过java.lang.Class被实现,java.lang.reflect.Method和java.lang.reflect.Field,所以可以基本上被和任何Java元素使用。
    现在写一个程序,从一个类和它的方法中读取所有的存在的注解:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public static void main(String[] args) throws Exception{
    Class<AnnotatedClass> object = AnnotatedClass.class;
    Annotation[] annotations = object.getAnnotations();
    for(Annotation annotation : annotations){
    System.out.println(annotation);
    }
    if(object.isAnnotationPresent(CustomAnnotationClass.class)){
    Annotation annotation = object.getAnnotation(CustomAnnotationClass.class);
    System.out.println(annotation);
    }
    for( Method method : object.getDeclatredMethods()){
    if(method.isAnnotationPresent(CustomAnnnotationMethod.class)){
    Annotation annotation = method.getAnnotation(CustomAnnotionMethod.class);
    System.out.println(annotation);
    }
    }

    }

在这个程序中,可以看到getAnnotations()方法来获取所有某个对象(方法、类)上的所有注解的用法。
展示了怎样使用isAnnotationPresent()方法和getAnnotation()方法检查是否存在特定的注解,和如何获取它。