20 注解 Annotations

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

java.lang中内置的三种注解

@Override, 表示当前的方法定义将覆盖超类中的方法

@Deprecated, 编译器为注解为它的元素添加警告

@SuppressWarnings, 关闭不当的编译器警告信息

  • 基本语法

注解的用法几乎与修饰符一样

@Test void testExecute() {}

定义注解

定义注解的方法和接口差不多, 并且编译会产生.class文件

Test.java

import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {}

注解中会加入一些元注解, 它们在java.lang.annotation中定义

@Target表示注解将应该的地方, 如一个方法或者一个域

@Retention表示在哪一级别可用, SOURCE(源代码中), 类文件中(CLASS), 或者运行时(RUNTIME)

注解中会包含一些嗯元素, 它们看起来就像接口的方法, 但是它们可以被定义默认值

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

public @interface UseCase {
    public int id();
    public String description() default "no description";
}

下面是一个在方法前使用注解的例子

public class PasswordUtil {

    //检查密码至少含有一位数字的注解
    @UseCase(id = 47, description = "Passwords must contain at least one numeric")
    public boolean validatePassword(String password) {
        return (password.matches("\\w*\\d\\w*"));
    }

    //加密密码的注解, 这里description没有给出, 被赋予默认值
    @UseCase(id = 48)
    public String encryptPassword(String password) {
        return new StringBuilder(password).reverse().toString();
    }

    //检查密码是否在一个旧的密码表中的注解
    @UseCase(id = 49, description = "New passwords can't equal previously used ones")
    public boolean checkForNewPassword(
        List<String> prevPasswords, String password) {
        return !prevPasswords.contains(password);
    }
}

注解中的元素以键-值对的形式给出

元注解

元注解一共有四种

@Target                                       表示注解可以用在什么地方

                                                     CONSTRUCTOR: 构造器的声明

                                                     LOCAL_VALIABLE: 局部变量声明

                                                     METHOD: 方法声明

                                                     PACKAGE: 包声明

                                                     PARAMETER: 参数声明

                                                     TYPE: 类, 接口, 或enum声明

@Retention                                  表示需要在什么级别保存注解

                                                     SOURCE: 注解将被编译器丢弃

                                                     CLASS: 注解在class文件中可用, 但会被VM丢弃

                                                     RUNTIME: VM将在运行期也保留注解, 可以通过反射机制读取注解的信息

@Documented                             将注解包含在Javadoc中

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

  • 编写注解处理器

注解信息可以通过反射机制读出来

下面这个例子是把上面写在PasswordUtil类方法中的注解都出来的一个程序

public class UseCaseTracker {
    public static void 
    trackUseCases(List<Integer> useCases, Class<?> cl) {

        // getDeclaredMethods获取类的所有方法对象Method
        for(Method m : cl.getDeclaredMethods()) {

            // getAnnotation, Method, Class, Field等类都实现了这个方法
            UseCase uc = m.getAnnotation(UseCase.class);
            if(uc != null) {

                //通过调用id(),description()方法, 提取注解对象中元素的值
                System.out.println("Found 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) throws Exception {
        List<Integer> list = new ArrayList<Integer>(Arrays.asList(47,48,49,50));
        trackUseCases(list,PasswordUtil.class);
    }
}

注解元素

上面那个注解, 定义了两个元素, 注解中的元素一共可以被定义为以下这些类型

1) 所有基本类型                   

2) String

3) Class

4) enum

5) Annotation

6) 以上类型的数组

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

默认值

所有注解元素都需要有确定的值, 要么在使用时提供注解的值, 要么具有默认值

且非基本类型的元素不能使用null值, 这就使得处理器难以表示一个元素存在或缺失了

我们只能自己定义一些特殊的值, 作为空值

生成外部文件       

下面是一个用多个注解定义与Bean有关的数据库表的名字, 以及与Bean属性关联的列的名字和SQL类型

//为类定义表的名字                                  

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
    public String name() default "";
}       

//为域定义Integer类型, 嵌套了一个约束注解             

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

//为域定义String类型, 嵌套了一个约束注解

@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 Constraints {
    boolean primaryKey() default false;
    boolean allowNull() default true;
    boolean unique() default false;
}

//表MEMBER

//DBTable注解的name定义了表名

@DBTable(name = "MEMBER")
public class Member {

    //SQLString注解的value值, 定义了默认的最大长度
    @SQLString(30) String firstName;
    @SQLString(50) String lastName;
    @SQLInteger Integer age;

    //SQLString里嵌套了Constraints注解, 将其主键值设为true
    @SQLString(value = 30, constraints = @Constraints(primaryKey = true))
    String handle;
    static int memberCount;
    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; }
}

@SQLString(30)这种不用写键名直接赋值的, 代表注解中有一个名为value的元素, 并且value是唯一需要赋值的元素,

可以这样简写

注解不支持继承

实现处理器

根据上面的表结构, 构造创建表的语句

public class TableCreator {
    public static void main(String[] args) throws Exception {
        String[] Args = {"annotations.database.Member"};
        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(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;

                //SQLInteger注解
                if(anns[0] instanceof SQLInteger) {
                    SQLInteger sInt = (SQLInteger)anns[0];

                    //字段名
                    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();

                    //sString.value()表示限制的位数
                    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(tableCreate);
            }
        }
    }

    //构造约束语句
    public 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;
    }
}

通过加载一个类, 先看看是否包含@DBTABLE注解, 如果没有就跳过, 如果有, 就获取其所有字段, 再从字段中获取所有的注解,

并生成字段创建语句

  • 基于注解的单元测试

用到net.mindview.atunit

使用@Unit注解

在要进行测试的方法前用@Test来标记

public class AtUnitExample1 {
    public String methodOne() {
        return "This is methodOne";
    }
    public int methodTwo() {
        System.out.println("This is methodTwo");
        return 2;
    }
    @Test boolean methodOneTest() { return methodOne().equals("This is methodOne"); }
    @Test boolean m2() { return methodTwo() == 2; }
    @Test private boolean m3() { return true; }

    @Test boolean failureTest() { return false; }
    @Test boolean anotherDisappointment() { return false; }
    public static void main(String[] args) throws Exception {
        //OSExecute.command("java net/mindview/atunit/AtUnit ./bin/annotations/AtUnitExample1");
    }
}

Output:

annotations.AtUnitExample1

    .methodOneTest

    .m2 This is methodTwo

   

    .m3

    .failureTest(failed)

    .anotherDisappointment(failed)

(5 tests)

>>> 2 FAILURES <<<

anotations.AtUnitExample1: failureTest

anotations.AtUnitExample1: anotherDisappointment

@Test将确保这些要验证的方法没有参数, 并且返回boolean或者void

可以用继承的类或者组合了要测试方法的类, 来执行测试

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值