自定义注解

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

注解是受c#启发,在 javaSE5 中引入的,虽然javaSE5 预先定义了一些注解,但一般来说,主要还是程序员添加新的注解,并按自己的方式使用他们。

内置注解

java SE5 内置了三种注解:

  • @Override, 表示当前的方法覆盖超类中的方法。如果拼写错误,或者方法签名对不上被覆盖的方法,编译器就会发出错误提示,
  • @Deprecated, 标记方法被弃用了,如果使用了被它标记的元素,编译器会发出警告信息
  • @SuppressWarnings, 关闭不当的编译器警告信息。

之外,java还提供了四种注解,专门负责新注解的创建,也被称为元注解。

元注解

@Target

用来定义你的注解将用于什么地方,例如用在方法上,类上 还是字段上等, 使用方式: @Target(ElementType.METHOD), 其中 ElementType 的取值有下面几种:

  • CONSTRUCTOR 构造器
  • FIELD 域, 包括 enum 的实例
  • LOCAL_VARIABLE 局部变量
  • METHOD 方法上
  • PACKAGE
  • PAEAMETER 参数
  • TYPE 类, 接口,包括注解类型 或 enum

@Retention

表示在什么级别保存该注解的信息,例如源码中,类文件中,或者运行时,使用方式: @Retention(RetentionPolicy.RUNTIME), 其中 RetentionPolicy 的取值有:

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

@Documented

将此注解包含在 javadoc中

@Inherited

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

编写注解

一个注解得以工作要有三个要素:

  1. 定义注解
  2. 使用注解
  3. 注解处理器

下面用一个对象/关系映射功能 ORM的简单例子来展现如何编写自定义注解:

  1. @DBTable 将此注解用于 JavaBean 上,以便生成一个数据库表
  2. @Constrains此注解用于字段上,描述该字段有一些约束,比如不能为空,要唯一等
  3. @SQLString 将此注解用于字段上,会生成一个 varchar 类型的列
  4. @SQLInt 将此注解用于字段上,会生成一个 int 类型的列

定义注解

/**
 * 注解定义
 */

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable{
    // 表名, 规定:必须要有一个默认值,
    String name() default "";
}

/**
 * 字段的约束
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints{
    // 是否是主键
    boolean primary() default false;
    // 是否可以为空
    boolean allowNull() default true;
    // 是否唯一
    boolean unique() default false;
}

/**
 * 生成 varchar 类型的列
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString{

    /**
     *  让value代表 varchar的长度,value 是个比较特殊的名字,在使用该注解的时候,
     *  如果该元素是唯一需要赋值的元素。那么可以简写,无需使用 名———值 语法,而是
     *  @SQLString(30)
     */
    int value() default 0;
    // 列的名字
    String name() default "";
}

/**
 * 生成 int 类型的列
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInt{
    // 列的名字
    String name() default "";
}

注解的使用

/**
 * 注解使用
 */
@DBTable(name = "users")// 表名是useres
public class User {
    /**
     * 设置id是主键,唯一, 不能为空
     */
    @Constraints(primary = true, allowNull = false, unique = true)
    @SQLInt
    private int id;

    @SQLInt
    private  int age;

    @SQLString(20)
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

注解处理器

/**
 * 注解处理器
 */
public class TableCreator{
    public String createTableSQLString(Class<?> cl) {
        // 获取类上的 DBTable 注解
        DBTable table = cl.getAnnotation(DBTable.class);
        if(table == null) {
            return "";
        }
        // 获取 DBTable 注解中的 name 参数
        String tableName = table.name();

        // 保存所有列名
        List<String> columnNameList = new ArrayList<String>();
        // 反射的方式遍历类中的字段
        for (Field field : cl.getDeclaredFields()) {
            // 列名
            String columnName = "";

            // 遍历字段上的注解
            for (Annotation annotation : field.getAnnotations()) {
                // 如果包含 SQLInt 注解
                if(annotation instanceof SQLInt) {
                    SQLInt sint = (SQLInt) annotation;
                    if(sint.name().length() == 0) {
                        columnName = field.getName().toLowerCase();
                    }else{
                        // 如果没有设置name属性,就用字段名作为列名
                        columnName = sint.name();
                    }
                    columnName += " INT";

                    // 获取字段上的约束类型的注解
                    Constraints con = field.getAnnotation(Constraints.class);
                    columnName += buildConstraints(con);

                    // 否则检查是否包含 @SQLString 注解
                }else if(annotation instanceof SQLString) {
                    SQLString sString = (SQLString) annotation;
                    if(sString.name().length() == 0) {
                        columnName = field.getName().toLowerCase();
                    }else{
                        // 如果没有设置name属性,就用字段名作为列名
                        columnName = sString.name();
                    }
                    // 获取字段长度属性
                    columnName += " VARCHAR(" + sString.value() + ")";
                    // 获取字段上的约束类型的注解
                    Constraints con = field.getAnnotation(Constraints.class);
                    columnName += buildConstraints(con);
                }
            }
            columnNameList.add(columnName);
        }


        StringBuilder createConmmand = new StringBuilder("");
        createConmmand.append("CREATE TABLE ").append(tableName).append("(");

        int length = columnNameList.size();
        for (int i = 0; i < length; i ++) {
            createConmmand.append("\n    ").append(columnNameList.get(i));
            if(i != length -1){
                createConmmand.append(",");
            }
        }

        createConmmand.append(");");


        return createConmmand.toString();
    }

    public String buildConstraints(Constraints con){
        if(con == null) {
            return "";
        }
        if(!con.allowNull()){
            return " NOT NULL";
        }
        if(con.primary()){
            return " PRIMARY KEY";
        }
        if(con.unique()) {
            return " UNIQUE";
        }
        return "";
    }
}

public class AnnotationDemo {

    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> userClass = Class.forName("io.dc.User");
        String sql =  new TableCreator().createTableSQLString(userClass);
        // 直接下面的也行
//        String sql =  new TableCreator().createTableSQLString(User.class);
        System.out.println(sql);
    }
}

注解使用注意

  • 不同注解可以组合使用,但同类型的注解不能重复使用
  • 目前注解不支持继承
  • 注解中的属性不能为 null, 本例中的 @SQLString name() 不能为 null, 必须设置 default
  • 注解元素只能一下几种,否则编译器会报错
    • 所有基本类型(int, float, boolean 等)
    • String
    • Class
    • enum
    • Annotation
    • 以上类型的数组

完整代码

为了测试方便,把所有类都放在一个文件中了:

package io.dc;

import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;


/**
 * 注解定义
 */

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface DBTable{
    // 表名, 规定:必须要有一个默认值,
    String name() default "";
}

/**
 * 字段的约束
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Constraints{
    // 是否是主键
    boolean primary() default false;
    // 是否可以为空
    boolean allowNull() default true;
    // 是否唯一
    boolean unique() default false;
}

/**
 * 生成 varchar 类型的列
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface SQLString{

    /**
     *  让value代表 varchar的长度,value 是个比较特殊的名字,在使用该注解的时候,
     *  如果该元素是唯一需要赋值的元素。那么可以简写,无需使用 名———值 语法,而是
     *  @SQLString(30)
     */
    int value() default 0;
    // 列的名字
    String name() default "";
}

/**
 * 生成 int 类型的列
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface SQLInt{
    // 列的名字
    String name() default "";
}


/**
 * 注解使用
 */
@DBTable(name = "users")// 表名是useres
class User {
    /**
     * 设置id是主键,唯一, 不能为空
     */
    @Constraints(primary = true, allowNull = false, unique = true)
    @SQLInt
    private int id;

    @SQLInt
    private  int age;

    @SQLString(20)
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}


/**
 * 注解处理器
 */
class TableCreator{
    public String createTableSQLString(Class<?> cl) {
        // 获取类上的 DBTable 注解
        DBTable table = cl.getAnnotation(DBTable.class);
        if(table == null) {
            return "";
        }
        // 获取 DBTable 注解中的 name 参数
        String tableName = table.name();

        // 保存所有列名
        List<String> columnNameList = new ArrayList<String>();
        // 反射的方式遍历类中的字段
        for (Field field : cl.getDeclaredFields()) {
            // 列名
            String columnName = "";

            // 遍历字段上的注解
            for (Annotation annotation : field.getAnnotations()) {
                // 如果包含 SQLInt 注解
                if(annotation instanceof SQLInt) {
                    SQLInt sint = (SQLInt) annotation;
                    if(sint.name().length() == 0) {
                        columnName = field.getName().toLowerCase();
                    }else{
                        // 如果没有设置name属性,就用字段名作为列名
                        columnName = sint.name();
                    }
                    columnName += " INT";

                    // 获取字段上的约束类型的注解
                    Constraints con = field.getAnnotation(Constraints.class);
                    columnName += buildConstraints(con);

                    // 否则检查是否包含 @SQLString 注解
                }else if(annotation instanceof SQLString) {
                    SQLString sString = (SQLString) annotation;
                    if(sString.name().length() == 0) {
                        columnName = field.getName().toLowerCase();
                    }else{
                        // 如果没有设置name属性,就用字段名作为列名
                        columnName = sString.name();
                    }
                    // 获取字段长度属性
                    columnName += " VARCHAR(" + sString.value() + ")";
                    // 获取字段上的约束类型的注解
                    Constraints con = field.getAnnotation(Constraints.class);
                    columnName += buildConstraints(con);
                }
            }
            columnNameList.add(columnName);
        }


        StringBuilder createConmmand = new StringBuilder("");
        createConmmand.append("CREATE TABLE ").append(tableName).append("(");

        int length = columnNameList.size();
        for (int i = 0; i < length; i ++) {
            createConmmand.append("\n    ").append(columnNameList.get(i));
            if(i != length -1){
                createConmmand.append(",");
            }
        }

        createConmmand.append(");");


        return createConmmand.toString();
    }

    public String buildConstraints(Constraints con){
        if(con == null) {
            return "";
        }
        if(!con.allowNull()){
            return " NOT NULL";
        }
        if(con.primary()){
            return " PRIMARY KEY";
        }
        if(con.unique()) {
            return " UNIQUE";
        }
        return "";
    }
}

public class AnnotationDemo {

    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> userClass = Class.forName("io.dc.User");
        String sql =  new TableCreator().createTableSQLString(userClass);
        // 直接下面的也行
//        String sql =  new TableCreator().createTableSQLString(User.class);
        System.out.println(sql);
    }
}

结果:

CREATE TABLE users(
    id INT NOT NULL,
    age INT,
    name VARCHAR(20));

Process finished with exit code 0