注解Annotation又叫元数据,是JDK5中引入的一种以通用格式为程序提供配置信息的方式。使用注解Annotation可以使元数据写在程序源码中,使得代码看起来简洁,同时编译器也提供了对注解Annotation的类型检查,使得在编译期间就可以排除语法错误。
1JDK内置的3中Annotation:
在JDK5中,内置了3个通用目的的注解Annotation,这三个内置的注解在java.lang包下:
(1).@Override:
这个注解常用在继承类或实现接口的子类方法上,表面该方法是子类覆盖父类的方法,该方法的方法签名要遵循覆盖方法的原则:即访问控制权限必能比父类更严格,不能比父类抛出更多的异常。
(2).@Deprecated:
这个注解告诉编译器该元素是过时的,即在目前的JDK版本中已经有新的元素代替该元素。
(3).@SuppressWarnings:
该注解关闭编译器中不合适的警告,即强行压制编译器的警告提示。
2.注解Annotation的定义:
注解Annotation的定义和接口类似,实际上,编译器也把注解Annotationn像接口一样编译成class字节码文件。例如:
import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Test{}
像这种没有任何元素的空注解Annotation叫做标记Annotation.
在声明注解的时候往往需要使用@Target,@Retention等注解,这种注解被称为注解的注解(元数据注解),即是专门用于处理注解Annotation本身的。
(1).@Target注解:
用于指示注解所应用的目标程序元素种类,该注解常和ElementType枚举类型一起联合使用,ElementType枚举提供了java程序中声明的元素类型如下:
(2).@Retention注解:
该注解用于指示所定义的注解类型的注释在程序声明周期中得保留范围,该注解常和RetentionPolicy枚举联合使用。RetentionPolicy枚举常量定义了注解在代码中的保留策略:
3.创建和处理自定义注解Annotation:
正常使用注解时,需要在注解中定义元素,用于接收程序设置的值,正常定义注解的例子如下:
import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface UseCase{ public int id(); public String description() default “no description”; }
正常定义注解Annotation的方式类似定义接口,id和description是注解UseCase的属性,而不是方法,注解中不能定义方法只能定义属性。其中description属性有默认的值“no description“,即在使用时如果没有指定description的值,则程序使用其默认值。
上面UseCase注解用于跟踪方法的测试用例说明,使用上面注解的例子如下:
import java.util.*; public class PasswordUtils{ @UseCase(id = 47, description = “Passwords must contain at least one numeric”) public Boolean validatePassword(String password){ return (password.mathes(“\\w*\\d\\w*”)); } @UseCase(id = 48) public String encryptPassword(Srring password){ return new StringBuilder(password).reverse().toString(); } @UseCase(id = 49, description = “New passwords can’t equal previously used ones”) public Boolean checkForNewPassword(List<String> prevPasswords, String password){ return !prevPasswords.contains(password); } }
JDK5中提供了Annotation相关的API,结合使用java的反射机制可以实现自定义的Annotation注解处理器(JDK中也提供了使用APT,Annotationprocess tool方式处理注解,在后面会讲解),处理上述Annotation的例子如下:
import java.lang.reflect.*; import java.util.*; public class UseCaseTracker{ public static void traceUseCases(List<Integer> useCases, Class<?> clazz){ //获取指定类中所有声明的方法 for(Method m : clazz.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(int i : useCases){ System.out.println(“Warning: Missing use case-” + i); } } public static void main(String[] args){ List<Integer> useCases = new ArrayLis<Integer>(); Collections.addAll(useCases, 47, 48, 49, 50); trackUseCases(useCases, PasswordUtils.class); } }
输出结果:
Found Use Case:47 Passwords must contain at least onenumeric
Found Use Case:48 no description
Found Use Case:49 New Passwords can’t equal previously usedones
Warning: Missing use case-50
注意:使用反射获取到注解对象之后,类似使用调用方法的方式获取注解的值,如uc.id()等。另外,注解不支持继承,因此声明注解时不能使用extends和implements关键字。
4.Annotation注解元素:
Annotation注解中的元素只能是下面的数据类型:
(1).java的8中基本类型,如int, boolean等等,如果可以自动装箱和拆箱,则可以使用对应的对象包装类型。
(2).String类型。
(3).Class类型。
(4).Enums类型。
(5).Annotation类型。
(6).上面类型的数组。
除了上面这些类型以外,如果在注解中定义其他类型的数据,编译器将会报错。
注意:注解中的元素要么指定默认值,要么由使用的类赋值,如果即没有默认值,使用类也没有赋值的话,注解元素是不会像普通类成员变量一样给定默认值,即必须赋值或者显示指定默认值。默认值例子如下:
import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DefaultValue{ public int id() default -1; public String description() default “”; }
5.一个使用Annotation实现ORM的例子:
从EJB3之后,EJB的实体Bean持久化技术被单独抽取出来形成了JPA技术,JPA和Hibernate3之后都支持使用Annotation注解方式进行对象和关系型数据库映射(ObjectRelationship Mapping, ORM),下面使用Annotation注解方式实现一个简单的ORM功能:
(1).相关注解:
import java.lang.annotation.*; @Target(ElementType.TYPE)//该注解只能应用在类上 @Retention(RetentionPolicy.RUNTIME) public @interface DBTable{//指定数据库名称 public 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{//String类型数据 int value() default 0; String name() default “”; Constraints constraints() default @Constraints;//注解的属性元素也是注解 } @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface SQLInteger{//int类型数据 String name() default “”; Constraints constraints() default @Constraints; }
(2).使用Annotation注解的实体类:
@DBTable(name=”MEMBER”) public class Member{ @SQLString(30)//当只指定一个属性的值,并且该属性名为value时,不用写属性名称 String firstName; @SQLString(50) String lastName; @SQLInteger Integer age; @String(value=30, constraints=@Constraints(primaryKey = true)) String handle; static int memberCount; }
注意:当为注解多于1个以上的属性指定值时,即使有value属性也要写value的属性名称。
(3).实现自定义注解处理器:
import java.lang.annotation.*; import java.lang.reflect.*; import java.util.*; public class TableCreator{ public static void mian(String[] args)throws Exception{ Member m = new Member(); Class clazz = m.getClass(); String tableName = null; DBTable dbTable = class.getAnnotation(DBTable.class); if(db != null){ tableName = db.name(); } If(tableName == null){//如果没有指定Table名称,使用类名 tableName = clazz.getName().toUpperCase; } List<String> columnDefs = new ArrayList<String>(); //构造SQL创建列语句 for(Field f : clazz.getDeclaredFields()){//遍历类中声明的所有字段 String columnName = null; //获取当前字段上声明的所有注解 Annotationn[] anns = f.getDeclaredAnnotationns(); if(anns.length() < 1){//当前字段上没有声明注解 continue; } if(anns[0] instanceof SQLInteger){//注解数组第一个定义的是数据类型 SQLInteget sInt = (SQLInteger) anns[0]; if(sInt.name().length() < 1){//如果没有指定列名称,则使用字段名称 columnName = f.getName().toUpperCase(); }else{ columnName = sInt.name(); } columnDefs.add(columnName + “ INT” + getConstraints(sInt.constraints())); } if(anns[0] instanceof SQLString){ SQLString sString = (SQLString) anns[0]; if(sString.name().length() < 1){//如果没有指定列名称,则使用字段名称 columnName = f.getName().toUpperCase(); }else{ columnName = sInt.name(); } columnDefs.add(columnName + “ VARCHAR(” + sString.value() +”)” + getConstraints(sString.constraints())); } } StringBuilder createCommand = new StringBuilder(“CREATE TABLE” + tableName + “(”); for(String columnDef : columnDefs){ createCommand.append(“\n ” + columnDef + “,”); } //删除最后的”,” String tableCreate = createCommand.subString(0, createCommand.length() - 1) + “);”; System.out.println(“Table creation SQL for ” + className + “ is :\n” + tableCreate); } //获取约束 Private static String getConstraints(Constraints con){ String constraints = “”; if(!con.allowNnull()){ constraints += “ NOT NULL”; } if(con.primaryKey()){ constraints += “ PRIMARY KEY”; } if(con.unique()){ constraints += “ UNIQUE”; } return constraints; } }
输出结果:
Table creation SQL for Member is:
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30),
LASTNAME VARCHAR(50),
AGE INT,
HANDLE VARCHAR(30) PRIMARY KEY);
6.使用apt处理Annotation注解:
Annotation processing tool, apt是sun提供的第一个版本的Annotation注解处理器,apt的使用和javac类似,是真的未编译的源码,而非已经编译的class字节码文件,使用apt的时候不能使用java的反射机制,因为源码尚未编译,需要使用mirrorAPI,mirror API可以使得apt看到未编译源代码中的方法,字段和类信息。使用apt的例子如下:
(1).注解:
package annotations; import java.lang.annotation.*; @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface ExtractInterface{ public String value(); }
这个注解用于从使用该注解的类中抽象公共的方法,并为该类生成一个接口。
(2).使用注解的类:
package annotations; @ExtractInterface(“IMultiplier”) public class Multiplier{ public int multiply(int x, int y){ int total = 0; for(int i = 0; I < x; i++){ total = add(total, y); } return total; } private int add(int x, int y){ return x + y; } }
(3).注解处理器:
package annotations; import com.sun.mirror.apt.*; import com.sun.mirror.declaration.*; import java.io.*; import java.util.*; public class InterfaceExtractorProcessor implements AnnotationProcessor{ //注解处理器的工作环境 private final AnnotationProcessorEnvironment env; private List<MethodDeclaration> interfaceMethods = new ArrayList< MethodDeclaration>(); public InterfaceExtractorProcessor(AnnotationProcessEnvironment env){ this.env = env; } public void process(){ //查询注解处理器环境中的类型声明 for(TypeDeclaration typeDecl : env.getSpecifiedTypeDeclarations()){ //获取注解 ExtractInterface annot = typeDecl.getAnnotation(ExtractInterface.class); if(annot == null){ break; } //遍历所有添加注解的方法 for(MethodDeclaration m : typeDecl.getMethods()){ //方法签名中的访问控制符是public的,且不是静态方法 if(m.getModifiers().contains(Modifier.PUBLIC) && !(m.getModifiers().contains(Modifier.STATIC))){ interfaceMethods.add(m); } } if(interfaceMethods.size() > 0){ try{ PrintWriter writer = env.getFiler().createSourceFile(annot.value()); writer.println(“package ” + typeDecl.getPackage().getQualifiedName() + “;”); writer.println(“public interface ” + annot.value() + “{“); //写方法声明 for(MethodDeclaration m : interfaceMethods){ writer.print(“ public’); writer.print(m.getReturnType() + “ ”); writer.print(m.getSimpleName() + “ ”); int i = 0; //写方法参数列表 for(ParametherDeclaration parm : m.getParameters()){ writer.print(parm.getType() + “ ” + parm.getSimpleName()); if( ++i < m.getParameters().size()){ writer.print(“, ”); } } writer.println(“);”) } writer.println(“}”); writer.close(); }catch(Exception e){ Throw new RuntimeException(e); } } } } }
使用sun的mirror API可以获取源码中的Field, type, method等信息。
(4).为apt工厂提供注解处理器:
package annotations; import com.sun.mirror.apt.*; import com.sun.mirror.declaration.*; import java.util.*; public class InterfaceExtractorProcessorFactory implements AnnotationProcessorFactory{ //获取注解处理器 public AnnotationProcessor getProcessorFor(Set<AnnotationTypeDeclaration> atds, AnnotationProcessorEnvironment env){ return new InterfaceExtractorProcess(env); } //定义注解处理器所支持的注解类型 public Collection<String> supportedAnnotationTypes(){ return Collections.singleton(“ExtractInterface”); } //定义注解处理器支持的选项 public Collection<String> supportedOptions(){ return Collections.emptySet(); } }
(5).运行apt:
使用下面的命令允许apt:
apt-factory annotations.InterfaceExtractorProcessorFactory Multiplier.java –s ../annotations
输出结果:
package annotations;
public interface IMultiplier{
public int multiply(intx, int y);
}