美文网首页
Java 注解原理详细介绍

Java 注解原理详细介绍

作者: 搬砖写Bug | 来源:发表于2020-07-03 14:37 被阅读0次

什么是注解

Java 注解(Annotation)又称 Java 标注或元数据,是 JDK5.0 引入的新特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。例如我们常见的@Override、@Deprecated、@Test等。

简单来说,可以把注解理解成代码中的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过注解,开发人员可以在不改变原有代码情况下,在源代码中嵌入补充信息。


annotation.png

注解的作用

  1. 生成文档:通过代码里标识的元数据生成javadoc文档;
  2. 编译检查:通过代码里标识的元数据,让编译器在编译期间进行检查;
  3. 编译时动态处理:例如编译时通过代码里标识的元数据,动态生成代码;
  4. 运行时动态处理:运行时通过代码里标识的元数据动态处理,例如使用反射注入实例

注解语法和使用

通常,注解可分为以下三类:
1. 元注解
元注解是用于定义注解的注解,java.lang.annotation提供了四种元注解,专门注解其他的注解:

  • @Documented: 标明是否生成javadoc文档
  • @Retention: 标明注解被保留的阶段,即什么时候使用该注解
public enum RetentionPolicy {
    SOURCE,            /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了  */
    CLASS,             /* 编译器将Annotation存储于类对应的.class文件中,默认行为  */
    RUNTIME            /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}

例如 @Override, 当它修饰一个方法的时候,表明该方法是重写父类的方法,编译期间会进行语法检查,但编译器处理完后,@Override就没有任何作用。

  • @Target: 标明注解使用的范围,即该注解用于什么地方
public enum ElementType {
    TYPE,               /* 描述类、接口(包括注释类型)或枚举  */
    FIELD,              /* 描述成员变量、对象、属性  */
    METHOD,             /* 用来描述方法 */
    PARAMETER,          /* 参数声明  */
    CONSTRUCTOR,        /* 构造方法声明  */
    LOCAL_VARIABLE,     /* 描述局部变量  */
    ANNOTATION_TYPE,    /* 注释类型声明  */
    PACKAGE             /* 包声明  */
}
  • @Inherited: 标明注解可继承,是否允许子类继承该注解

2. Java常用注解
@Override: 标明重写某个方法
@Deprecated: 标明某个类或方法过时
@SuppressWarnings: 标明要忽略的警告,
当代码中使用这些注解后,编译器就会进行检查。

例如,这两段代码是@Override和@SuppressWarnings的Java源文件:

[Override.java]
@Target(ElementType.METHOD)                       /* 表明@Override用于描述方法  */
@Retention(RetentionPolicy.SOURCE)                /* 表明@Override仅在于编译器处理期间存在  */
public @interface Override {
}

[SuppressWarnings.java]
 /*   表明@SuppressWarnings可以描述方法、字段、参数、构造方法类等  */
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) 
@Retention(RetentionPolicy.SOURCE)             /*   表明@SuppressWarnings仅在于编译器处理期间存在     */
public @interface SuppressWarnings {
    String[] value();
}

@interface: 表示实现了 java.lang.annotation.Annotation 接口,即该注解就是一个Annotation。
定义 Annotation 时,@interface 是必须的。
注意:它和通常的 implemented 实现接口的方法不同,Annotation 接口的实现细节都由编译器完成。
通过 @interface 定义注解后,该注解不能继承其他的注解或接口。
@Override使用很常见,来看一个使用@SuppressWarnings的例子:

@SuppressWarnings("deprecation")
private void push() {
    ......
}

如果push() 方法是过期的方法,编译时就会产生警告。而使用了 @SuppressWarnings(value={"deprecation"})后,编译器会对"调用 push() 产生的警告"保持沉默。

3. 自定义注解
可以根据自己的需求定义注解,如写UT/IT case 时使用到的@Test,Google 开源依赖注入框架Dagger2中的@Inject、@Module等

@Test注解后,在运行该方法时,测试框架会自动识别该方法并单独调

注解处理器

如果没有注解处理器,那注解跟注释其实没有什么区别,下面是Thinking Java的一个Demo,使用注解创建数据表:

1. 定义用来创建表名的注解@DBTable:

//DBTable.java
@Target(ElementType.TYPE) //Applies to classes only
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
    public String name() default "";
}

2. 定义用来约束的注解@Constraints:

//Constraints.java
@Target(ElementType.FIELD) 
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
    boolean primarykey() default false;
    boolean allowNull() default true;
    boolean unique() default false;
}

3. 定义声明String类型的注解@SQLString:

//SQLString.java
@Target(ElementType.FIELD)
public @interface SQLString {
    int value() default 0;
    String name() default "";
    Constraints constraints() default @Constraints;
}

4. 定义声明Integer类型的注解@SQLInteger:

//SQLInteger.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
    String name() default "";
    Constraints constraints() default @Constraints;
}

注意在SQLString.java和SQLInteger.java中constraints()元素的默认值就是@Constraints注解设定的默认值。如果现在要令嵌入的@Constraints注解中的unique()元素为true, 并以此作为constraints元素的默认值,则需要如下定义该元素

//Uniqueness.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Uniqueness {
    Constraints constraints() default @Constraints(unique = true)
}

5. 使用以上这些注解,定义一个数据库表:

//Member.java
@DBTable(name = "MEMBER")
public class Member {
    @SQLString(30) String firstName;
    @SQLString(50) String lastName;
    @SQLInteger Integer age;
    @SQLString(value = 30, constraints = @Constraints(primarykey = true))
    String handle;
    static int memberCount;

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public Integer getAge() {
        return age;
    }

    public String getHandle() {
        return handle;
    }

    public static int getMemberCount() {
        return memberCount;
    }
}

6. 实现注解处理器:

//TableCreator.java
{Args:com.sdkd.database.Member}
public class TableCreator {
    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 {
        if(args.length < 1) {
            System.out.println("arguments: annotated classes");
            System.exit(0);
        }
        for(String className : args) {
            Class<?> cl = Class.forName(className);
            DBTable dbTable = cl.getAnnotation(DBTable.class);
            if(dbTable == null) {
                System.out.println("No DBTable annotations in class" + className);
                continue;
            }
            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>();
            for(Field field : cl.getDeclaredFields()) {
                String columnName = null;
                Annotation[] anns = field.getDeclaredAnnotations();
                if (anns.length < 1) continue;
                if (anns[0] instanceof SQLInteger) {
                    SQLInteger sInt = (SQLInteger) anns[0];
                    //Use field name if name not specified
                    if (sInt.name().length() < 1) {
                        columnName = field.getName().toUpperCase();
                    } else {
                        columnName = sInt.name();
                    }
                    columnDefs.add(columnName + " INT" + getConstraints(sInt.constraints()));
                }
                if (anns[0] instanceof SQLString) {
                    SQLString sString = (SQLString) anns[0];
                    if (sString.name().length() < 1) {
                        columnName = field.getName().toUpperCase();
                    } else {
                        columnName = sString.name();
                    }
                    columnDefs.add(columnName + " VARCHAR(" +
                            sString.value() + ")" +
                            getConstraints(sString.constraints()));
                }
            }
            StringBuilder createCommand = new StringBuilder(
                    "CREATE TABLE " + tableName + "("
            );
            for(String columnDef : columnDefs) {
                createCommand.append("\n    " + columnDef + ",");
                String tableCreate = createCommand.substring(0, createCommand.length() - 1) + ");";
                System.out.println("Table Creation SQL for " +
                        className + " is :\n" + tableCreate);
            }
        }
    }
}

7. main()方法测试:

public class Test {
    public static void main(String[] args) throws Exception {
        String[] arg = {"com.sdkd.database.Member"};
        new TableCreator().main(arg);
    }
}

7. 输出:

Table Creation SQL for com.sdkd.database.Member is :
CREATE TABLE MEMBER(
    FIRSTNAME VARCHAR(30));
Table Creation SQL for com.sdkd.database.Member is :
CREATE TABLE MEMBER(
    FIRSTNAME VARCHAR(30),
    LASTNAME VARCHAR(50));
Table Creation SQL for com.sdkd.database.Member is :
CREATE TABLE MEMBER(
    FIRSTNAME VARCHAR(30),
    LASTNAME VARCHAR(50),
    AGE INT);
Table Creation SQL for com.sdkd.database.Member is :
CREATE TABLE MEMBER(
    FIRSTNAME VARCHAR(30),
    LASTNAME VARCHAR(50),
    AGE INT,
    HANDLE VARCHAR(30)PRIMARY KEY);

相关文章

网友评论

      本文标题:Java 注解原理详细介绍

      本文链接:https://www.haomeiwen.com/subject/sinvxktx.html