本文转发自:
本文目录:
1、理解Java注解
2、基本语法
2.1、声明注解与元注解
2.2、注解元素及其数据类型
2.3、编译器对默认值的限制
2.4、注解不支持继承
2.5、快捷方式
2.6、Java 内置注解与其它元注解
3、注解与反射机制
4、运行时注解处理器
5、Java 8 中注解增强
5.1、元注解 @Repeatable
5.2、新增的两种 ElementType
还有一篇不错的注解文章,推荐阅读:一小时搞明白自定义注解(Annotation)
Java 注解是在 JDK5 时引入的新特性,鉴于目前大部分框架(如:Spring)都使用了注解简化代码并提高编码的效率,因此掌握并深入理解注解对于一个 Java 工程师是来说是很有必要的事。本篇我们将通过以下几个角度来分析注解的相关知识点:
1、理解Java注解
实际上 Java 注解与普通修饰符(public、static、void 等)的使用方式并没有多大区别,下面的例子是常见的注解:
public class AnnotationDemo {
// @Test注解修饰方法A
@Test
public static void A(){
System.out.println("Test.....");
}
// 一个方法上可以拥有多个不同的注解
@Deprecated
@SuppressWarnings("uncheck")
public static void B(){
}
}
通过在方法上使用 @Test 注解后,在运行该方法时,测试框架会自动识别该方法并单独调用,@Test 实际上是一种标记注解,起标记作用,运行时告诉测试框架该方法为测试方法。而对于 @Deprecated 和 @SuppressWarnings(“uncheck”),则是 Java 本身内置的注解,在代码中,可以经常看见它们,但这并不是一件好事,毕竟当方法或是类上面有 @Deprecated 注解时,说明该方法或是类都已经过期不建议再用,@SuppressWarnings 则表示忽略指定警告,比如@SuppressWarnings(“uncheck”),这就是注解的最简单的使用方式,那么下面我们就来看看注解定义的基本语法。
2、基本语法
2.1、声明注解与元注解
我们先来看看前面的 Test 注解是如何声明的:
// 声明Test注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}
我们使用了 @interface 声明了 Test 注解,并使用 @Target 注解传入 ElementType.METHOD 参数来标明 @Test 只能用于方法上,@Retention(RetentionPolicy.RUNTIME) 则用来表示该注解生存期是运行时。从代码上看注解的定义很像接口的定义,确实如此,毕竟在编译后也会生成 Test.class 文件。对于 @Target 和 @Retention 是由 Java 提供的元注解,所谓元注解就是标记其他注解的注解,下面分别介绍:
@Target
用来约束注解可以应用的地方(如方法、类或字段),其中 ElementType 是枚举类型,其定义如下,也代表可能的取值范围:
public enum ElementType {
/**标明该注解可以用于类、接口(包括注解类型)或enum声明*/
TYPE,
/** 标明该注解可以用于字段(域)声明,包括enum实例 */
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})
@Retention
用来约束注解的生命周期,分别有三个值:源码级别(source),类文件级别(class)或者 运行时级别(runtime),其含有如下:
1、SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的 class 文件里);
2、CLASS:注解在 class 文件中可用,但会被 JVM 丢弃(该类型的注解信息会保留在源码里和 class 文件里,在执行的时候,不会加载到虚拟机中)。请注意:当注解未定义 Retention 值时,默认值是:CLASS,如 Java 内置注解,@Override、@Deprecated、@SuppressWarnning 等;
3、RUNTIME:注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class 文件和执行的时候都有注解的信息),如 SpringMvc 中的 @Controller、@Autowired、@RequestMapping 等。
2.2、注解元素及其数据类型
通过上述对 @Test 注解的定义,我们了解了注解定义的过程,由于 @Test 内部没有定义其他元素,所以 @Test 也称为标记注解(marker annotation)。但在自定义注解中,一般都会包含一些元素以表示某些值,方便处理器使用,这点在下面的例子将会看到:
// 对应数据表注解
@Target(ElementType.TYPE) // 只能应用于类上
@Retention(RetentionPolicy.RUNTIME) // 保存到运行时
public @interface DBTable {
String name() default "";
}
上述定义一个名为 DBTable 的注解,该用于主要用于数据库表与 Bean 类的映射(稍后会有完整案例分析)。与前面Test 注解不同的是,我们声明一个 String 类型的 name 元素,其默认值为空字符,但是必须注意到对应任何元素的声明应采用方法的声明方式,同时可选择使用 default 提供默认值,@DBTable 使用方式如下:
// 在类上使用该注解
@DBTable(name = "MEMBER")
public class Member {
//.......
}
关于注解支持的元素数据类型除了上述的 String,还支持如下数据类型:
1、所有基本类型(int, float, boolean, byte, double, char, long, short)
2、String
3、Class
4、enum
5、Annotation
6、上述类型的数组
倘若使用了其他数据类型,编译器将会丢出一个编译错误。注意,声明注解元素时可以使用基本类型但不允许使用任何包装类型,同时还应该注意到注解也可以作为元素的类型,也就是嵌套注解。下面的代码演示了上述类型的使用过程:
package com.zju.annotationdemo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 数据类型使用Demo
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Reference{
boolean next() default false;
}
public @interface AnnotationElementDemo {
// 枚举类型
enum Status {FIXED,NORMAL};
// 声明枚举
Status status() default Status.FIXED;
// 布尔类型
boolean showSupport() default false;
// String类型
String name()default "";
// class类型
Class<?> testCase() default Void.class;
// 注解嵌套
Reference reference() default @Reference(next=true);
// 数组类型
long[] value();
}
2.3、编译器对默认值的限制
编译器对元素的默认值有些过分挑剔。首先,元素不能有不确定的值。也就是说,元素必须要么具有默认值,要么在使用注解时提供元素的值。其次,对于非基本类型的元素,无论是在源代码中声明,还是在注解接口中定义默认值,都不能以null 作为值,这就是限制,没有什么利用可言,但造成一个元素的存在或缺失状态,因为每个注解的声明中,所有的元素都存在,并且都具有相应的值,为了绕开这个限制,只能定义一些特殊的值,例如空字符串或负数,表示某个元素不存在。
2.4、注解不支持继承
注解是不支持继承的,因此不能使用关键字 extends 来继承某 个@interface,但注解在编译后,编译器会自动继承java.lang.annotation.Annotation 接口,这里我们反编译前面定义的 DBTable 注解:
package com.zju.annotationdemo;
import java.lang.annotation.Annotation;
// 反编译后的代码
public interface DBTable extends Annotation{
public abstract String name();
}
虽然反编译后发现 DBTable 注解继承了 Annotation 接口,请记住,即使 Java 的接口可以实现多继承,但定义注解时依然无法使用 extends 关键字继承 @interface。
2.5、快捷方式
所谓的快捷方式就是注解中定义了名为 value 的元素,并且在使用该注解时,如果该元素是唯一需要赋值的一个元素,那么此时无需使用 key = value 的语法,而只需在括号内给出 value 元素所需的值即可。这可以应用于任何合法类型的元素,记住,这限制了元素名必须为 value,简单案例如下:
package com.zju.annotationdemo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface IntegerVaule{
int value() default 0;
String name() default "";
}
// 使用注解
public class QuicklyWay {
// 当只想给value赋值时,可以使用以下快捷方式
@IntegerVaule(20)
public int age;
// 当name也需要赋值时必须采用key=value的方式赋值
@IntegerVaule(value = 10000, name = "MONEY")
public int money;
}
2.6、Java内置注解与其它元注解
接着看看 Java 提供的内置注解,主要有 3 个,如下:
@Override
用于标明此方法覆盖了父类的方法,源码如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Deprecated
用于标明已经过时的方法或类,源码如下,关于 @Documented 稍后分析:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
@SuppressWarnnings
用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告,其实现源码如下:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
其内部有一个 String 数组,主要接收值如下:
deprecation:使用了不赞成使用的类或方法时的警告;
unchecked:执行了未检查的转换时的警告,例如:当使用集合时没有用泛型 (Generics) 来指定集合保存的类型;
fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
path:在类路径、源文件路径等中有不存在的路径时的警告;
serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
finally:任何 finally 子句不能正常完成时的警告;
all:关于以上所有情况的警告。
这个三个注解比较简单,看个简单案例即可:
// 注明该类已过时,不建议使用
@Deprecated
class A{
public void A(){ }
// 注明该方法已过时,不建议使用
@Deprecated()
public void B(){ }
}
class B extends A{
@Override // 标明覆盖父类A的A方法
public void A() {
super.A();
}
// 去掉检测警告
@SuppressWarnings({"uncheck","deprecation"})
public void C(){ }
// 去掉检测警告
@SuppressWarnings("uncheck")
public void D(){ }
}
前面我们分析了两种元注解,@Target 和 @Retention,除了这两种元注解,Java还提供了另外两种元注解,@Documented 和 @Inherited,下面分别介绍:
@Documented
被修饰的注解会生成到 javadoc 中。
@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 A(){
}
}
使用 javadoc 命令生成文档:
zejian@zejiandeMBP annotationdemo$ javadoc DocumentDemo.java DocumentA.java DocumentB.java
如下:
可以发现使用 @Documented 元注解定义的注解(@DocumentA)将会生成到 javadoc 中,而 @DocumentB 则没有在doc 文档中出现,这就是元注解 @Documented 的作用。
@Inherited
可以让注解被继承,但这并不是真的继承,只是通过使用 @Inherited,可以让子类 Class 对象使用 getAnnotations() 获取父类被 @Inherited 修饰的注解,如下:
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentA {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentB {
}
@DocumentA
class A{ }
class B extends A{ }
@DocumentB
class C{ }
class D extends C{ }
// 测试
public class DocumentDemo {
public static void main(String... args){
A instanceA = new B();
System.out.println("已使用的@Inherited注解:" + Arrays.toString(instanceA.getClass().getAnnotations()));
C instanceC = new D();
System.out.println("没有使用的@Inherited注解:" + Arrays.toString(instanceC.getClass().getAnnotations()));
}
/**
* 运行结果:
*已使用的@Inherited注解:[@com.zejian.annotationdemo.DocumentA()]
*没有使用的@Inherited注解:[]
*/
}
3、注解与反射机制
前面经过反编译后,我们知道 Java 所有注解都继承了 Annotation 接口,也就是说 Java 使用 Annotation 接口代表注解元素,该接口是所有 Annotation 类型的父接口。那我们就先看下 Annotation 接口的源码:
- Annotation 接口的源码
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
// 注解的类型
Class<? extends Annotation> annotationType();
}
同时为了运行时能准确获取到注解的相关信息,Java 在 java.lang.reflect 反射包下新增了 AnnotatedElement 接口,它主要用于表示目前正在 JVM 中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,如反射包的 Constructor 类、Field 类、Method 类、Package 类和 Class 类都实现了 AnnotatedElement 接口,它简要含义如下(更多详细介绍可以看 深入理解 Java 类型信息 (Class对象)与反射机制):
Class:类的Class对象定义
Constructor:代表类的构造器定义
Field:代表类的成员变量定义
Method:代表类的方法定义
Package:代表类的包定义
- AnnotatedElement 接口的源码
package java.lang.reflect;
import java.lang.annotation.Annotation;
public interface AnnotatedElement {
// 如果指定类型的注解存在于此元素上,则返回 true,否则返回 false
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
// 该元素如果存在指定类型的注解,则返回这些注解,否则返回 null
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
// 返回此元素上存在的所有注解,包括从父类继承的
Annotation[] getAnnotations();
// 返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;
// 这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组
Annotation[] getDeclaredAnnotations();
}
Constructor 类、Field 类、Method 类、Package 类和 Class 类)都实现以下的方法:
简单案例演示如下:
package com.zejian.annotationdemo;
import java.lang.annotation.Annotation;
import java.util.Arrays;
@DocumentA
class A{ }
// 继承了A类
@DocumentB
public class DocumentDemo extends A{
public static void main(String... args){
Class<?> clazz = DocumentDemo.class;
// 根据指定注解类型获取该注解
DocumentA documentA = clazz.getAnnotation(DocumentA.class);
System.out.println("A:" + documentA);
// 获取该元素上的所有注解,包含从父类继承
Annotation[] an = clazz.getAnnotations();
System.out.println("an:" + Arrays.toString(an));
// 获取该元素上的所有注解,但不包含继承!
Annotation[] an2 = clazz.getDeclaredAnnotations();
System.out.println("an2:"+ Arrays.toString(an2));
// 判断注解DocumentA是否在该元素上
boolean b = clazz.isAnnotationPresent(DocumentA.class);
System.out.println("b:" + b);
/**
* 执行结果:
A:@com.zejian.annotationdemo.DocumentA()
an:[@com.zejian.annotationdemo.DocumentA(), @com.zejian.annotationdemo.DocumentB()]
an2:@com.zejian.annotationdemo.DocumentB()
b:true
*/
}
}
4、运行时注解处理器
了解完注解与反射的相关 API 后,现在通过一个实例(该例子是博主改编自《Tinking in Java》)来演示利用运行时注解来组装数据库 SQL 的构建语句的过程:
@Target(ElementType.TYPE) // 只能应用于类上
@Retention(RetentionPolicy.RUNTIME) // 保存到运行时
public @interface DBTable {
String name() default "";
}
// 注解Integer类型的字段
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
// 该字段对应数据库表列名
String name() default "";
// 嵌套注解
Constraints constraint() default @Constraints;
}
// 注解String类型的字段
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
// 对应数据库表的列名
String name() default "";
// 列类型分配的长度,如varchar(30)的30
int value() default 0;
Constraints constraint() default @Constraints;
}
// 约束注解
@Target(ElementType.FIELD) // 只能应用在字段上
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
// 判断是否作为主键约束
boolean primaryKey() default false;
// 判断是否允许为null
boolean allowNull() default false;
// 判断是否唯一
boolean unique() default false;
}
// 数据库表Member对应实例类bean
@DBTable(name = "MEMBER")
public class Member {
// 主键ID
@SQLString(name = "ID", value = 50, constraint = @Constraints(primaryKey = true))
private String id;
@SQLString(name = "NAME" , value = 30)
private String name;
@SQLInteger(name = "AGE")
private int age;
@SQLString(name = "DESCRIPTION" ,value = 150 , constraint = @Constraints(allowNull = true))
private String description; // 个人描述
// 省略set get.....
}
上述定义4个注解,分别是 @DBTable(用于类上)、@Constraints(用于字段上)、 @SQLString(用于字段上)、@SQLString(用于字段上) 并在 Member 类中使用这些注解,这些注解的作用的是用于帮助注解处理器生成创建数据库表MEMBER 的构建语句。在这里有点需要注意的是,我们使用了嵌套注解 @Constraints,该注解主要用于判断字段是否为 null 或者字段是否唯一。
必须清楚认识到上述提供的注解生命周期必须为 @Retention(RetentionPolicy.RUNTIME),即运行时,这样才可以使用反射机制获取其信息。有了上述注解和使用,剩余的就是编写上述的注解处理器了,前面我们聊了很多注解,其处理器要么是Java 自身已提供、要么是框架已提供的,我们自己都没有涉及到注解处理器的编写,但上述定义处理 SQL 的注解,其处理器必须由我们自己编写了,如下:
package com.zejian.annotationdemo;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
// 运行时注解处理器,构造表创建语句
public class TableCreator {
public static String createTableSql(String className) throws ClassNotFoundException {
Class<?> cl = Class.forName(className);
DBTable dbTable = cl.getAnnotation(DBTable.class);
// 如果没有表注解,直接返回
if(dbTable == null) {
System.out.println("No DBTable annotations in class " + className);
return null;
}
String tableName = dbTable.name();
// If the name is empty, use the Class name:
if(tableName.length() < 1)
tableName = cl.getName().toUpperCase();
List<String> columnDefs = new ArrayList<String>();
// 通过Class类API获取到所有成员字段
for(Field field : cl.getDeclaredFields()) {
String columnName = null;
// 获取字段上的注解
Annotation[] anns = field.getDeclaredAnnotations();
if(anns.length < 1)
continue; // Not a db table column
//判断注解类型
if(anns[0] instanceof SQLInteger) {
SQLInteger sInt = (SQLInteger) anns[0];
// 获取字段对应列名称,如果没有就是使用字段名称替代
if(sInt.name().length() < 1)
columnName = field.getName().toUpperCase();
else
columnName = sInt.name();
// 构建语句
columnDefs.add(columnName + " INT" + getConstraints(sInt.constraint()));
}
// 判断String类型
if(anns[0] instanceof SQLString) {
SQLString sString = (SQLString) anns[0];
// Use field name if name not specified.
if(sString.name().length() < 1)
columnName = field.getName().toUpperCase();
else
columnName = sString.name();
columnDefs.add(columnName + " VARCHAR(" + sString.value() + ")" +
getConstraints(sString.constraint()));
}
}
// 数据库表构建语句
StringBuilder createCommand = new StringBuilder("CREATE TABLE " + tableName + "(");
for(String columnDef : columnDefs)
createCommand.append("\n " + columnDef + ",");
// Remove trailing comma
String tableCreate = createCommand.substring(0, createCommand.length() - 1) + ");";
return tableCreate;
}
/**
* 判断该字段是否有其他约束
* @param con
* @return
*/
private static String getConstraints(Constraints con) {
String constraints = "";
if(!con.allowNull())
constraints += " NOT NULL";
if(con.primaryKey())
constraints += " PRIMARY KEY";
if(con.unique())
constraints += " UNIQUE";
return constraints;
}
public static void main(String[] args) throws Exception {
String[] arg={"com.zejian.annotationdemo.Member"};
for(String className : arg) {
System.out.println("Table Creation SQL for " + className + " is :\n" + createTableSql(className));
}
/**
* 输出结果:
Table Creation SQL for com.zejian.annotationdemo.Member is :
CREATE TABLE MEMBER(
ID VARCHAR(50) NOT NULL PRIMARY KEY,
NAME VARCHAR(30) NOT NULL,
AGE INT NOT NULL,
DESCRIPTION VARCHAR(150)
);
*/
}
}
如果对反射比较熟悉的同学,上述代码就相对简单了,我们通过传递 Member 的全路径后通过 Class.forName() 方法获取到 Member 的 Class 对象,然后利用 Class 对象中的方法获取所有成员字段 Field,最后利用 field.getDeclaredAnnotations() 遍历每个 Field 上的注解再通过注解的类型判断来构建建表的 SQL 语句。
这便是利用注解结合反射来构建 SQ L语句的简单的处理器模型,是否已回想起 Hibernate?
5、Java 8 中注解增强
5.1、元注解 @Repeatable
元注解 @Repeatable 是 JDK1.8 新加入的,它表示在同一个位置重复相同的注解。在没有该注解前,一般是无法在同一个类型上使用相同的注解的。
// Java8前无法这样使用
@FilterPath("/web/update")
@FilterPath("/web/add")
public class A {}
Java8 前如果是想实现类似的功能,我们需要在定义 @FilterPath 注解时定义一个数组元素接收多个值如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface FilterPath {
String [] value();
}
// 使用
@FilterPath({"/update","/add"})
public class A { }
但在 Java8 新增了 @Repeatable 注解后就可以采用如下的方式定义并使用了。
package com.zejian.annotationdemo;
import java.lang.annotation.*;
// 使用Java8新增@Repeatable原注解
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(FilterPaths.class) // 参数指明接收的注解class
public @interface FilterPath {
String value();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface FilterPaths {
FilterPath[] value();
}
// 使用案例
@FilterPath("/web/update")
@FilterPath("/web/add")
@FilterPath("/web/delete")
class AA{ }
我们可以简单理解为通过使用 @Repeatable 后,将使用 @FilterPaths 注解作为接收同一个类型上重复注解的容器,而每个 @FilterPath 则负责保存指定的路径串。
为了处理上述的新增注解,Java8 还在 AnnotatedElement 接口新增了 getDeclaredAnnotationsByType() 和 getAnnotationsByType() 两个方法并在接口给出了默认实现,在指定 @Repeatable 的注解时,可以通过这两个方法获取到注解相关信息。但请注意,旧版 API 中的 getDeclaredAnnotation() 和 getAnnotation() 是不对 @Repeatable 注解的处理的(除非该注解没有在同一个声明上重复出现)。
注意 getDeclaredAnnotationsByType 方法获取到的注解不包括父类,其实当 getAnnotationsByType() 方法调用时,其内部先执行了 getDeclaredAnnotationsByType 方法,只有当前类不存在指定注解时,getAnnotationsByType() 才会继续从其父类寻找。但请注意如果 @FilterPath 和 @FilterPaths 没有使用 @Inherited 的话,仍然无法获取。下面通过代码来演示:
// 使用Java8新增@Repeatable原注解
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(FilterPaths.class)
public @interface FilterPath {
String value();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface FilterPaths {
FilterPath[] value();
}
@FilterPath("/web/list")
class CC { }
// 使用案例
@FilterPath("/web/update")
@FilterPath("/web/add")
@FilterPath("/web/delete")
class AA extends CC{
public static void main(String[] args) {
Class<?> clazz = AA.class;
// 通过getAnnotationsByType方法获取所有重复注解
FilterPath[] annotationsByType = clazz.getAnnotationsByType(FilterPath.class);
FilterPath[] annotationsByType2 = clazz.getDeclaredAnnotationsByType(FilterPath.class);
if (annotationsByType != null) {
for (FilterPath filter : annotationsByType) {
System.out.println("1:" + filter.value());
}
}
System.out.println("-----------------");
if (annotationsByType2 != null) {
for (FilterPath filter : annotationsByType2) {
System.out.println("2:" + filter.value());
}
}
System.out.println("使用getAnnotation的结果:" + clazz.getAnnotation(FilterPath.class));
/**
* 执行结果(当前类拥有该注解FilterPath,则不会从CC父类寻找)
1:/web/update
1:/web/add
1:/web/delete
-----------------
2:/web/update
2:/web/add
2:/web/delete
使用getAnnotation的结果:null
*/
}
}
从执行结果来看如果当前类拥有该注解 @FilterPath,则 getAnnotationsByType 方法不会从 CC 父类寻找,下面看看另外一种情况,即 AA 类上没有 @FilterPath 注解:
// 使用Java8新增@Repeatable原注解
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited // 添加可继承元注解
@Repeatable(FilterPaths.class)
public @interface FilterPath {
String value();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited // 添加可继承元注解
@interface FilterPaths {
FilterPath[] value();
}
@FilterPath("/web/list")
@FilterPath("/web/getList")
class CC { }
// AA上不使用@FilterPath注解,getAnnotationsByType将会从父类查询
class AA extends CC{
public static void main(String[] args) {
Class<?> clazz = AA.class;
// 通过getAnnotationsByType方法获取所有重复注解
FilterPath[] annotationsByType = clazz.getAnnotationsByType(FilterPath.class);
FilterPath[] annotationsByType2 = clazz.getDeclaredAnnotationsByType(FilterPath.class);
if (annotationsByType != null) {
for (FilterPath filter : annotationsByType) {
System.out.println("1:" + filter.value());
}
}
System.out.println("-----------------");
if (annotationsByType2 != null) {
for (FilterPath filter : annotationsByType2) {
System.out.println("2:" + filter.value());
}
}
System.out.println("使用getAnnotation的结果:" + clazz.getAnnotation(FilterPath.class));
/**
* 执行结果(当前类没有@FilterPath,getAnnotationsByType方法从CC父类寻找)
*1:/web/list
*1:/web/getList
*-----------------
*使用getAnnotation的结果:null
*/
}
}
注意定义 @FilterPath 和 @FilterPath 时必须指明 @Inherited,getAnnotationsByType 方法否则依旧无法从父类获取@FilterPath 注解,这是为什么呢,不妨看看 getAnnotationsByType 方法的实现源码:
// 接口默认实现方法
default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
// 先调用getDeclaredAnnotationsByType方法
T[] result = getDeclaredAnnotationsByType(annotationClass);
// 判断当前类获取到的注解数组是否为0
if (result.length == 0 && this instanceof Class &&
// 判断定义注解上是否使用了@Inherited元注解
AnnotationType.getInstance(annotationClass).isInherited()) { // Inheritable
// 从父类获取
Class<?> superClass = ((Class<?>) this).getSuperclass();
if (superClass != null) {
result = superClass.getAnnotationsByType(annotationClass);
}
}
return result;
}
5.2、新增的两种 ElementType
在 Java8 中 ElementType 新增两个枚举成员,TYPE_PARAMETER 和 TYPE_USE
// 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 ,通过它们,我们可以把注解应用到各种新场合中。