四、运行时注解处理器
了解完注解与反射的相关API后,现在通过一个实例来演示利用运行时注解来组装数据库SQL的构建语句的过程
/** * 表注解 */ @Target(ElementType.TYPE)//只能应用于类上 @Retention(RetentionPolicy.RUNTIME)//保存到运行时 public @interface DBTable { String name() default ""; } /** * 注解Integer类型的字段 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface SQLInteger { //该字段对应数据库表列名 String name() default ""; //嵌套注解 Constraints constraint() default @Constraints; } /** * 注解String类型的字段 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface SQLString { //对应数据库表的列名 String name() default ""; //列类型分配的长度,如varchar(30)的30 int value() default 0; Constraints constraint() default @Constraints; } /** * 约束注解 */ @Target(ElementType.FIELD)//只能应用在字段上 @Retention(RetentionPolicy.RUNTIME) public @interface Constraints { //判断是否作为主键约束 boolean primaryKey() default false; //判断是否允许为null boolean allowNull() default false; //判断是否唯一 boolean unique() default false; } /** * 数据库表Member对应实例类bean */ @DBTable(name = "MEMBER") public class Member { //主键ID @SQLString(name = "ID",value = 50, constraint = @Constraints(primaryKey = true)) private String id; @SQLString(name = "NAME" , value = 30) private String name; @SQLInteger(name = "AGE") private int age; @SQLString(name = "DESCRIPTION" ,value = 150 , constraint = @Constraints(allowNull = true)) private String description;//个人描述 //省略set get..... }
上述定义4个注解,分别是@DBTable(用于类上)、@Constraints(用于字段上)、 @SQLString(用于字段上)、@SQLString(用于字段上)并在Member类中使用这些注解,这些注解的作用的是用于帮助注解处理器生成创建数据库表MEMBER的构建语句,在这里有点需要注意的是,我们使用了嵌套注解@Constraints,该注解主要用于判断字段是否为null或者字段是否唯一。必须清楚认识到上述提供的注解生命周期必须为@Retention(RetentionPolicy.RUNTIME),即运行时,这样才可以使用反射机制获取其信息。有了上述注解和使用,剩余的就是编写上述的注解处理器了,前面我们聊了很多注解,其处理器要么是Java自身已提供、要么是框架已提供的,我们自己都没有涉及到注解处理器的编写,但上述定义处理SQL的注解,其处理器必须由我们自己编写了,如下
import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; /** * 运行时注解处理器,构造表创建语句 */ public class TableCreator { public static String createTableSql(String className) throws ClassNotFoundException { Class<?> cl = Class.forName(className); DBTable dbTable = cl.getAnnotation(DBTable.class); //如果没有表注解,直接返回 if(dbTable == null) { System.out.println( "No DBTable annotations in class " + className); return null; } 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>(); //通过Class类API获取到所有成员字段 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]; //获取字段对应列名称,如果没有就是使用字段名称替代 if(sInt.name().length() < 1) columnName = field.getName().toUpperCase(); else columnName = sInt.name(); //构建语句 columnDefs.add(columnName + " INT" + getConstraints(sInt.constraint())); } //判断String类型 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.constraint())); } } //数据库表构建语句 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) + ");"; return tableCreate; } /** * 判断该字段是否有其他约束 * @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; } public static void main(String[] args) throws Exception { String[] arg={"com.zejian.annotationdemo.Member"}; for(String className : arg) { System.out.println("Table Creation SQL for " + className + " is :\n" + createTableSql(className)); } /** * 输出结果: Table Creation SQL for com.zejian.annotationdemo.Member is : CREATE TABLE MEMBER( ID VARCHAR(50) NOT NULL PRIMARY KEY, NAME VARCHAR(30) NOT NULL, AGE INT NOT NULL, DESCRIPTION VARCHAR(150) ); */ } }
如果对反射比较熟悉的同学,上述代码就相对简单了,我们通过传递Member的全路径后通过Class.forName()方法获取到Member的class对象,然后利用Class对象中的方法获取所有成员字段Field,最后利用field.getDeclaredAnnotations()遍历每个Field上的注解再通过注解的类型判断来构建建表的SQL语句。这便是利用注解结合反射来构建SQL语句的简单的处理器模型,是否已回想起Hibernate?
五、Java 8中注解增强
1、元注解@Repeatable
元注解@Repeatable是JDK1.8新加入的,它表示在同一个位置重复相同的注解。在没有该注解前,一般是无法在同一个类型上使用相同的注解的
//Java8前无法这样使用 @FilterPath("/web/update") @FilterPath("/web/add") public class A {}
Java8前如果是想实现类似的功能,我们需要在定义@FilterPath注解时定义一个数组元素接收多个值如下
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface FilterPath { String [] value(); }
//使用 @FilterPath({"/update","/add"}) public class A { }
但在Java8新增了@Repeatable注解后就可以采用如下的方式定义并使用了