14.3.4 Java8新增的重复注解

14.3.4 Java8新增的重复注解

Java8以前,同一个程序元素前最多只能使用一个相同类型的注解;如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”。例如在Struts2开发中,有时需要在Action类上使用多个@Result注解。在Java8以前只能写成如下形式:

1
2
3
4
5
@Result({
@Result(name="success",location="failed.jsp"),
@Result(name="success", location="CC.jsp")
})
public Acton FooAction{...}

上面代码中使用了两个@Result注解,但由于传统Java语法不允许多次使用@Result修饰同一个类,因此程序必须使用@Results注解作为两个@Result的容器,@Results注解包含一个类型为Result[]的成员变量value,程序指定的多个@Result将作为@Resultsvalue属性(数组类型)的数组元素

Java8开始,上面语法可以得到简化:Java8允许使用多个相同类型的注解来修饰同一个类,因此上面代码可能(之所以说可能,是因为重复注解还需要对原来的注解进行改造)可以简化为如下形式:

1
2
3
@Result(name="success",location="failed.jsp"),
@Result(name="success", location="CC.jsp")
public Acton FooAction{...}

开发重复注解需要使用@Repeatable修饰,下面通过示例来示范如何开发重复注解。首先定义一个FKTag注解。

1
2
3
4
5
6
7
8
9
10
11
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(FkTags.class)
public @interface FkTag {
// 为锟斤拷注锟解定锟斤拷2锟斤拷锟斤拷员锟斤拷锟斤拷
String name() default "锟斤拷锟斤拷锟斤拷锟�";

int age();
}

上面定义了FKTag注解,该注解包含两个成员变量。但该注解默认不能作为重复注解使用,如果使用两个以上的该注解修饰同一个类,编译器会报错。
为了将该注解改造成重复注解,需要使用@Repeatable修饰该注解,使用@Repeatable时必须为value成员变量指定值,该成员变量的值应该是一个“容器”注解——该“容器”注解可包含多个@FkTag,因此还需要定义如下的“容器”注解

1
2
3
4
5
6
7
8
9
10
import java.lang.annotation.*;

// 指定该注解信息会保留到运行时
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FkTags
{
// 定义value成员变量,该成员变量可接受多个@FkTag注解
FkTag[] value();
}

上面代码中,定义了一个FkTag[]类型的value成员变量,这意味着@FKTags注解的value成员变量可接受多个@FkTag注解,因此@FkTags注解可作为@FkTag的容器
修饰@FkTags注解的的注解:

1
@Retention(RetentionPolicy.RUNTIME)

指定@FKTags注解信息也可保留到运行时,这是必需的,因为:@FKTag注解信息需要保留到运行时,如果@FKTags注解只能保留到源代码级别(RetentionPolicy.SOURCE)或类文件(RetentionPolicy.CLASS),将会导致@FKTags的保留期小于@FkTag的保留期,如果程序将多个@FkTag注解放入@FkTags中,若JVM丢弃了@FKTags注解,自然也就丢弃了@FKTags的信息——而我们希望@FKTags注解可以保留到运行时,这就矛盾了。

容器注解保留期要更长

“容器”注解的保留期必须比它所包含的注解的保留期更长,否则编译器会报错
接下来程序可在定义@FkTag注解时添加如下修饰代码:

1
@Repeatable(FkTags.class);

经过上面步骤,就成功地定义了一个重复注解:@FkTag。读者可能已经发现,实际上@FkTag依然有“容器”注解,因此依然可用传统代码来使用该注解:

1
2
3
4
@FkTags({
@FkTag(age=5),
@FkTag(name="疯狂Java",age=9)
})

又由于@FKTag是重复注解,因此可直接使用两个@FKTag注解,如下代码所示

1
2
@FkTag(age=5),
@FkTag(name="疯狂Java",age=9)

实际上,第二种用法只是一个简化写法,系统依然将两个@FkTag注解作为@FkTagsvalue成员变量的数组元素。

程序 容器注解的本质

如下程序演示了重复注解的本质。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@FkTag(age = 5)
@FkTag(name = "疯狂Java", age = 9)
// @FkTags({@FkTag(age=5),
// @FkTag(name="疯狂Java" , age=9)})
public class FkTagTest {
public static void main(String[] args) {
Class<FkTagTest> clazz = FkTagTest.class;
/*
* 使用Java 8新增的getDeclaredAnnotationsByType()方法获取 修饰FkTagTest类的多个@FkTag注解
*/
FkTag[] tags = clazz.getDeclaredAnnotationsByType(FkTag.class);
// 遍历修饰FkTagTest类的多个@FkTag注解
for (FkTag tag : tags) {
System.out.println(tag.name() + "-->" + tag.age());
}
/*
* 使用传统的getDeclaredAnnotation()方法获取 修饰FkTagTest类的@FkTags注解
*/
FkTags container = clazz.getDeclaredAnnotation(FkTags.class);
System.out.println(container);
}
}

上面程序中的

1
FkTag[] tags = clazz.getDeclaredAnnotationsByType(FkTag.class);

这行代码,获取修饰FkTagTest类的多个@FkTag注解,此行代码使用的是Java8新增的getDeclaredAnnotationsByType()方法,该方法的功能与传统的getDeclaredAnnotation(()方法相同,只不过getDeclaredAnnotationsByType()方法相当于功能增强版,它可以获取多个重复注解,而getDeclaredAnnotation()方法则只能获取一个(在Java8以前,不允许出现重复注解)
上面程序中的

1
FkTags container = clazz.getDeclaredAnnotation(FkTags.class);

这行代码尝试获取修饰FkTagTest类的@ FkTags注解,虽然上面源代码中并未显式使用@FKTags注解,但由于程序使用了两个@FkTag注解修饰该类,因此系统会自动将两个@FKTag注解作为@FKTagsvalue成员变量的数组元素处理。
因此,第二行粗体字代码将可以成功地获取到@FkTags注解。
编译、运行程序,可以看到如下输出:

1
2
3
4
PS G:\Desktop\随书源码\疯狂Java讲义(第4版)光盘\codes\14\14.3> java FkTagTest
疯狂软件-->5
疯狂Java-->9
@FkTags(value=[@FkTag(name=疯狂软件, age=5), @FkTag(name=疯狂Java, age=9)])

重复注解只是一种简化写法

重复注解只是一种简化写法,这种简化写法是一种假象:多个重复注解其实会被作为“容器”注解的value成员变量的数组元素。例如上面的重复的@FkTag注解其实会被作为@ ,FkTags注解的value成员变量的数组元素处理,