JavaBase:注解

注解

提出问题

概述

是什么

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

分类,各个分类是什么

  • 标记注解,没有元素的注解

优缺

  • 注解可以提供用来完整地描述程序所需的信息,而这些信息是无法用Java表达的
  • 注解使得我们能够以将由编译器来测试和验证的格式,存储有关程序的额外信息,注解可以用来生成描述符文件,甚至是新的类定义,并且有助于减轻编写“样板”代码的负担。
  • 可以将元数据保存在Java源代码中,并利用annotationAPI为自己的注解构造处理工具
  • 更加干净易读的代码以及编译期类型检查

为什么要用(作用)

  • 每当你创建描述符性质的类或接口时,一旦其中包含了重复性的工作,那就可以考虑使用注解来简化与自动化该过程

应用场景

常见注解

Java内置

  • @Override。表示当前方法定义将覆盖超类中的错误,如果没有覆盖,则编译器发出错误提示
  • @Deprecated。如果程序使用了注解为它的元素,编译器会发出警告
  • @SuppressWarnings,关闭不当的编译器警告信息。

基本语法

定义注解

1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}

注解的定义类似于定义一个接口,除了多出一个@,在定义注解时会需要一些元注解

成员

在注解中会包含一些元素来表示某些值,当分析处理注解时,程序可以利用这些值,注解的元素类似接口的方法,唯一的区别是你可以为其指定默认值。

示例

1
2
3
4
5
6
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
public int id();
public String description() default "no description";
}

该注解用于跟踪一个项目中的用例,如果一个方法实现了某个用例的需求则可以加上该注解

1
2
3
4
@UseCase(id = 47)
public boolean validatePassword(Stirng pws){

}

注解的元素在使用时表现为键值对的形式,置于@UseCase声明后的括号内。

元注解

  • @Target:定义你的注解将运用到什么地方,例如一个方法或者域
  • Retention:定义该注解在哪个级别可用,Source(源代码)、CLASS(类文件)、RUNTIME(运行时)
  • @Documented,将此注解包含在JavaDoc中
  • @Inherited,允许子类继承父类中的注解。

注解元素

注解@UseCase由UseCase.jva定义,其中可以包含的可用类型有:

  • 所有基本类型int 、float、boolean
  • String
  • Class、enum
  • Annotation
  • 数组

默认值限制

  • 编译器要求元素不能有不确定的值,即元素要么具有默认值,要么在使用注解时提供元素的值
  • 对于非基本类型的元素,无论是源代码声明或者定义默认值均不能为null。即所有元素都存在,因此只能以空字符串、负数来表示元素不存在

嵌套注解

Constraints注解嵌入到SQLString当中,并且值是默认的

1
2
3
public @interface SQLString {
Constraints constraints() default @Constraints;
} ///:~

若想要改变内部的值,则需要

1
2
3
public @interface SQLString {
Constraints constraints() default @Constraints(unique=true);
} ///:~

快捷方式

当注解中定义了名为value的元素,并且在赋值时是唯一需要赋值的一个元素,此时无需使用键值对的语法,只需要赋值即可,这可以用于任何类型的元素。

编写注解处理器

Java通过反射机制构造注解处理器。并且Java提供了外部工具apt解析带有注解的Java代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class UseCaseTracker {
public static void trackUseCases(List<Integer> useCases,
Class<?> cl) {
for(Method m : cl.getDeclaredMethods()) {//获得该类声明的所有方法
UseCase uc = m.getAnnotation(UseCase.class);//如果该方法存在UseCase的注解
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: Missing use case-" + i);
}
}
public static void main(String[] args) {
List<Integer> useCases = new ArrayList<Integer>();
Collections.addAll(useCases, 47, 48, 49, 50);
trackUseCases(useCases, PasswordUtils.class);
}
} /* Output:
Found Use Case:47 Passwords must contain at least one numeric
Found Use Case:48 no description
Found Use Case:49 New passwords can't equal previously used ones
Warning: Missing use case-50
*///:~

生成外部文件

有些framework需要一些额外的信息才能与你的源代码协同工作,而这种情况最适合注解实现价值。

如果你希望提供一些基本的对象/关系映射功能,能够自动生成数据库表用于存储JavaBean对象

  • 可以选择XML描述文件,指明类的名字、每个成员以及数据库映射的相关信息。
  • 使用注解将所有信息保存在Java源文件中。

首先定义@DBTable

1
2
3
4
5
@Target(ElementType.TYPE) // Applies to classes only
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
public String name() default "";
} ///:~

为修饰JavaBean准备的注解

注解处理器通过@Constraints提取出数据库表的元数据,尽管该注解只提供了数据库约束的一个很小的子集,但依然是一种帮助

1
2
3
4
5
6
7
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
boolean primaryKey() default false;
boolean allowNull() default true;
boolean unique() default false;
} ///:~

内部使用了嵌套注解@Constraints将CoLumn的约束嵌入,

1
2
3
4
5
6
7
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
int value() default 0;
String name() default "";
Constraints constraints() default @Constraints;
} ///:~
1
2
3
4
5
6
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
String name() default "";
Constraints constraints() default @Constraints;
} ///:~

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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 getHandle() { return handle; }
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public String toString() { return handle; }
public Integer getAge() { return age; }
} ///:~

Bean对应的表名为MEMBER,其内部有firstName等元素,值为30、50.

注解处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
public class TableCreator {
public static void main(String[] args) throws Exception {
if(args.length < 1) {
System.out.println("arguments: 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<String>();
for(Field field : cl.getDeclaredFields()) {
String columnName = null;
Annotation[] anns = field.getDeclaredAnnotations();
if(anns.length < 1)
continue; // Not a db table column
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];
// Use field name if name not specified.
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 + ",");
// Remove trailing comma
String tableCreate = createCommand.substring(
0, createCommand.length() - 1) + ");";
System.out.println("Table Creation SQL for " +
className + " is :\n" + 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;
}
}
/* Output:
Table Creation SQL for annotations.database.Member is :
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30));
Table Creation SQL for annotations.database.Member is :
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30),
LASTNAME VARCHAR(50));
Table Creation SQL for annotations.database.Member is :
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30),
LASTNAME VARCHAR(50),
AGE INT);
Table Creation SQL for annotations.database.Member is :
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30),
LASTNAME VARCHAR(50),
AGE INT,
HANDLE VARCHAR(30) PRIMARY KEY);
*///:~

Main方法会处理命令行传入的每个类名,用forName加载每个类,并检查类是否带有@DBTable注解,如果有就将发现的表名保存,并读取所有域。等等

注解处理器

使用apt处理注解

将观察者模式用于apt

基于注解的单元测试

反省总结

参考