注解也被称为元数据,为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后的某个时刻非常方便地使用这些数据。
注解的语法比较简单,除了@符号的使用之外,它基本与java固有的语法一致。在java中,按照下面的格式定义一个注解
public @interface MyAnno {
String name() default "";
}
在定义注解时,需要用到一些元注解,元注解专职负责注解其他的注解,用来说明注解的作用对象以及运行时期等。java目前内置了4种元注解。
注解 | 说明 |
---|---|
@Target | 表示该注解可以用于什么地方。可能的ElementType参数包括: CONSTRUCTOR:构造器的声明 FIELD:域声明(包括enum实例) LOCAL_VARIABLE:局部变量声明 METHOD:方法声明 PACKAGE:包声明 PARAMETER:参数声明 TYPE:类、接口(包括注解类型)或enum声明 |
@Retention | 表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括: SOURCE:注解将被编译器丢弃 CALSS:注解在class文件中可用,但会被VM丢弃 RUNTIME:VM将在运行期也保留注解,因此可以用过反射机制读取注解的信息。 |
@Documented | 将此注解包含在javadoc中。 |
@Inherited | 允许子类继承父类中的注解。 |
注解元素可用的类型有如下几种:
所有的基本类型(int、float、boolean等)
String
Class
enum
Annotation
以上类型的数组
如果使用了其他类型,编译器就会报错。注意,也不允许使用任何包装类型,不过由于自动打包的存在,这不算是什么限制。注解也可以作为元素的类型,也就是说注解可以嵌套,这是一个很有用的技巧。
编译器对元素的默认值有些过分挑剔。
首先,元素不能有不确定的值。也就是说,元素必须要么具有默认值,要么在使用注解时提供元素的值。
其次,对于非基本类型的元素,无论是在源代码中声明时,或是在注解接口中定义默认值时,都不能以null作为其值。这个约束使得处理器很难表现一个元素的存在或缺失状态,因为在每个注解的声明中,所有的元素都存在,并且都具有相应的值。为了绕开这个约束,我们只能自己定义一些特殊的值,例如空字符串或负数,以此表述某个元素不存在。
下面是一个自定义注解的栗子。在这个例子中,自定义了@DBTable注解,用于表明要为有该注解的类生成建表语句,@Constraints、@SQLString、@SQLInteger注解表示数据库表字段相关的属性。四个注解的定义如下:
/**
* 标记类是一张表
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
/**
* 表名
*/
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;
}
/**
* 数据库int类型
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
/**
* 字段名称
*/
String name() default "";
Constraints constraints() default @Constraints;
}
注解有了,当注解被添加到具体的类的时候,还需要对应的处理注解的handle才行,否则,注解是不会生效的。生成sql语句的处理器如下:
/**
* 注解处理器
*/
public class TableCreator {
public static void main(String[] args) throws ClassNotFoundException {
args = new String[]{"com.wgyang.demo.annotation.bean.Member"};
for (String className : args) {
String sql = createSql(className);
System.out.println(sql);
}
}
/**
* 根据注解生成sql语句
*
* @param className 类的全限定名
* @return sql建表语句
* @throws ClassNotFoundException
*/
private static String createSql(String className) throws ClassNotFoundException {
Class<?> clazz = Class.forName(className);
DBTable dbTable = clazz.getAnnotation(DBTable.class);
if (dbTable == null) {
return null;
}
String tableName = dbTable.name();
if (tableName.isEmpty()) {
tableName = clazz.getName().toUpperCase();
}
List<String> columns = new ArrayList<>();
for (Field field : clazz.getDeclaredFields()) {
String columnName = null;
Annotation[] annos = field.getDeclaredAnnotations();
if (annos.length < 1) {
continue;
}
if (annos[0] instanceof SQLInteger) {
SQLInteger sqlInteger = (SQLInteger) annos[0];
if (sqlInteger.name().isEmpty()) {
columnName = field.getName().toUpperCase();
} else {
columnName = sqlInteger.name();
}
columns.add(columnName + " INT" + getConstraints(sqlInteger.constraints()));
}
if (annos[0] instanceof SQLString) {
SQLString sqlString = (SQLString) annos[0];
if (sqlString.name().isEmpty()) {
columnName = field.getName().toUpperCase();
} else {
columnName = sqlString.name();
}
columns.add(columnName + " VARCHAR(" + sqlString.value() + ")" + getConstraints(sqlString.constraints()));
}
}
StringBuilder builder = new StringBuilder("CREATE TABLE " + tableName + "(");
for (String column : columns) {
builder.append("\n ").append(column).append(",");
}
return builder.substring(0, builder.length() - 1) + ");";
}
/**
* 获取字段属性,并根据属性值拼接sql片段
*
* @param con 字段属性
* @return sql语句片段
*/
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;
}
}
对一个实体类添加自定义的注解,注解处理器便可以生成该实体对应的建表语句。
/**
* 数据库表对象,该类添加了建表注解,注解处理器根据类上的注解生成建表语句
*/
@DBTable(name = "MEMBER")
public class Member {
@SQLString(30)
private String firstName;
@SQLString(50)
private String lastName;
@SQLInteger
private Integer age;
@SQLString(value = 30, constraints = @Constraints(primaryKey = true))
private String handle;
}
执行TableCreator类的main方法,输出结果如下:
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30),
LASTNAME VARCHAR(50),
AGE INT,
HANDLE VARCHAR(30) PRIMARY KEY);