美文网首页
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