Java注解

注解Annotationjdk1.5中引入的一种特殊的注释机制。注解是一种可以看做标注的元数据普通的注释会被编译器直接忽略掉,而某些注解则可以被编译器打包放入class文件中。这些注解在jvm运行中可以获取到,从而与反射结合在一起,完成一些功能。在Java中,类、方法、变量、参数和包等均可以使用注解进行标注。最常见的有@Override@Deprected@SuppressWarnning

Java内置注解

Java1.7之前,其内部定义了一套注解,共有 7 个,根据功能分为普通注解和元注解。

作用在代码上的注解:

  • @Override - 检查该方法是否是重载方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误;
  • @Deprecated - 标记过时方法。如果使用该方法,会报编译警告;
  • @SuppressWarnings - 指示编译器去忽略注解中声明的警告;

作用在注解上的注解,即元注解

  • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问;
  • @Target - 标记这个注解应该是哪种 Java 成员;
  • @Documented - 标记这些注解是否包含在用户文档中;
  • @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

Annotation的架构

image-20200921225126642

上图为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,只可以作用在类上,即只可以标注在类名之上,它的RetentionPolicyRUNTIME,即该注解可保留至运行期。需要注意到的是,@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的主要方法。

image-20200921231018134

下面以一个利用运行时注解来组装数据库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));
       }

   }
}

参考

Java Annotation认知(包括框架图、详细介绍、示例说明)

Java注解(Annotation)详解

posted @ 2020-09-21 23:23  Reecelin  阅读(63)  评论(0编辑  收藏  举报