zoukankan      html  css  js  c++  java
  • [java]注解

    注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。

    注解的语法比较简单,除了@符号的使用之外,它基本上与Java固有的语法一致。java SE5内置了三种,定义在java.lang中的注解:

    • @Override,表示当前的方法定义将覆盖父类中的方法。
    • @Deprecated,如果程序员使用了注解它的元素,那么编译器将会发出警告信息。
    • @Suppress Warnings,关闭不当的 编译器警告信息。在java SE5之前的版本中,也可以使用该注解,不过会被忽略不起作用。

    一、元注解

    Java目前只内置了三种标准注解,以及四种元注解。元注解专职负责注解其他的注解:


    @Target 表示该注解可以用于什么地方。可能的ElementType参数包括:
    CONSTRUCTOR:构造器的声明
    FIELD:域声明(包括enum实例)
    LOCAL_VARIABLE:局部变量声明
    METHOD:方法声明
    PACKAGE:包声明
    PATAMETER:参数声明
    TYPE:类、接口(包括注解类型)或enum声明

    @Retention 表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括:
    SOURCE:注解将被编译器丢弃
    CLASS:注解在class文件中可用,但会被VM丢弃
    RUNTIME:VM将在运行期也保留注解,因此可以通过反射机制读取注解的信息

    @Documented 将此注解包含在Javadoc中

    @Inherited 允许子类继承父类中的注解

    二、定义注解

    在注解中,一般都会包含一些元素以表示某些值。当分析处理注解时,程序或工具可以利用这些值。注解的元素看起来就像接口的方法,唯一的方法是你可以为其制定默认值。

    没有元素的注解称为标记注解

    @interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。

    下面是一个简单的注解,我们可以用它来跟踪一个项目中的用例。

    //UseCase.java
    
    import java.lang.annotation.ElementType;
    
    import java.lang.annotation.Retention;
    
    import java.lang.annotation.RetentionPolicy;
    
    import java.lang.annotation.Target;
    
    @Target(ElementType.METHOD)
    
    @Retention(RetentionPolicy.RUNTIME)
    
    public @interface UseCase {
    
        int id();
    
        String description() default "no description";
    
    }
    

    //PasswordUtils.java
    
    import java.util.List;
    
    public class PasswordUtils {
    
        @UseCase(id=47,description = "Passwords must comtain at least one numeric")
    
        public boolean validatePassword(String password){
    
            return (password.matches("\w*\d\w*"));
    
        }
    
        @UseCase(id=48)
    
        public String encryptPassword(String password){
    
            return new StringBuilder(password).reverse().toString();
    
        }
    
        @UseCase(id = 49,description = "New passwords can not equal previously used ones")
    
        public boolean checkForNewPassword(List<String> prevPasswords,String password){
    
            return !prevPasswords.contains(password);
    
        }
    
    }
    

    注解的元素在使用时表现为名-值对的形式,并需要置于@UseCase声明之后的括号内。在encryptpassword()方法的注解中,并没有给出description元素的值,因此,在UseCase的注解处理器分析处理这个类时会使用该元素的默认元素。

    三、编写注解处理器

    使用注解的过程中,很重要的一个部分就是创建与使用注解处理器

    下面是一个非常简单的注解处理器,我们将用它来读取PasswordUtils类,并使用反射机制查找@UseCase标记。我们为其提供了一组id值,然后他会列出在           PasswordUtils中找到用例,以及缺失的用例。

    //UseCaseTracker.java
    
    import java.lang.reflect.Method;
    
    import java.util.ArrayList;
    
    import java.util.Collections;
    
    import java.util.List;
    
    public class UseCaseTracker {
    
        public static void main(String[] args) {
    
            List<Integer> useCases=new ArrayList<>();
    
            Collections.addAll(useCases,47,48,49,50);
    
            trackUseCases(useCases,PasswordUtils.class);
    
        }
    
        private static void trackUseCases(List<Integer> useCases, Class<?> cl) {
    
            for (Method m:cl.getDeclaredMethods()){
    
                UseCase uc = m.getAnnotation(UseCase.class);
    
                if (uc!=null){
    
                    System.out.println("Found Use Case:"+uc.id()+" "+uc.description());
    
                    useCases.remove(new Integer(uc.id()));
    
                }
    
            }
    
            for (int i:useCases){
    
                System.out.println("Warning: Miss use case-"+i);
    
            }
    
        }
    
    }
    

    输出:

    Found Use Case:47 Passwords must comtain at least one numeric
    
    Found Use Case:48 no description
    
    Found Use Case:49 New passwords can not equal previously used ones
    
    Warning: Miss use case-50

    这个程序用到了两个反射的方法:getDeclaredMethod()和getAnnotation(),它们都属于AnnotatedElement接口(Class、Method和Field等类都实现了该接口)。getAnnotation()方法返回指定类型的注解对象,在这里就是UseCase。如果被注解的方法上没有该类型的注解,则返回null。然后我们通过调用id()和description()方法从返回的UseCase对象中提取元素的值。其中,encryptPassword()方法在注解的时候没有指定description的值,因此处理器在处理它对应的注解时,通过description()方法取得的是默认值no description。

    四、注解元素

    标签@UseCase由UseCase.java定义,其中包含int元素id,以及一个String元素description。注解元素可用的类型如下所示:

    • 所有基本类型(int,float,boolean等)
    • String
    • Class
    • enum
    • Annotation
    • 以上类型的数组

    如果使用其他类型,编译器会报错。

    五、默认值设置

    元素不能有不确定的值。也就是说,元素必须要么具有默认值,要么在使用注解时提供元素的值。

    其次,对于非基本类型的元素,无论是在源代码中声明或是在注解接口中定义默认值时,都不能以null作为其值。这个约束使得处理器很难表现一个元素的存在或缺失的状态,因为在每个注解的声明中,所有的元素都是存在,并且都具有相应的值。。为了绕开这个约束,我们只能定义一些特殊的值,例如空字符串或负数,以表示某个元素不存在。

    import java.lang.annotation.ElementType;
    
    import java.lang.annotation.Retention;
    
    import java.lang.annotation.RetentionPolicy;
    
    import java.lang.annotation.Target;
    
    @Target(ElementType.METHOD)
    
    @Retention(RetentionPolicy.RUNTIME)
    
    @interface SimulatingNull {
    
        int id() default -1;
    
        String description() default "";
    
    }
    


     

    六、生成外部文件

    假设你希望提供一些基本的对象/关系映射功能,能够自动生成数据库表,用以存储JavaBean对象。你可以选择使用XML描述文件,指明类的名字、每个成员以及数据库映射的相关信息。然而,使用注解的话,你可以将所有信息都保存在JavaBean源文件中。为此,我们需要一些新的注解,用以定义与Bean关联的数据库表的名字,以及与Bean属性关联的列的名字和SQL类型。

    以下是一个注解的定义,它告诉注解处理器,你需要生成一个数据库表:

    import java.lang.annotation.ElementType;
    
    import java.lang.annotation.Retention;
    
    import java.lang.annotation.RetentionPolicy;
    
    import java.lang.annotation.Target;
    
    /**
    
     * Created by pc on 2016/2/18.
    
     */
    
    @Target(ElementType.TYPE)
    
    @Retention(RetentionPolicy.RUNTIME)
    
    public @interface DBTable {
    
        public String name() default "";
    
    }
    

    在@Target注解中指定的每一个ElementType就是一个约束,它告诉编译器,这个自定义的注解只能应用于该类型。程序员可以指定enum ElementType中的某一个值,或者以逗号分隔的形式指定多个值。如果想要将注解应用于所有的ElementType那么可以省去@Target元注解,不过这并不常见。

    注意,@DBTable有一个name()元素,该注解通过这个元素为处理器创建数据库表提供表的名字。

    接下来是为修饰JavaBean域准备的注解:

    //Constraints.java
    
    import java.lang.annotation.ElementType;
    
    import java.lang.annotation.Retention;
    
    import java.lang.annotation.RetentionPolicy;
    
    import java.lang.annotation.Target;
    
    @Target(ElementType.FIELD)
    
    @Retention(RetentionPolicy.RUNTIME)
    
    public @interface Constraints {
    
        boolean primaryKey() default false;
    
        boolean allowNull() default true;
    
        boolean unique() default false;
    
    }
    //SQLString.java
    
    import java.lang.annotation.ElementType;
    
    import java.lang.annotation.Retention;
    
    import java.lang.annotation.RetentionPolicy;
    
    import java.lang.annotation.Target;
    
    @Target(ElementType.FIELD)
    
    @Retention(RetentionPolicy.RUNTIME)
    
    public @interface SQLString {
    
        int value() default 0;
    
        String name() default "";
    
        Constraints constraints() default @Constraints;
    
    }
    
    //SQLInteger.java
    
    import java.lang.annotation.ElementType;
    
    import java.lang.annotation.Retention;
    
    import java.lang.annotation.RetentionPolicy;
    
    import java.lang.annotation.Target;
    
    @Target(ElementType.FIELD)
    
    @Retention(RetentionPolicy.RUNTIME)
    
    public @interface SQLInteger {
    
        String name() default "";
    
        Constraints constraints() default @Constraints;
    
    }
    

    注解处理器通过@Constraints注解提取出数据表的元数据,另外两个@interface定义的是SQL类型。这些SQL类型具有name()元素和constraints()元素。后者利用了注解嵌套的功能将column类型的数据库约束信息嵌入其中。注意constraints()元素的默认值时@Constraints。由于@Constraints注解类型之后,没有在括号中指明@Constraints中的元素的值。因此,constraints()元素的默认值实际上就是一个所有元素艘为默认值的@Constraints注解。如果要领嵌入@Constraints注解中的unique()元素为true,并以此作为constraints()元素的默认值,则需要如下定义元素:

    public @interface Uniqueness {
    
        Constraints constraints() default @Constraints(unique = true);
    
    }
    

    下面是一个简单的Bean定义,我们在其中应用了以上的这些注解:

    @DBTable(name = "MEMBER")
    
    public class Member {
    
        static int memberCount;
    
        @SQLString(30)
    
        String firstName;
    
        @SQLString(50)
    
        String lastName;
    
        @SQLInteger
    
        Integer age;
    
        @SQLString(value = 30, constraints = @Constraints(primaryKey = true))
    
        String handle;
    
        public String getHandle() {
    
            return handle;
    
        }
    
        public String getFirstName() {
    
            return firstName;
    
        }
    
        public String getLastName() {
    
            return lastName;
    
        }
    
        public String toString() {
    
            return handle;
    
        }
    
        public Integer getAge() {
    
            return age;
    
        }
    
    }
    

    类的注解@DBTable给定了值MEMBER,它将会用来作为表的名字。Bean的属性firstName和lastName,都被注解为@SQLString类型,并且其元素值分别为30和50,。这些注解有两个有趣的地方:第一,他们都使用了嵌入的@Constraints注解的默认值;第二,它们都使用了快捷方式,何为快捷方式呢?如果程序员的注解中定义了名为value的元素,并且在应用该注解的时候,如果该元素是唯一需要赋值的一个元素,那么此时无需使用名-值的这种语法,而只需要在括号内给出value元素所需要的值即可。这可以应用于任何合法类型的元素。当然了,这也限制了程序员必须将次元素命名为value,不过在上面的例子中,这不但时语义更清晰,而且这样的注解语句也更容易理解:

    @SQLString(30)

    处理器将在创建表的时候使用该值设置SQL列的大小。

    实现处理器:

    import java.lang.annotation.Annotation;
    
    import java.lang.reflect.Field;
    
    import java.util.ArrayList;
    
    import java.util.List;
    
    /**
    
     * Created by pc on 2016/2/18.
    
     */
    
    public class TableCreator {
    
        public static void main(String[] args) throws Exception {
    
            if (args.length < 1) {
    
                System.out.println("argument: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<>();
    
                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("
      " + columnDef + ",");
    
                    String tableCreate = createCommand.substring(0, createCommand.length() - 1) + ");";
    
                    System.out.println("Table Creation SQL for " + className + " is :
    " + tableCreate);
    
                }
    
            }
    
        }
    
        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;
    
        }
    
    }
    

    输入args: Member

    输出:

    Table Creation SQL for Member is :
    
    CREATE TABLEMEMBER(
    
      FIRSTNAME VARCHAR(30));
    
    Table Creation SQL for Member is :
    
    CREATE TABLEMEMBER(
    
      FIRSTNAME VARCHAR(30),
    
      LASTNAME VARCHAR(50));
    
    Table Creation SQL for Member is :
    
    CREATE TABLEMEMBER(
    
      FIRSTNAME VARCHAR(30),
    
      LASTNAME VARCHAR(50),
    
      AGE INT );
    
    Table Creation SQL for Member is :
    
    CREATE TABLEMEMBER(
    
      FIRSTNAME VARCHAR(30),
    
      LASTNAME VARCHAR(50),
    
      AGE INT ,
    
      HANDLE VARCHAR(30) PRIMARY KEY);
    

    main()方法会处理命令行传入的每一个类名。使用forName()方法加载每一个类,并使用getAnnotation(DBTable.class)检查该类是否带有@DBTable注解。如果有,就将发现的表名保存下来。然后读取这个类的所有域,并用getDeclaredAnnotation()进行检查。该方法返回一个包含一个域上的所有注解的数组。最后用instanceof操作符来判断这些注解是否是@SQLInteger或SQLString类型,如果是的话,在对应的处理块中将构造出相应column名的字符串片段。注意,由于注解没有继承机制,所以要获得近似多态上网行为,使用getDeclaredAnnotation()是唯一的方法。

    嵌套中的@Constraint注解被传递给getConstraints()方法,由它负责构造一个包含SQL约束的String对象。

    需要注意的是,上面的演示的技巧对于真实的对象/关系映射而言,是很幼稚的。例如使用@DBTable类型的注解,程序员以参数的形式给出表的名字,如果程序员想要改变表的名字,这将迫使其必须重新编译Java代码。这不是我们希望看到的结果。现在已经有了很多可用的framework,可以将对象映射到关系数据库,并且其中越来越多的framework已经开始利用注解了。

  • 相关阅读:
    PMP:9.项目资源管理
    @JsonIgnore忽略JSON字段
    xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
    android加载不到.so文件
    报错Failed to install the following SDK components: platforms;android-29 Android SDK Platform 29
    Mac 终端启动运行Redis
    Mac 命令行执行Sublime
    Bean转为Json指定字段名,防止被修改大小写
    Rest接口入参忽略大小写 使用jackson注解
    mongo批量导入
  • 原文地址:https://www.cnblogs.com/xiaomoxian/p/5199601.html
Copyright © 2011-2022 走看看