声明注解与元注解
当我们需要自定义注解时,我们需要元注解(描述注解的注解)来协助,Java提供了四种基本的元注解,这四种注解分别为@Target, @Retention,@Documented,@Inherited。
1.@Target
表示注解应该应用到什么地方。
而对于@Target本身的定义
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
它的作用范围就是注解上,而因为它是个元注解,所以可以用来表述自己。其实真正的写法是@Target(value = ElementType.METHOD。这里简写的原因是因为Target类中有一个value()属性,如果定义成别的变量,则必须要写全了。
2.@Retention
用来约束注解的生命周期,分别有三个值,源码级别(source),类文件级别(class),运行时级别(runtime),其含有如下:
其他两个注解,@Documented的注解会包含在JavaDoc中;@Inherited表明该注解允许子类继承父类中的注解。
自定义注解
事先说明一下,Java及第三方框架已经为我们提供了大量的注解,我们其实没必要自己定义注解使用,除非真的没有什么可以满足我们的注解,这时可以考虑自己定义。
例如:
@Retention(RUNTIME)//保留到运行时
@Target(TYPE)//作用在类上
public @interface DBTable {
String name() default "";
}
定义一个注解和定义一个interface非常相似,只不过多了一个@符号。在定义注解时,使用元注解,比如@Target、 @Retention来表明注解的应用范围,生命周期。
而定义注解时,可以看到定义体里面包含了一些比较特殊的方法。这些方法称为配置参数。只能是public或者default访问权限,方法的返回值就是配置参数的类型,并且我们可以通过default关键字指定默认值,但要注意不可为null,所以这里使用了””;
配置参数可以使用的类型如下:
所有的基本类型。
String
Class
enum
Annotation
以上类型的数组
定义注解时要注意:
1. 注解可以嵌套。
2. 注解不可继承,因为定义注解时,已经默认继承了java.lang.annotation.Annotation。
3. 配置参数必须要提供确定的值,要不使用时提供,要不定义时提供默认值,绝不能为null.
4. 注解中如果定义了名为value的配置参数(不论value为何类型),该参数是唯一需要赋值的一个参数,直接在括号内给出value的值就可以了。否则配置必须以key-value的形式提供。
注解与反射
Java里有一个AnnotatedElement接口,通过官方文档可以看到Class, Constructor, Executable, Field, Method, Package, Parameter都实现了这个接口,这就意味着我们可以通过反射获取在这上面应用的注解。但是因为使用的反射,所以每个注解的生命周期约束必须为Runtime。
一个经典的例子就是通过注解为一个Bean创建表。
例如:
@DBTable(name = "user")
public class User {
@SQLString(name = "user_id", length = 10,constraints = @Constraint(primaryKey = true))
private String id;
@SQLString(name = "user_name",length = 40)
private String name;
//省略
}
对于上面这个简单Java类,我们希望当扫描到它时,能直接生成一张表,其中DBTable来指定表的名称,SQLString指定列的名称,当我们没有显示提供配置参数的值时,它将使用类名作为表名、属性名作为列名。Constraint是一个列的约束,例如来说明是否为主键、是否允许为空等。
具体定义如下:
@Retention(RUNTIME)//保留到运行时
@Target(TYPE)//作用在类上
public @interface DBTable {
//指定数据表的名称
String name() default "";
}
@Retention(RUNTIME)
@Target(FIELD)
public @interface SQLString {
//指定列名称
String name() default "";
//指定长度,例如varchar(20)
int length() default 0;
//列约束
Constraint constraints() default @Constraint;
}
@Retention(RUNTIME)
@Target(FIELD)
public @interface Constraint {
// 是否作为主键约束
boolean primaryKey() default false;
// 是否允许为null
boolean allowNull() default false;
// 是否唯一
boolean unique() default false;
}
然后就可以通过反射编写一个处理器了。
public static String createSql(String className) throws Exception {
if (className == null || className.length() < 1) {
throw new NullPointerException("the className must be not null!");
}
//加载类
Class> cl = Class.forName(className);
DBTable dbTable = Objects.requireNonNull(cl.getAnnotation(DBTable.class));
String tableName = dbTable.name();
if (tableName.length() < 1) {// 判断配置参数是否为默认值,只需判断是否小于1即可,他不可能为null
tableName = cl.getName().toUpperCase();
}
List cols = new ArrayList<>();
for (Field field : cl.getDeclaredFields()) {
String colName = null;// 每次遍历需要重新设置colName的值
Annotation[] annos = field.getAnnotations();
if (annos.length < 1) {
continue; // 如果一个属性上没有注解,就继续遍历下一个
}
if (annos[0] instanceof SQLString) {// 每个属性上只用了一个注解,如果有多个需要在此循环遍历
SQLString sqlString = (SQLString) annos[0];
if (sqlString.name().length() < 1) { // 判断是否显示的提供了属性配置
colName = field.getName().toUpperCase();
} else {
colName = sqlString.name();
}
cols.add(colName + " varchar(" + sqlString.length() + ")" + getConstraints(sqlString.constraints()));
}
} // 遍历注解结束
StringBuilder sb = new StringBuilder();
sb.append("create table " + tableName + " (");
for (String col : cols) {
sb.append("\n" + col + ",");//注意这里的逗号,如果留空格,最后剪掉的时候要注意多裁剪一位
}
String tableCreate = sb.substring(0, sb.length() - 1) + ")";// 去掉最后一个逗号
return tableCreate;
}
//这个方法用来获取约束
private static String getConstraints(Constraint constraint) {
String strCon = "";// 默认约束为"",注意不是null!sql里约束没有null
if (!constraint.allowNull()) {
strCon += " not null ";// 注意前后要留有空格
}
if (constraint.primaryKey()) {
strCon += " primary key ";
}
if (constraint.unique()) {
strCon += " unique ";
}
return strCon;
}
注:如果你使用了一个可重复注解,即@Repeatable来定义你要处理的注解,那么请在处理器中使用getDeclaredAnnotationsByType() 和 getAnnotationsByType()这两个两个方法。
我们是通过运行时(Runtime)反射机制来处理注解,这就要求你必须使用Runtime,然而很多框架都可以在编译处理注解。这个过程也不算复杂,只需要我们自定义注解处理器( Processor)即可。下一篇我将介绍如何在编译期间处理注解,并将你定义的处理器注册到编译器中。