深入理解Java注解类型(@Annotation)
http://blog.csdn.net/javazejian/article/details/71860633
出自【zejian 的博客】
java 注解是在 JDK5 时引入的新特性,鉴于目前大部分框架 (如 Spring) 都使用了注解简化代码并提高编码的效率,因此掌握并深入理解注解对于一个 Java 工程师是来说是很有必要的事。本篇我们将通过以下几个角度来分析注解的相关知识点
-
理解 Java 注解
-
基本语法
-
声明注解与元注解
-
注解元素及其数据类型
-
编译器对默认值的限制
-
注解不支持继承
-
快捷方式
-
Java 内置注解与其它元注解
-
注解与反射机制
-
运行时注解处理器
-
Java 8 中注解增强
-
元注解 Repeatable
-
新增的两种 ElementType
理解 Java 注解
实际上 Java 注解与普通修饰符 (public、static、void 等) 的使用方式并没有多大区别,下面的例子是常见的注解:
public class AnnotationDemo {@Test </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> A(){ System.out.println(</span>"Test....."<span style="color: rgba(0, 0, 0, 1)">); } @Deprecated @SuppressWarnings(</span>"uncheck"<span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> B(){ }
}
通过在方法上使用 @Test 注解后,在运行该方法时,测试框架会自动识别该方法并单独调用,@Test 实际上是一种标记注解,起标记作用,运行时告诉测试框架该方法为测试方法。而对于 @Deprecated 和 @SuppressWarnings(“uncheck”),则是 Java 本身内置的注解,在代码中,可以经常看见它们,但这并不是一件好事,毕竟当方法或是类上面有 @Deprecated 注解时,说明该方法或是类都已经过期不建议再用,@SuppressWarnings 则表示忽略指定警告,比如 @SuppressWarnings(“uncheck”),这就是注解的最简单的使用方式,那么下面我们就来看看注解定义的基本语法
基本语法
声明注解与元注解
我们先来看看前面的 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 值时,则此注解可以用于任何元素之上,多个值使用 {} 包含并用逗号隔开,如下:
@Retention 用来约束注解的生命周期,分别有三个值,源码级别(source),类文件级别(class)或者运行时级别(runtime),其含有如下:
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
-
SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的 class 文件里)
-
CLASS:注解在 class 文件中可用,但会被 VM 丢弃(该类型的注解信息会保留在源码里和 class 文件里,在执行的时候,不会加载到虚拟机中),请注意,当注解未定义 Retention 值时,默认值是 CLASS,如 Java 内置注解,@Override、@Deprecated、@SuppressWarnning 等
-
RUNTIME:注解信息将在运行期 (JVM) 也保留,因此可以通过反射机制读取注解的信息(源码、class 文件和执行的时候都有注解的信息),如 SpringMvc 中的 @Controller、@Autowired、@RequestMapping 等。
注解元素及其数据类型
通过上述对 @Test 注解的定义,我们了解了注解定义的过程,由于 @Test 内部没有定义其他元素,所以 @Test 也称为标记注解(marker annotation),但在自定义注解中,一般都会包含一些元素以表示某些值,方便处理器使用,这点在下面的例子将会看到:
/** * Created by wuzejian on 2017/5/18. * 对应数据表注解 */ @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,还支持如下数据类型
所有基本类型(int,float,boolean,byte,double,char,long,short)
String
Class
enum
Annotation
上述类型的数组
倘若使用了其他数据类型,编译器将会丢出一个编译错误,注意,声明注解元素时可以使用基本类型但不允许使用任何包装类型,同时还应该注意到注解也可以作为元素的类型,也就是嵌套注解,下面的代码演示了上述类型的使用过程:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface Reference{ boolean next() default false; }public @interface AnnotationElementDemo {</span><span style="color: rgba(0, 0, 255, 1)">enum</span><span style="color: rgba(0, 0, 0, 1)"> Status {FIXED,NORMAL}; Status status() </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> Status.FIXED; </span><span style="color: rgba(0, 0, 255, 1)">boolean</span> showSupport() <span style="color: rgba(0, 0, 255, 1)">default</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">; String name()</span><span style="color: rgba(0, 0, 255, 1)">default</span> ""<span style="color: rgba(0, 0, 0, 1)">; Class</span><?> testCase() <span style="color: rgba(0, 0, 255, 1)">default</span> Void.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">; Reference reference() </span><span style="color: rgba(0, 0, 255, 1)">default</span> @Reference(next=<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 0, 255, 1)">long</span><span style="color: rgba(0, 0, 0, 1)">[] value();
}
编译器对默认值的限制
编译器对元素的默认值有些过分挑剔。首先,元素不能有不确定的值。也就是说,元素必须要么具有默认值,要么在使用注解时提供元素的值。其次,对于非基本类型的元素,无论是在源代码中声明,还是在注解接口中定义默认值,都不能以 null 作为值,这就是限制,没有什么利用可言,但造成一个元素的存在或缺失状态,因为每个注解的声明中,所有的元素都存在,并且都具有相应的值,为了绕开这个限制,只能定义一些特殊的值,例如空字符串或负数,表示某个元素不存在。
注解不支持继承
注解是不支持继承的,因此不能使用关键字 extends 来继承某个 @interface,但注解在编译后,编译器会自动继承 java.lang.annotation.Annotation 接口,这里我们反编译前面定义的 DBTable 注解
package com.zejian.annotationdemo; import java.lang.annotation.Annotation; public interface DBTable extends Annotation{ public abstract String name();}
虽然反编译后发现 DBTable 注解继承了 Annotation 接口,请记住,即使 Java 的接口可以实现多继承,但定义注解时依然无法使用 extends 关键字继承 @interface。
快捷方式
所谓的快捷方式就是注解中定义了名为 value 的元素,并且在使用该注解时,如果该元素是唯一需要赋值的一个元素,那么此时无需使用 key=value 的语法,而只需在括号内给出 value 元素所需的值即可。这可以应用于任何合法类型的元素,记住,这限制了元素名必须为 value,简单案例如下
@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME) @interface IntegerVaule{ int value() default 0; String name() default ""; }public class QuicklyWay {@IntegerVaule(</span>20<span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> age; @IntegerVaule(value </span>= 10000,name = "MONEY"<span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> money;
}
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:关于以上所有情况的警告。
这个三个注解比较简单,看个简单案例即可:
@Deprecatedclass A{ public void A(){ }@Deprecated() </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> B(){ }
}
class B extends A{
@Override </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> A() { </span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.A(); } @SuppressWarnings({</span>"uncheck","deprecation"<span style="color: rgba(0, 0, 0, 1)">}) </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> C(){ } @SuppressWarnings(</span>"uncheck"<span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> D(){ }
}
前面我们分析了两种元注解,@Target 和 @Retention,除了这两种元注解,Java 还提供了另外两种元注解,@Documented 和 @Inherited,下面分别介绍:
@Documented 被修饰的注解会生成到 javadoc 中
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentA {}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentB {}
@DocumentA
@DocumentB
public class DocumentDemo { public void A(){}}
使用 javadoc 命令生成文档:
如下:
可以发现使用 @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 {} @DocumentAclass A{ }class B extends A{}@DocumentBclass C{}
class D extends C{ }public class DocumentDemo {
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> main(String... args){ A instanceA</span>=<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> B(); System.out.println(</span>"已使用的@Inherited注解:"+<span style="color: rgba(0, 0, 0, 1)">Arrays.toString(instanceA.getClass().getAnnotations())); C instanceC </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> D(); System.out.println(</span>"没有使用的@Inherited注解:"+<span style="color: rgba(0, 0, 0, 1)">Arrays.toString(instanceC.getClass().getAnnotations())); } </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 运行结果: 已使用的@Inherited注解:[@com.zejian.annotationdemo.DocumentA()] 没有使用的@Inherited注解:[] </span><span style="color: rgba(0, 128, 0, 1)">*/</span>}</pre>
注解与反射机制
前面经过反编译后,我们知道 Java 所有注解都继承了 Annotation 接口,也就是说 Java 使用 Annotation 接口代表注解元素,该接口是所有 Annotation 类型的父接口。同时为了运行时能准确获取到注解的相关信息,Java 在 java.lang.reflect 反射包下新增了 AnnotatedElement 接口,它主要用于表示目前正在 VM 中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,如反射包的 Constructor 类、Field 类、Method 类、Package 类和 Class 类都实现了 AnnotatedElement 接口,它简要含义如下(更多详细介绍可以看 深入理解 Java 类型信息 (Class 对象) 与反射机制):
Class:类的 Class 对象定义Constructor:代表类的构造器定义
Field:代表类的成员变量定义
Method:代表类的方法定义
Package:代表类的包定义
下面是 AnnotatedElement 中相关的 API 方法,以上 5 个类都实现以下的方法
返回值 | 方法名称 | 说明 |
---|---|---|
<A extends Annotation> |
getAnnotation(Class<A> annotationClass) |
该元素如果存在指定类型的注解,则返回这些注解,否则返回 null。 |
Annotation[] |
getAnnotations() |
返回此元素上存在的所有注解,包括从父类继承的 |
boolean |
isAnnotationPresent(Class<? extends Annotation> annotationClass) |
如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。 |
Annotation[] |
getDeclaredAnnotations() |
返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为 0 的数组 |
简单案例演示如下:
@DocumentAclass A{ } @DocumentBpublic class DocumentDemo extends A{</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> main(String... args){ Class</span><?> clazz = DocumentDemo.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">; DocumentA documentA</span>=clazz.getAnnotation(DocumentA.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">); System.out.println(</span>"A:"+<span style="color: rgba(0, 0, 0, 1)">documentA); Annotation[] an</span>=<span style="color: rgba(0, 0, 0, 1)"> clazz.getAnnotations(); System.out.println(</span>"an:"+<span style="color: rgba(0, 0, 0, 1)"> Arrays.toString(an)); Annotation[] an2</span>=<span style="color: rgba(0, 0, 0, 1)">clazz.getDeclaredAnnotations(); System.out.println(</span>"an2:"+<span style="color: rgba(0, 0, 0, 1)"> Arrays.toString(an2)); </span><span style="color: rgba(0, 0, 255, 1)">boolean</span> b=clazz.isAnnotationPresent(DocumentA.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">); System.out.println(</span>"b:"+<span style="color: rgba(0, 0, 0, 1)">b); </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 执行结果: A:@com.zejian.annotationdemo.DocumentA() an:[@com.zejian.annotationdemo.DocumentA(), @com.zejian.annotationdemo.DocumentB()] an2:@com.zejian.annotationdemo.DocumentB() b:true </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> }
}
运行时注解处理器
了解完注解与反射的相关 API 后,现在通过一个实例(该例子是博主改编自《Tinking in Java》)来演示利用运行时注解来组装数据库 SQL 的构建语句的过程
/** * Created by wuzejian on 2017/5/18. * 表注解 */@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface DBTable {String name() default ""; }/** * Created by wuzejian on 2017/5/18. * 注解 Integer 类型的字段 */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface SQLInteger {String name() </span><span style="color: rgba(0, 0, 255, 1)">default</span> ""<span style="color: rgba(0, 0, 0, 1)">; Constraints constraint() </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> @Constraints;
}/**
Created by wuzejian on 2017/5/18.
注解 String 类型的字段
*/@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface SQLString {String name() default "";
int value() default 0;
Constraints constraint() default @Constraints;
}/**Created by wuzejian on 2017/5/18.
约束注解
*/@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface Constraints {boolean primaryKey() default false;
boolean allowNull() default false;
boolean unique() default false;
}/**Created by wuzejian on 2017/5/18.
数据库表 Member 对应实例类 bean
*/@DBTable(name = "MEMBER")public class Member {@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;}
上述定义 4 个注解,分别是 @DBTable(用于类上)、@Constraints(用于字段上)、 @SQLString(用于字段上)、@SQLString(用于字段上) 并在 Member 类中使用这些注解,这些注解的作用的是用于帮助注解处理器生成创建数据库表 MEMBER 的构建语句,在这里有点需要注意的是,我们使用了嵌套注解 @Constraints,该注解主要用于判断字段是否为 null 或者字段是否唯一。必须清楚认识到上述提供的注解生命周期必须为@Retention(RetentionPolicy.RUNTIME)
,即运行时,这样才可以使用反射机制获取其信息。有了上述注解和使用,剩余的就是编写上述的注解处理器了,前面我们聊了很多注解,其处理器要么是 Java 自身已提供、要么是框架已提供的,我们自己都没有涉及到注解处理器的编写,但上述定义处理 SQL 的注解,其处理器必须由我们自己编写了,如下
public class TableCreator {public static String createTableSql(String className) throws ClassNotFoundException {
Class<?> cl = Class.forName(className);
DBTable dbTable = cl.getAnnotation(DBTable.class);</span><span style="color: rgba(0, 0, 255, 1)">if</span>(dbTable == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { System.out.println( </span>"No DBTable annotations in class " +<span style="color: rgba(0, 0, 0, 1)"> className); </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">; } String tableName </span>=<span style="color: rgba(0, 0, 0, 1)"> dbTable.name(); </span><span style="color: rgba(0, 0, 255, 1)">if</span>(tableName.length() < 1<span style="color: rgba(0, 0, 0, 1)">) tableName </span>=<span style="color: rgba(0, 0, 0, 1)"> cl.getName().toUpperCase(); List</span><String> columnDefs = <span style="color: rgba(0, 0, 255, 1)">new</span> ArrayList<String><span style="color: rgba(0, 0, 0, 1)">(); </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)">(Field field : cl.getDeclaredFields()) { String columnName </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">; Annotation[] anns </span>=<span style="color: rgba(0, 0, 0, 1)"> field.getDeclaredAnnotations(); </span><span style="color: rgba(0, 0, 255, 1)">if</span>(anns.length < 1<span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">continue</span><span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">if</span>(anns[0] <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> SQLInteger) { SQLInteger sInt </span>= (SQLInteger) anns[0<span style="color: rgba(0, 0, 0, 1)">]; </span><span style="color: rgba(0, 0, 255, 1)">if</span>(sInt.name().length() < 1<span style="color: rgba(0, 0, 0, 1)">) columnName </span>=<span style="color: rgba(0, 0, 0, 1)"> field.getName().toUpperCase(); </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> columnName </span>=<span style="color: rgba(0, 0, 0, 1)"> sInt.name(); columnDefs.add(columnName </span>+ " INT" +<span style="color: rgba(0, 0, 0, 1)"> getConstraints(sInt.constraint())); } </span><span style="color: rgba(0, 0, 255, 1)">if</span>(anns[0] <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> SQLString) { SQLString sString </span>= (SQLString) anns[0<span style="color: rgba(0, 0, 0, 1)">]; </span><span style="color: rgba(0, 0, 255, 1)">if</span>(sString.name().length() < 1<span style="color: rgba(0, 0, 0, 1)">) columnName </span>=<span style="color: rgba(0, 0, 0, 1)"> field.getName().toUpperCase(); </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> columnName </span>=<span style="color: rgba(0, 0, 0, 1)"> sString.name(); columnDefs.add(columnName </span>+ " VARCHAR(" +<span style="color: rgba(0, 0, 0, 1)"> sString.value() </span>+ ")" +<span style="color: rgba(0, 0, 0, 1)"> getConstraints(sString.constraint())); } } StringBuilder createCommand </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> StringBuilder( </span>"CREATE TABLE " + tableName + "("<span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)">(String columnDef : columnDefs) createCommand.append(</span>"\n " + columnDef + ","<span style="color: rgba(0, 0, 0, 1)">); String tableCreate </span>=<span style="color: rgba(0, 0, 0, 1)"> createCommand.substring( </span>0, createCommand.length() - 1) + ");"<span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> tableCreate;
}
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 判断该字段是否有其他约束 * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> con * </span><span style="color: rgba(128, 128, 128, 1)">@return</span> <span style="color: rgba(0, 128, 0, 1)">*/</span>
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));
}</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 输出结果: 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) ); </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
}
}
如果对反射比较熟悉的同学,上述代码就相对简单了,我们通过传递 Member 的全路径后通过 Class.forName() 方法获取到 Member 的 class 对象,然后利用 Class 对象中的方法获取所有成员字段 Field,最后利用field.getDeclaredAnnotations()
遍历每个 Field 上的注解再通过注解的类型判断来构建建表的 SQL 语句。这便是利用注解结合反射来构建 SQL 语句的简单的处理器模型,是否已回想起 Hibernate?
Java 8 中注解增强
元注解 @Repeatable
元注解 @Repeatable 是 JDK1.8 新加入的,它表示在同一个位置重复相同的注解。在没有该注解前,一般是无法在同一个类型上使用相同的注解的
@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.*;/** * Created by zejian on 2017/5/20. */ @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/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 的话,仍然无法获取。下面通过代码来演示:
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</span><?> clazz = AA.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">; FilterPath[] annotationsByType </span>= clazz.getAnnotationsByType(FilterPath.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">); FilterPath[] annotationsByType2 </span>= clazz.getDeclaredAnnotationsByType(FilterPath.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (annotationsByType != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (FilterPath filter : annotationsByType) { System.out.println(</span>"1:"+<span style="color: rgba(0, 0, 0, 1)">filter.value()); } } System.out.println(</span>"-----------------"<span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (annotationsByType2 != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (FilterPath filter : annotationsByType2) { System.out.println(</span>"2:"+<span style="color: rgba(0, 0, 0, 1)">filter.value()); } } System.out.println(</span>"使用getAnnotation的结果:"+clazz.getAnnotation(FilterPath.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">)); </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 执行结果(当前类拥有该注解FilterPath,则不会从CC父类寻找) 1:/web/update 1:/web/add 1:/web/delete ----------------- 2:/web/update 2:/web/add 2:/web/delete 使用getAnnotation的结果:null </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> }
}
从执行结果来看如果当前类拥有该注解 @FilterPath, 则 getAnnotationsByType 方法不会从 CC 父类寻找,下面看看另外一种情况,即 AA 类上没有 @FilterPath 注解
public @interface FilterPath {String value();
}@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Inherited @interface FilterPaths {
FilterPath[] value();
}@FilterPath("/web/list")@FilterPath("/web/getList")class CC { }class AA extends CC{
public static void main(String[] args) {Class</span><?> clazz = AA.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">; FilterPath[] annotationsByType </span>= clazz.getAnnotationsByType(FilterPath.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">); FilterPath[] annotationsByType2 </span>= clazz.getDeclaredAnnotationsByType(FilterPath.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (annotationsByType != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (FilterPath filter : annotationsByType) { System.out.println(</span>"1:"+<span style="color: rgba(0, 0, 0, 1)">filter.value()); } } System.out.println(</span>"-----------------"<span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (annotationsByType2 != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (FilterPath filter : annotationsByType2) { System.out.println(</span>"2:"+<span style="color: rgba(0, 0, 0, 1)">filter.value()); } } System.out.println(</span>"使用getAnnotation的结果:"+clazz.getAnnotation(FilterPath.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">)); </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 执行结果(当前类没有@FilterPath,getAnnotationsByType方法从CC父类寻找) 1:/web/list 1:/web/getList ----------------- 使用getAnnotation的结果:null </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> }
}
注意定义 @FilterPath 和 @FilterPath 时必须指明 @Inherited,getAnnotationsByType 方法否则依旧无法从父类获取 @FilterPath 注解,这是为什么呢,不妨看看 getAnnotationsByType 方法的实现源码:
default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {T[] result = getDeclaredAnnotationsByType(annotationClass);if (result.length == 0 && this instanceof Class &&
AnnotationType.getInstance(annotationClass).isInherited()) {
Class</span><?> superClass = ((Class<?>) <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">).getSuperclass();
if (superClass != null) {
result = superClass.getAnnotationsByType(annotationClass);
}
}return result;
}
新增的两种 ElementType
在 Java8 中 ElementType 新增两个枚举成员,TYPE_PARAMETER 和 TYPE_USE ,在 Java8 前注解只能标注在一个声明 (如字段、类、方法) 上,Java8 后,新增的 TYPE_PARAMETER 可以用于标注类型参数,而 TYPE_USE 则可以用于标注任意类型(不包括 class)。如下所示
class D<@Parameter T> { } class Image implements @Rectangular Shape { } new @Path String("/usr/bin")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
import java.lang.@NotNull String
这里主要说明一下 TYPE_USE,类型注解用来支持在 Java 的程序中做强类型检查,配合第三方插件工具(如 Checker Framework),可以在编译期检测出 runtime error(如 UnsupportedOperationException、NullPointerException 异常),避免异常延续到运行期才发现,从而提高代码质量,这就是类型注解的主要作用。总之 Java 8 新增加了两个注解的元素类型 ElementType.TYPE_USE 和 ElementType.TYPE_PARAMETER ,通过它们,我们可以把注解应用到各种新场合中。
ok~,关于注解暂且聊到这,实际上还有一个大块的知识点没详细聊到,源码级注解处理器,这个话题博主打算后面另开一篇分析。
主要参考资料 《Thinking in Java》