Java注解
注解Annotation
是jdk1.5
中引入的一种特殊的注释机制。注解是一种可以看做标注的元数据。普通的注释会被编译器直接忽略掉,而某些注解则可以被编译器打包放入class文件中。这些注解在jvm
运行中可以获取到,从而与反射结合在一起,完成一些功能。在Java
中,类、方法、变量、参数和包等均可以使用注解进行标注。最常见的有@Override
、@Deprected
和@SuppressWarnning
。
Java内置注解
Java1.7
之前,其内部定义了一套注解,共有 7 个,根据功能分为普通注解和元注解。
作用在代码上的注解:
@Override
- 检查该方法是否是重载方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误;@Deprecated
- 标记过时方法。如果使用该方法,会报编译警告;@SuppressWarnings
- 指示编译器去忽略注解中声明的警告;
作用在注解上的注解,即元注解
@Retention
- 标识这个注解怎么保存,是只在代码中,还是编入class
文件中,或者是在运行时可以通过反射访问;@Target
- 标记这个注解应该是哪种Java
成员;@Documented
- 标记这些注解是否包含在用户文档中;@Inherited
- 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)
Annotation的架构
上图为Annotation
的架构图。从中我们看出:
- 1个
Annotation
和1个RentationPolicy
关联,可以理解为,每1个Annotation
对象,都有唯一的Rentation
属性; - 1个
Annotation
和1n个`ElementType`关联,可以理解为,每1个`Annotation`对象可以有1n个ElementType
属性;
例如常见的@Override
注解和@SuppressWarning
注解。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
下面我们先介绍这些属性的含义。
@Target
用来约束注解可以应用的地方(如,类,方法,字段等),其中ElementType
是枚举类型,用以指定Annotation
的类型,即可作用在哪些地方。具体如下所示:
public enum ElementType {
//类、接口(包括注释类型)或枚举声明
TYPE,
//字段声明(包括枚举常量)
FIELD,
//方法声明
METHOD,
//参数声明
PARAMETER,
//构造方法声明
CONSTRUCTOR,
//局部变量声明
LOCAL_VARIABLE,
//注释类型声明
ANNOTATION_TYPE,
//包声明
PACKAGE
}
@Retention
用来约束注解的生命周期,其中RentationPolicy
也是枚举类型,用以指定Annotation
的策略。通俗点说,就是不同 RetentionPolicy
类型的 Annotation
的生命周期不同,具体如下所示:
public enum RetentionPolicy {
//该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里
SOURCE,
//该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中
//因此,不能通过反射拿到注解的信息
CLASS,
//源码、class文件和执行的时候都会该注解的信息
//注解信息将在运行期(JVM)也保留,因此可以通过反射获取注解的信息
RUNTIME
}
除了@Target
和@Retention
这两个元注解之外,还有@Documented
和@Inherited
这两个。先说@Documented
,被@Documented
修饰的注解会生成到javadoc
之中。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DocumentA {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DocumentB {
}
@DocumentA
@DocumentB
public class DocumentTest {
}
使用javadoc命令生成文档:
javadoc DocumentTest.java DocumentA.java DocumentB.java
@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注解:[]
Annotation的语法定义
Annotation
的写法通常如下所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationTest {
}
AnnotationTest
即是我们自定义的一个注解,后面我们可以直接使用@AnnotationTest
来使用它。自定义注解时,我们使用@interface
来表示这是一个注解,如class
表示类,interface
表示接口一样。要注意的是,虽然其与interfce
关键字很像,但二者有本质区别。interface
表示接口,接口可被继承或者实现,而@interface
表示注解,而注解不能被继承。上面我们来详细解释一下这个自定义注解.它的名字是AnnotationTest
,只可以作用在类上,即只可以标注在类名之上,它的RetentionPolicy
为RUNTIME
,即该注解可保留至运行期。需要注意到的是,@AnnotationTest
这个注解中没有定义任何其他元素,这种注解被称为标记注解,用来做标记使用。
下面我们再定义一个注解,@AnnotationTest2
。此时,我们声明了一个String
类型的name
元素,默认为空字符;一个int
类型的age
元素,默认为0
。需要注意的是,元素的声明与普通java
类的属性不同,必须以普通java
类的方法形式来声明,同时可选择使用default
来提供默认值。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationTest2{
String name() default "";
int value() default 0;
}
@AnnotationTest
的使用如下:
public class Test{
@AnnotationTest(name = "Reece",age = 18)
private User user;
//todo
}
public class User{
private String name;
private int age;
}
注解支持的元素数据类型包括以下几种。需要注意到的是,注解本身也可以作为元素存在于另一个注解中,这就是嵌套注解。
- 八种基本数据类型
String
Class
enum
Annotation
- 上述类型的数组形式
下面演示了上述类型的使用过程
public enum Status {
NORMAL(200,"成功"),
ERROR(404,"失败")
;
private String msg;
private int code;
Status(int code,String msg) {
this.msg = msg;
this.code = code;
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Reference {
boolean next() default false;
}
public @interface AnnotationTest3{
//声明枚举
Status status() default Status.NORMAL;
//String 类型
String name() default "";
//String数组
String[] value();
//boolean类型
boolean needed() default false;
//Class类型
Class<?> testCase() default Void.class;
//注解嵌套
Reference reference() default @Reference(next=true);
}
注解与反射机制的结合
java
中所有注解都继承了Annotation
接口,也就是说,java
使用Annotation
接口代表注解元素,该接口是所有Annotation
类型的父接口。在java.lang.reflect
反射包下新增了AnnotatedElement
接口,主要用于表示正在JVM
中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术读取注解的信息,如反射包的Constructor
类、Field
类、Method
类、Package
类和Class
类都实现了AnnotatedElement
接口,即可在JVM
中分别获取到对应反射类中的注解信息。下图为jdk1.8
中关于AnnotatedElement
的主要方法。
下面以一个利用运行时注解来组装数据库SQL
的构建过程为例。
/**
*表注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
String name() default "";
}
/**
* 注解Integer类型的字段
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
//该字段对应数据库表中类型为int类型的列名
String name() default "";
//嵌套注解
Constraints constraint() default @Constraints;
}
/**
* 注解String类型的字段
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
//对应数据库表类型为varchar类型的列名
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;
}
public class TableCreator{
//todo
pulic static String createTable(String className) throws Exception{
Class<?> clazz =Class.forName(className);
DBTable dbTable=clazz.getAnnotation(DBTable.class);
if(dbTable==null){
return null;
}
String tableName=dbTable.name();
if(tableName.length() <1){
tableName=class.getName.toUpperCase();
}
List<String> columnDefs=new ArrayList();
Filed[] fields=class.getDeclaredFileds();
for(Field field:fields){
String columnName="";
Annotation[] annotions=field.getDeclaredAnnotations();
if(annotations.length<1){
continue;
}
for(int i=0;i<annotations.length;i++){
sqlParse(annotations[i],columnName,field,columnDefs);
}
}
StringBuilder createSQL=new StringBuilder("create table "+tableName+" (");
for(String columnName:columnDefs){
createSQL.append("\n "+column+",");
}
String tableCreate=createSQL.subString(0,createSQL.length()-1)+"\n );";
return tableCreate;
}
public static String getConstraints(Constraints constraints) {
String cons = "";
if (!constraints.allowNull()) {
cons += "NOT NULL ";
}
if (constraints.primaryKey()) {
cons += "PRIMARY KEY ";
}
if (constraints.unique()) {
cons += "UNIQUE ";
}
return cons;
}
private static void sqlParse(Annotation annotation, String columnName, Field field, List<String> columnDefs) {
if (annotation instanceof SQLInteger) {
SQLInteger sqlInteger = (SQLInteger) annotation;
if (sqlInteger.name().length() < 1) {
columnName = field.getName().toUpperCase();
} else {
columnName = sqlInteger.name();
}
columnDefs.add(columnName + " INT " + getConstraints(sqlInteger.constraints()));
} else if (annotation instanceof SQLString) {
SQLString sqlString = (SQLString) annotation;
if (sqlString.name().length() < 1) {
columnName = field.getName().toUpperCase();
} else {
columnName = sqlString.name();
}
columnDefs.add(columnName + " VARCHAR(" + sqlString.value() + ") " + getConstraints(sqlString.constraints()));
}
}
public static void main(String[] args) throws ClassNotFoundException {
String[] classes = {"com.company.annotation.Member"};
for (String className : classes) {
System.out.println("TABLE CREATION SQL for " + className + "is:\n" + createTable(className));
}
}
}