java基础-注解一-注解基础

注解,也叫元数据。为我们在代码中添加信息提供了一种形式化的方法,是我们可以在稍后的某个时刻非常方便的使用这些数据。
每当你创建描述符性质的类或者接口时,一旦其中包含了重复性的工作,那就可以使用注解来简化与自动化该过程。

定义注解:

一个简单的注解定义的实例:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}

除了@符号以外,@Test的定义就像一个空接口。定义接口时,会需要一些元注解,所谓元注解,就是专职负责注解其他注解的注解,java目前只内置了四种元注解,他们分别是:

  • @Target : 表示该注解可以用在什么地方,可能的ElementType参数包括:
    • CONSTRUCTOR:构造器的声明
    • FIELD:域声明
    • LOCAL_VARIABLE:局部变量声明
    • METHOD:方法声明
    • PACKAGE:包声明
    • PARAMETER:参数声明
    • TYPE:类、接口(包括注解类型)或者emun的声明
      ElementType参数的值可以有多个,用逗号分隔。省略@Target则表示所有的值都试用
  • @Retention :表示需要在什么级别保存该注解信息。可选的RetentionPolicy包括:

    • SOURCE:注解将被编译器丢弃
    • CLASS:注解在class文件中可用,但会被VM丢弃
    • RUNTIME:VM将在运行期也保留该注解,因此可以通过反射机制读取注解信息。
  • @Document :此注解包含在javadoc中。

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

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

注解在加入注解元素:

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

注解使用

public class PasswordUtils {
    @UseCase(id=47,description="Password must contain 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 password can't equal previously used ones")
    public boolean checkForNewPassword(List prevPasswords,String password){
        return !prevPasswords.contains(password);
    }
}

编写注解处理器

如果没有用来读取注解的工具,那么注解并不会比注释更有用。使用注解的过程中,很重要的部分就是注解处理器的创建和使用。java SE5扩展了反射机制的AP,以帮助程序员构造这类工具。同时还提供了一个外部的apt工具帮助程序员解析带有注解的java源代码。

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

上述UseCaseTracker类是一个简单的注解处理器。我们用它读取PasswordUtils类,并且使用反射机制查找@UseCase标记。
这个程序使用到了两个放射的方法:getDeclaredMethods()getAnnotation() 他们都属于AnnotatedElement接口 (Class、Method与Field都实现了这个接口) getAnnotation() 返回指定类型的注解对象,在这里就是UseCase。如果被注解的方法上没有该类型的注解,则会返回null。然后调用id()description() 方法从返回的UseCase对象中提取元素的值。

注解元素

注解元素可用的类型如下:

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

如果使用了其他类型,编译器就会报错。注意,包装类型也是不允许使用的。注解也可以作为元素的类型,也就是说注解可以嵌套使用。

默认值限制

编译器对元素的默认值有很多限制
首先,元素不能有不确定的默认值。也就是说,元素必须要么有默认值,要么在注解时提供元素的值。
其次,对于非基本类型的元素,无论是源代码中声明时,或是在注解接口中定义默认值时,都不能以null作为其值。为了绕开这个约束,我们可以自定义一些特殊的值,例如空字符串或者附属,以表示这个元素不存在。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
    public int id() default -1;
    public String description() default " ";
}

使用注解替代某些配置文件

有些框架需要一些额外的信息才能和源代码协同工作。比如Hibernate,Spring等,这个时候可以使用注解替代这些配置文件。下面举一个使用注解生成数据库表的例子:

/**
 * 告诉处理器生成一个数据库表
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
    public String name() default "";
}

创建修饰JavaBean域的注解:

/**
 1. 修饰属性
 2. 提取数据库表元数据
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
    boolean primaryKey() default false;
    boolean alloNull() default true;
    boolean unique() default false; 
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
    int value() default 0;
    String name() default "";
    Constraints constraints() default @Constraints;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
    String name() default "";
    Constraints constraints() default @Constraints;
}

注解处理器通过@Constraints提取数据库表中的元数据。当然,对于数据库所能提供者约束而言,这里只是他的一个子集。
另外两个注解@SQLString和@SQLInteger定义的是SQL类型。
下面是一个简单的Bean定义:

@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 String toString(){return handle;}
}

类的注解@DBTable给定了值MEMBER,这将成为数据库表的名字。
这里需要说明两点:
1. 他们都使用了嵌入的@Constraints注解的默认值。
2. 他们都是用了快捷方式,所谓的快捷方式就是程序员在注解中定义了名外value的元素,并且在应用该注解的时候,如果该元素是唯一需要赋值的元素,那么此时无需使用 名-值 对 这种语法。只需要在括号内给出value的值即可。
@SQLString(30) 处理器将在创建表的时候使用该值设置SQL列的大小。
这里需要注意的是注解是不支持继承的。
####实现处理器

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 annotation in class "+className);
            }
            String tableName = dbTable.name();
            if(tableName.length() < 1){
                tableName = cl.getName().toUpperCase();
            }
            List columnDefs = new ArrayList();
            for(Field field : cl.getDeclaredFields()){
                String columnName = null;
                Annotation[] annos = field.getDeclaredAnnotations();
                if(annos.length<1){
                    continue;
                }
                if(annos[0] instanceof SQLInteger){
                    SQLInteger sInt = (SQLInteger)annos[0];
                    if(sInt.name().length()<1)
                        columnName = field.getName().toUpperCase();
                    else
                        columnName = sInt.name();
                    columnDefs.add(columnName+" INT" + getConstraints(sInt.constraints()));
                }
                if(annos[0] instanceof SQLString){
                    SQLString sString = (SQLString)annos[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 create SQL for " + className + "is :\n"+tableCreate);
            }
        }
    }
    private static String getConstraints(Constraints con){
        String constraints = "";
        if(!con.alloNull())
            constraints += " NOT NOLL";
        if(con.primaryKey())
            constraints += " PRIMARY KEY";
        if(con.unique())
            constraints += " UNIQUE";
        return constraints;
    }
}

输出:

Table create SQL for com.len.javabase.anno.base.Memberis :
CREATE TABLE MEMBER(
   FIRSTNAME VARCHAR(30))
Table create SQL for com.len.javabase.anno.base.Memberis :
CREATE TABLE MEMBER(
   FIRSTNAME VARCHAR(30),
   LASTNAME VARCHAR(50))
Table create SQL for com.len.javabase.anno.base.Memberis :
CREATE TABLE MEMBER(
   FIRSTNAME VARCHAR(30),
   LASTNAME VARCHAR(50),
   AGE INT)
Table create SQL for com.len.javabase.anno.base.Memberis :
CREATE TABLE MEMBER(
   FIRSTNAME VARCHAR(30),
   LASTNAME VARCHAR(50),
   AGE INT,
   HANDLE VARCHAR(30) PRIMARY KEY)

上述过程只是利用反射做了一个简单的解析。下节介绍使用apt处理注解

你可能感兴趣的:(java基础)