注解(也被称为元数据),为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的使用这些数据。
注解在一定程度上是把元数据和源代码结合在一起。
java5.0内置了三种标准注解
@override:表示当前的方法将覆盖超类中的方法。
@Depercated:如果程序员使用了该注解,那么编译器会发出警告信息。
@SuppressWarnings:关闭不当的编译器警告信息。
一、基本语法
注解的定义和接口类似,和其他java类一样也会被编译成class文件。
package annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{}
1. 元注解
定义注解时需要会一些元注解(meta-annotation),Java5.0定义了4个标准的meta-annotation类型
元注解 | 说明 |
---|---|
@Target | 表示该注解可以用到哪些地方,可以指定一个enum的值,也可以用逗号分隔的方式指定多个值(如果想要将注解应用于所有的ElementType,可以省去@Target元注解,不过这种情况不常见),可以选择的ElementType参数有: CONSTRUCTOR:构造器的声明 FIELD:域声明(包括enum实例) LOCAL_VARIABLE:局部变量声明 METHOD:方法声明 PACKAGE:包声明 PARAMMETER:参数声明 TYPE:类、接口(包括注解类型)或enum声明 |
@Retention | 表示需要在什么级别保存该注解信息。可选参数RetentionPolicy参数包括: SOURCE:注解将被编译器丢弃。 CLASS:注解在class文件中可用,但在VM中将被丢弃。 RUNTIME:VM将在运行期也保留注解,因此可以通过反射机制读取注解的信息。 |
@Documented | 将注解包含在javadoc中 |
@Inherited | 允许子类继承父类的注解 |
2. 注解元素:
package annotation;
import java.lang.annotation.*;
@Target(value={ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
public int id();
public String description() default "no description";
}
标签@UseCase由UseCase.java定义,其中包含int类型元素id和String类型元素description。
注解可用的类型如下:
- 所有基本类型(int、float、boolean等)
- String类型
- enum类型
- Annotation类型
- 以上类型的数组
注:也不允许使用任何包装类型
没有元素的注解被称为标记注解(marker annotation),例如上例中的@Test。
3. 默认值限制
元素不能有不确定的值,也就是说元素要么有默认值,要么在使用注解时提供元素的值。
对于非基本类型的值,无论是源代码声明时还是在注解接口中定义默认值时都不能使用null。这个约束是的处理器很难表现一个元素的存在或缺失状态,因为在每个元素的注解声明中,所有的元素都存在,并且都具有相应的值,为了绕开这个约束,我们只能自己定义一些特殊的值,如空字符串或负数等以此表示该元素不存在。
package annotation;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Null {
public int id() default 0;
public String description() default "";
}
二、注解处理器
如果没有用来读取注解的工具,那注解也不会比注释更有用。使用注解的过程,最重要的就是编写和使用注解处理器
Java5.0扩展了反射机制的API,以帮助程序员构造这类工具。
下面是一个简单的注解处理器,它将通过反射机制读取PasswordUtils类中的标记。
package annotation;
import java.lang.reflect.Method;
import java.util.*;
public class UseCaseTracker {
public static void trackerUseCase(List<Integer> useCases, Class<?> c) {
for (Method m : c.getDeclaredMethods()) {
UseCase uc = m.getAnnotation(UseCase.class);
if (uc != null) {
System.out.println("Found use case:" + uc.id() + " " + uc.description());
useCases.remove(new Integer(uc.id()));
}
}
for (Integer u : useCases) {
System.out.println("Missing use case-" + u);
}
}
public static void main(String[] args) {
List<Integer> useCases = new ArrayList<Integer>();
Collections.addAll(useCases, 47, 48, 49, 50);
UseCaseTracker.trackerUseCase(useCases, PasswordUtils.class);
}
}
这个程序用到了两个反射的方法:getDeclaredMethods()和getAnnotation(),它们都属于AnnotatedElement接口(Class、Field、Method等都实现了该接口)。getAnnotation():返回指定类型的注解对象,这里是UseCase。如果方法上没有该类型的注解,那么返回null值。然后我们通过id()和description()方法从UseCase对象中获取元素的值。
package annotation;
import java.util.List;
public class PasswordUtils {
@UseCase(id=47, description="Passwords must contain at least one numeric")
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();
}
@UseCase(id=49, description="New passwords can not equal previously used ones")
public boolean checkForNewPassword(List<String> prePasswords, String password) {
return !prePasswords.contains(password);
}
}
运行结果:
Found use case:47 Passwords must contain at least one numeric
Found use case:48 no description
Found use case:49 New passwords can not equal previously used ones
Missing use case-50
encryptPassword没有指定description元素的值,因此处理器处理它对应的注解时通过description()方法获取的是默认值no description。
三、例子
假设你希望提供简单的对象/关系映射功能,能够自动生成数据库表,用来存储JavaBean对象。你可以使用XML描述文件,指明类的名字、每个成员以及数据库映射的相关信息。然而如果使用注解的话就可以将所有信息都保存到JavaBean源文件中。
为此我们需要一些新的注解
DBTable.java告诉处理器需要给我生成一个数据库表
package annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
public String name() default "";
}
注:@DBTable有一个元素name,通过这个元素为处理器创建数据库表提供表名。
下面为修饰JavaBean域提供的注解
package annotation;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Contraints {
boolean primaryKey() default false;
boolean allowNull() default true;
boolean unique() default false;
}
package annotation;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
int value() default 0;
String name() default "";
Contraints contrants() default @Contraints;
}
package annotation;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
String name() default "";
Contraints contrants() default @Contraints;
}
上面这两个SQL类型具有name()和contrants()元素,后者用到了嵌套注解的功能
package annotation;
@DBTable(name="MEMBER")
public class Member {
@SQLString(30) String firstName;
@SQLString(50) String lastName;
@SQLInteger Integer age;
@SQLString(value=30, contrants=@Contraints(primaryKey=true))
String handle;
static int memberCount;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getHandle() {
return handle;
}
public void setHandle(String handle) {
this.handle = handle;
}
@Override
public String toString() {
return handle;
}
}
可以使用多种不同的方式来定义自己的注解,上面只定义了String和Integer的注解,还有很多其他类型如果一一定义会很繁琐,这时可以使用@TableColumn单一的注解类,它包含一个enum元素,该枚举类定义了STRING,INTEGER,FLOAT…等,这就消除了所有SQL类型都需要定义一个注解类的负担。
一个目标可以使用多个注解,但是同一注解不能重复使用。
package annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
public class TableCreator {
public static void main(String[] args) throws ClassNotFoundException {
/*if (args.length < 1) {
System.out.println("arguments: annotation classes");
System.exit(0);
}*/
args = new String[]{"annotation.Member"};
for (String className : args) {
Class<?> c = Class.forName(className);
DBTable table = c.getAnnotation(DBTable.class);
if (table == null) {
System.out.println("NO DBTable in classes: " + className);
continue;
}
String tableName = table.name();
List<String> columnDefs = new ArrayList<String>();
for (Field f : c.getDeclaredFields()) {
String columnName = null;
Annotation[] annos = f.getAnnotations();
if (annos.length < 1)
continue;
if (annos[0] instanceof SQLString) {
SQLString sqlString = (SQLString) annos[0];
columnName = sqlString.name();
if (columnName.length() < 1) {
columnName = f.getName().toUpperCase();
}
columnDefs.add(columnName + " VARCHAR(" + sqlString.value() + ")" + getContraints(sqlString.contrants()));
}
if (annos[0] instanceof SQLInteger) {
SQLInteger sqlInt = (SQLInteger) annos[0];
columnName = sqlInt.name();
if (columnName.length() < 1) {
columnName = f.getName().toUpperCase();
}
columnDefs.add(columnName + " INT" + getContraints(sqlInt.contrants()));
}
}
StringBuffer buf = new StringBuffer("CREATE TABLE " + tableName + "(\n");
for (String column : columnDefs) {
buf.append(column + ",\n");
}
buf.substring(0, buf.length()-1);
buf.append(");");
System.out.println(buf);
}
}
/**
* 获取约束
* @param contrants
* @return
*/
private static String getContraints(Contraints con) {
String contraints = "";
if (con.primaryKey())
contraints += " PRIMARY KEY";
if (con.allowNull())
contraints += " NULL";
if (con.unique())
contraints += " UNIQUE";
return contraints;
}
}
运行结果:
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30) NULL,
LASTNAME VARCHAR(50) NULL,
AGE INT NULL,
HANDLE VARCHAR(30) PRIMARY KEY NULL,
);