转载请注明出处: 西木的博客
注解是众多引入Java SE5中的重要语言变化之一。它们可以提供用来完整地描述程序所需的信息,而这些信息是无法用Java来表达的。因此,注解使得我们可以将由编译器来测试和验证的格式,存储有关程序的额外信息。在Android开发中,大量的基础框架都用到了注解机制,我们也可以编写自己的注解,来加快我们程序的开发效率。这一讲,我们就先来回顾一下注解的基本原理,为后续的章节打下基础:
1.内置注解
JavaSE中内置了三种注解,定义在java.lang中:
- @Override 表示当前的方法定义将覆盖超类中的方法
- @Deprecated 如果程序元使用了注解为它的元素,编译器将发出警告
- @SuppressWarnings 关闭不当的编译器警告信息
Java还另外提供了四种注解,专门负责新注解的创建。稍后我们将学习它们。
2. 定义注解
我们先来看一个最简单的定义注解的例子
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{
public int value();
}
注解的定义看起来很像接口的定义,注解定义需要注意以下几点:
- 定义注解时,会需要一些元注解,入@Target和@Retention, @Target用来定义你的注解将应用于什么地方,@Retention用来定义注解在哪一级别可用(Source, Class 或 Runtime)
- 注解的名称前使用@interface关键字,这个关键字声明隐含了一个信息:它是继承了java.lang.annotation.Annotation接口,并非声明了一个interface
- 注解可以定义一些元素,看起来像接口中的方法,注解的元素方法必须无参数,无异常抛出。并且可以后面可以用default 加上一个默认值
- 如果注解中的元素没有提供默认值,则注解在使用时必须为其提供值
- 如果注解中定义了名为value的元素,并且在应用该注解的时候,该元素是唯一需要负值的元素,那么此时无需使用名-值对的形式,只需在括号内给出value元素的值即可
- 注解元素可用的类型有限,仅为:[基本类型, String,Class,enum,Annotation,以及以上类型的数组],如果使用其他任何自定义类型就会报错。
- 默认值限制: 元素的默认值不能是不确定的值,对于非基本类型,不能以null作为其值。
3.注解的使用
我们先来看一个稍微复杂点的例子:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase{
public int id();
public String description() default "no description";
}
我们定义了一个简单的注解,用它来跟踪一个项目中的用例。如果一个方法或一组方法实现了某个用例的需求,那么程序员可以为此方法加上该注解。
我们来看该注解如何使用:
class PassWordUtils
{
@UseCase(id = 47, description = "password must contain at least one character")
public boolean validatePassword(String password)
{
return password.matches("\\w+\\d\\w*");
}
@UseCase(id = 48)
public String encryptPassword(String password){
return new StringBuilder(password).reverse().toString();
}
}
这是一个验证password的实用类,它的两个函数都用UseCase标记了,我们可以看到,第二个case,我们没有提供 description元素值,因为它声明时,包括了默认值。
4.注解处理器
如果没有注解处理器,那么注解实际上不能发挥任何作用,大多数时候,程序员主要是定义自己的注解,并编写注解处理器来处理它们。以上述UseCase为例,我们可以编写一个处理器,找到PasswordUtils类中,所有声明的用例,输出它们。
class UseCaseTracker{
public static void trackUseCases(Class<?> cl)
{
for(Method m: cl.getDeclaredMethods()){
UseCase uc = m.getAnnotation(UseCase.class);
if(uc != null){
System.out.println("Found use case: " + uc.id() + " "
+ uc.description()+ ", method: "+ m.getName());
}
}
}
}
public class Launcher{
public static void main(String[] args) {
UseCaseTracker.trackUseCases(PassWordUtils.class);
}
}
我们首先通过 cl.getDeclaredMethods 拿到类中声明的所有方法,然后依次遍历这些方法,拿到类型为UseCase的annotation,如果拿到,就通过注解的元素方法输出注解上标注的值。
5.注解嵌套
我们依然以一个例子开始演示注解嵌套的使用:
//Constraints 注解代表对SQL列元素限制修饰
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
boolean primaryKey() default false;
boolean allowNull() default true;
boolean unique() default false;
}
//代表类型为String的SQL表列
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
//length of varchar
int value() default 0;
String name() default "";
Constraints constraints() default @Constraints;
}
大家可以看到我们定义的SQLString注解中,嵌套了类型为Constraints注解的元素,而且它的默认值为@Constraints,这实际上就是一个所有元素都为默认值的@Constraints注解,如果我们想让默认值得unique位true,我们可以写成:
Constraints constraints() default @Constraints(unique = true);
我们来看看如何使用嵌套注解呢:
@DBTable(name = "MEMBER")
public class Member {
@SQLString(30)
String firstName;
@SQLString(40)
String lastName;
@SQLString(value = 40, constraints = @Constraints(primaryKey = true))
String token;
}
我们可以看到对于嵌套注解的使用,还是比较麻烦的,我们构造出一个类型为Constraints注解的元素,赋给SQLString中得constraints元素。
我们希望通过声明一个Member类,来创建一个数据库表,所有加注解得field位表中的列,我们应该再创建一个注解处理器,这个处理器可以分析Member类,从而创建数据库表。
public class TableCreator {
public static void main(String[] args) {
// TODO Auto-generated method stub
createTable(Member.class);
}
public static void createTable(Class<?> clazz)
{
//1.找到数据库表注解和name元素
DBTable dbTable = clazz.getAnnotation(DBTable.class);
if(dbTable == null){
System.out.println("No dbtable annotations in class " + clazz.getName());
return;
}
String tableName = dbTable.name();
if(tableName.length() < 1){
tableName = clazz.getName().toUpperCase();
}
ArrayList<String> columnsDefs = new ArrayList<>();
//2.遍历所有field
for(Field field : clazz.getDeclaredFields())
{
String columnName = null;
Annotation[] anns = field.getDeclaredAnnotations();
if(anns.length < 1){
continue;
}
//找到SQLString类型的注解
if(anns[0] instanceof SQLString){
SQLString sString = (SQLString)anns[0];
if(sString.name().length() < 1){
columnName = field.getName().toUpperCase();
}else{
columnName = sString.name();
}
//添加列定义
columnsDefs.add(columnName + " VARCHAR("+sString.value()+")"
+getConstraints(sString.constraints()));
}
}
StringBuilder createCommand = new StringBuilder("CREATE TABLE "+tableName +"(");
for(String columnDef : columnsDefs){
createCommand.append("\n "+columnDef + " ");
}
String tableCreate = createCommand.substring(0, createCommand.length() - 1) + ")";
System.out.println("Table creation sql for "+ clazz.getName() + " 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;
}
}
main函数执行最后的输出为:
Table creation sql for com.junli.annotation.Member is :
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30)
LASTNAME VARCHAR(40)
TOKEN VARCHAR(40) PRIMARY KEY)
createTable方法会对传入的Class,检查该类是否带有@DBTable注解, 如果有,就将发现的表名保存下来。然后读取这个类的所有域,对于SQLString注解的域使用对应的处理块构造出相应地column名,对于嵌套的Constraints注解则传递给getConstraints方法,构造出一个含有SQL约束的String对象。
好了,到这里对Java中注解的基本讲解就到这里,下一节中,我们将结合实际的例子分析注解的巧妙用处。