注解(也被称为元数据)为我们在代码种添加信息提供了一种形式化的方法, 使我们可以在稍后某个时刻非常方便地使用这些数据.
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
可以用继承的类或者组合了要测试方法的类, 来执行测试