1 总览
注解(也被称为原数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后的某个时刻非常方便的使用这些数据。 —《 java编程思想》
注解是jdk1.5引入的,目的是:
2 Java中的注解
2.1 java自带的注解
注解名称 | 解释 |
---|---|
@Override | 覆盖超类中的方法。用于检查是否覆盖成功,当没有覆盖上超类的方法,编译器就会报错 |
@Deprecated | 带该注解的方法编译器会发出警告,一般用于某个方法过时了 |
@SuppressWarnings | 关闭不当的编译器警告信息 |
2.2 Java元注解
元注解用于开发自定义注解。
注解名称 | 用途 |
---|---|
@Retention | 定义注解的保留策略 |
@Target | 定义注解的作用目标 |
@Documentt | 说明该注解将被包含在javadoc中 |
@Inherited | 说明子类可以继承父类中的该注解 |
@Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Target(ElementType.TYPE) // 接口、类、枚举、注解
@Target(ElementType.FIELD) // 字段、枚举的常量
@Target(ElementType.METHOD) // 方法
@Target(ElementType.PARAMETER) // 方法参数
@Target(ElementType.CONSTRUCTOR) // 构造函数
@Target(ElementType.LOCAL_VARIABLE) // 局部变量
@Target(ElementType.ANNOTATION_TYPE) // 注解
@Target(ElementType.PACKAGE) // 包
2.3 第三方注解
常用框架的注解,这里先不添加
- Spring -
- SpringMVC -
- Mybatis -
- Hibernate -
- Struts2 -
3 开发自定义注解
3.1 注解定义
我们以编程思想中的一个自动生成数据库表例子为例,当然这个例子过于简单并不实用。主要是掌握注解的定义和如何运用反射+注解相关api来编写注解处理器。
1 先定义相关注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
public String name() default "";
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
boolean primaryKey() default false;
boolean allowNull() 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;
}
public @interface Uniqueness {
Constraints constraints() default @Constraints(unique=true);
}
2 给Bean类添加注解
我们希望通过给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 getHandle() {
return handle;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public Integer getAge() {
return age;
}
@Override
public String toString() {
return handle;
}
}
3 注解处理器
定义了注解,在bean类中添加了注解,现在我们必须有处理器处理这些注解。
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); // 获取一下DBTable.class注解对象
if (dbTable == null) { // 如果是空的说明没有获取到
System.out.println("No DBTable annotations n 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;
if (anns[0] instanceof SQLInteger) { // 如果是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())); // getConstraints()
}
if (anns[0] instanceof SQLString) {
SQLString sString = (SQLString) anns[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 Creation SQL for " + className
+ " is :\n" + tableCreate);
}
}
}
/**
* 根据Contraints对象,返回一个约束条件字符串
*
* @param con
* @return 字段约束字符串
*/
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;
}
}
该处理器首先拿到一个要处理的类的全限定名,通过反射Class<?> cl = Class.forName(className);
拿到class对象,通过DBTable dbTable = cl.getAnnotation(DBTable.class);
拿到在类上注解的DBTable注解对象,对其进行解析获得要生成的数据库表名。
通过反射cl.getDeclaredFields()
拿到该类的所有成员,然后遍历每个成员上的注解。代码Annotation[] anns = field.getDeclaredAnnotations();
可以拿到一个成员上的所有注解,返回Annotation数组,本例每个成员上只有一个注解,所以我们直接解析anns[0],先用instanceof判断注解类型后将其强转,然后通过强转得到的注解对象拿到他的各个属性。根据属性我们就可以设置数据库表中字段名称,类型和限制条件了。