java编程思想--20注解

260注解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程序中声明的元素类型如下:
ANNOTATION_TYPE:注释类型声明。
CONSTRUCTOR:构造方法声明。
FIELD:字段声明(包括枚举常量)。
LOCAL_VARIABLE:局部变量声明。
METHOD:方法声明。
PACKAGE:包声明。
PARAMETER:参数声明。
TYPE::类,接口或枚举声明。
(2).@Retention注解:
该注解用于指示所定义的注解类型的注释在程序声明周期中得保留范围,该注解常和RetentionPolicy枚举联合使用。RetentionPolicy枚举常量定义了注解在代码中的保留策略:
CLASS:编译器把注解记录在类文件中,但在运行时JVM不需要保留注解。
RUNTIME:编译器把注解记录在类文件中,在运行时JVM将保留注解,因此可以通过反射机制读取注解。
SOURCE:仅保留在源码中,编译器在编译时就要丢弃掉该注解。
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);
}


正文

1.什么是注解

注解(也叫元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。

2.注解的优点

1.可以提供用来完整地描述程序所需的信息(这些信息无法用Java表达)-> 以将由编译器来测试和验证的格式存储有关程序的额外信息。
2.可以用来生成描述符文件或是新的类的定义->减轻编写"样板"代码的负担
3.更加干净易读的代码以及编译器类型检查
3.注解的语法

@符号的使用,其他和Java固有语法一样

Java SE5内置了三种,定义在java.lang中的注解:

@Override 当前的方法定义将覆盖父类(超类)中的方法。
@Deprecated 被注解的元素被取代,不推荐使用( http://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/deprecation/deprecation.html)
@SuppressWarnings 关闭不当的编译器警告信息

Java提供四种注解,专门负责新注解的创建。这种注解叫做元注解。

1.@Target 表示该注解可以用于什么地方。
ElementType参数包括:
public enum ElementType {
    //类、接口(包括注解类型)或是enum声明
    TYPE,
    //域声明(包括enum实例)
    FIELD,
    //方法声明
    METHOD,
    //参数声明
    PARAMETER,
   //构造器声明
    CONSTRUCTOR,
    //局部变量声明
    LOCAL_VARIABLE,
     //注解类型声明
    ANNOTATION_TYPE,
    //包声明
    PACKAGE,
    //类型参数声明(Java1.8开始使用)
    TYPE_PARAMETER,
    //类型使用(Java1.8开始使用)
    TYPE_USE
}
2.@Retention 表示需要在什么级别保存该注解信息。
RetentionPolicy参数包括
public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     *注解将被编译器丢弃
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     *注解被编译器保存在类文件中。但在运行的时候没有被VM保存
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     *  注解将被编译器保存在类文件中,在运行的时候也会被VM保存。因此可通过反射机制读取注解的信息
     */
    RUNTIME
}
3.@Documented 将此注解包含在Javadoc中

4.@Inherited 允许子类继承父类中的注解

定义注解的语法

package com;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Created by JohnTsai on 15/11/7.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}
注解的定义很像一个空的接口。定义注解时,会需要一些元注解(meta-annotation,如上所示)
在注解中,一般都会包含一些元素以表示某些值。分析处理注解时,程序或工具可以利用这些值。
注解的元素看起来就像接口的方法,唯一的区别是可以为它设置默认值。

package com;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Created by JohnTsai on 15/11/7.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
    public int id();
   //default设置默认值
    public String description() default "no description";
}
使用注解的语法

package com;

/**
* Created by JohnTsai on 15/11/7.
*/
public class Testable {
    public void execute(){
        System.out.println("Executing...");
    }
    @Test void testExcute(){
        execute();
    }
}
注解的元素在使用时表现为名-值对的形式。

package com;

/**
* Created by JohnTsai on 15/11/7.
*/
public class PasswordUtils {
    @UseCase(id = 47, description = "Passwords must cantain 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();
    }
}
4.什么时候使用注解

每当创建描述符性质的类或接口时,如果包含了重复性的工作,就可以考虑使用注解来简化与自动化该过程。

5.编写注解处理器

使用注解很重要的一部分就是创建与使用注解处理器。Java SE5拓展了反射机制的API(构造这类工具),还提供了外部工具apt帮助我们解析带有注解的Java源代码。

package com;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
* Created by JohnTsai on 15/11/8.
*/
public class UseCaseTracker {
    public static void trackUseCases(List<Integer> useCases,Class<?> clz){
        for(Method m :clz.getDeclaredMethods()){
            UseCase useCase = m.getAnnotation(UseCase.class);
            if(useCase!=null){
                System.out.println(useCase.id()+useCase.description());
                useCases.remove(new Integer(useCase.id()));
            }
        }
        for(int i:useCases){
            System.out.println("Warning:Missing use case -"+i);
        }
    }

    public static void main(String[] args) {
        List<Integer> useCases = new ArrayList<>();
        Collections.addAll(useCases,47,48,49);
        trackUseCases(useCases,PasswordUtils.class);
    }
}

//output
/*
47Passwords must cantain at least one numeric!
48no description
Warning:Missing use case -49
*/
使用了两个反射的方法:getDeclaredMethods()和getAnnotation()(这两个方法都属于AnnotatedElement接口,Class,Method和Field等类都实现了这个接口)

注解元素

注解元素可用的类型如下:

1.所有的基本类型(如int,float,boolean等等)
2.String
3.Class
4.enum
5.Annotation
6.以上类型的数组
默认值限制

编译器对元素的默认值有限制。
不能有不确定的值
(即要么具有默认值,要么使用注解时提供的元素的值)
我们可以查看AndroidAnnotations中的@ViewById的源码,查看它的写法,就能看出默认值的写法:

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface ViewById {
    int value() default ResId.DEFAULT_VALUE;

    String resName() default "";
}

注解用到两个反射方法

getDeclaredMethods() 和getAnnotation(),他们都属于AnnotatedElement接口(Class,Method与Field等类都实现了该接口)
getDeclaredAnnotations和getAnnotations的区别

Annotation[] anns = field.getDeclaredAnnotations();
Annotation[] anns = field.getAnnotations();
这两个方法有什么区别是:
虽然返回的类型是一样的,但是 getDeclaredAnnotations返回的是具体的声明的注解;
field.getAnnotations()使用的Field类的父类的AccessibleObject的getAnnotations方法;getAnnotations方法返回getDeclaredAnnotations方法;
getDeclaredAnnotations方法的内容是throw new AssertionError("All subclasses should override this method");
即是AccessibleObject类的所有子类必须覆盖这个方法。即正确的调用方式应该是调用子类Field的getDeclaredAnnotations方法。
getDeclaredFields和getFields的区别

Class<?>的getFields方法只返回公共的字段,如果类没有公共的字段,那么返回长度为0的数组。
Class<?>的getDeclaredFields方法返回所有声明的字段(除了继承的字段):public, protected, default (package) access, and private fields,
getDeclaredMethods和getMethods的区别

getMethods方法返回所有公共的方法,包括从父类和父接口继承的方法。
getDeclaredMethods方法返回所有public, protected, default (package) access, and private methods, 但是排除 inherited methods.(继承的字段)

/**
*
*/
package annotation.database;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @author Administrator
*
*/

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
    boolean primaryKey() default false;

    boolean allowNull() default true;

    boolean unique() default false;
}
/**
*
*/
package annotation.database;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @author Administrator
*
*/

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
    public String name() default "";
}
/**
*
*/
package annotation.database;

/**
* @author Administrator
*
*/
@DBTable(name = "MEMBER")
public class Member {
@SQLString(30)
String firstName;
@SQLString(50)
String lastName;
@SQLInteger
Integer age;
@SQLString(value = 30, constraints = @Constraints(primaryKey = true))
String handle;
static int memberCount;

public String getFirstName() {
return firstName;
}

public String getLastName() {
return lastName;
}

public Integer getAge() {
return age;
}

public String getHandle() {
return handle;
}

public String toString() {
return handle;
}

}
package annotation.database;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @author Administrator
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
    String name() default "";

    Constraints constraints() default @Constraints;

}
/**
*
*/
package annotation.database;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @author Administrator
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
    int value() default 0;

    String name() default "";

    Constraints constraints() default @Constraints;
}
/**
*
*/
package annotation.database;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
* @author Administrator
*
*/
public class TableCreator {

/**
* @param args
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws Exception {
if (args.length < 1) {
System.out.println("arguments:annotated classes");
System.exit(0);
}
for (String className : args) {
Class<?> cl = Class.forName(className);
DBTable dbTable = cl.getAnnotation(DBTable.class);
if (dbTable == null) {
System.out.println("No DBTable annotations in class "
+ className);
continue;
}
String tableName = dbTable.name();
if (tableName.length() < 1) {
tableName = cl.getName().toUpperCase();
}
List<String> columnDefs = new ArrayList<String>();
for (Field field : cl.getDeclaredFields()) {
String columnName = null;
Annotation[] anns = field.getDeclaredAnnotations();
field.getAnnotations();
if (anns.length < 1) {
continue;
}
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.constraints()));
}
if (anns[0] instanceof SQLString) {
SQLString sString = (SQLString) anns[0];
if (sString.name().length() < 1) {
columnName = field.getName().toUpperCase();
} else {
columnName = sString.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.allowNull()) {
constraints += " NOT NULL";
}
if (con.primaryKey()) {
constraints += " PRIMARY KEY";
}
if (con.unique()) {
constraints += " UNIQUE";
}
return constraints;
}

}


1概念
注解是JDK1.5开始引入的一个Java语言新特性。注解是与接口很相似,它与类、接口、枚举是在同一个层次,它
们都称作为java 的一个类型(TYPE)。
+1.1Java语言指南解释
注解(也被称做元数据),为我们提供了程序代码的描述信息,而这些描述信息并不属于程序本身。注解并不直接
影响其注释的代码的工作。
+1.2Java编程思想解释
注解(也被称做元数据),为我们在代码中添加信息提供了一种形式化的方法,使我们可以再稍后的某个时刻非常方便
的使用这些数据。
+1.3Java语言规范解释
注解是一个包含注解类型和零或多个键-值对的Java修饰符。其中每个键-值对都绑定了一个不同的注解类型值。注解的
目标简单的说就是绑定信息到其标注的编程元素。
+2用途
注解的用途包括(但不限于)如下:
为编译器提供信息:注解可以被编译器(如Eclipse、IntelliJ)用来检测错误和已知警告,如Java内置的@Deprecated、@Override和@SuppressWarnnings就被经常用到。
编译时和部署时附加操作:软件工具可以在编译时或部署时根据注解信息生成可执行代码,XML文件等等。如生成说明文档,Jax-ws,Jax-rs使用注解生成服务描述的XML文件。
运行时操作:一些注解可以在程序运行时再去进行解释。如下
Hibernate和Ibatis使用它做动态表结构和SQL语句生成等操作
Junit从4.x版本开始使用注解简化测试代码和流程
AspectJ使用注解提供非侵入式的面向切面编程
Spring也使用注解做XML配置管理的一种配合或替代
有些工具根据特定的注解做代码分析等等
注解很多时候的确能帮助我们减少、美化代码,或为现有代码增加额外功能等。
+3原理
像文章开头提到的注解和类、接口一样是一种类型。注解处理程序通过Java的反射机获取程序代码中的注解,然后根据预先设定的处理规则解析处理相关注解以达到主机本身设定的功能目标。
+4语法
+4.1定义
注解的定义和接口的定义相似,又有些不同。以下是定义示例
1.无参数注解
/**java.lang.Deprecated源代码*/
@Documented //将此注解包含在JavaDoc中
@Retention(RetentionPolicy.RUNTIME)//VM运行期仍保留该注解,可通过反射获得。
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})//此注解适用的目标(构造方法、字段、本地变量、方法、包、参数、类型)
public @interface Deprecated 
{
//"Deprecated" 为注解名称
//注解均使用@interface声明
}
2.含参数注解
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)//该注解仅保留在Java原文件中
public @interface SuppressWarnings {
    /**支持的参数列表all,deprecation,unchecked,fallthrough,path,serial,finally等*/
    String[] value();//该注解可接受一个参数数组
}
此注解可接受的更多参数参见此处。
3.自定义注解
/*
*****************************************************************************
* This software is under the Apache License Version 2.0
* Author: Tao -  mail:[email protected]
* Spreading Your Heart
****************************************************************************
*/

package atao.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 一个包含两个参数的简单测试用例注解,此注解仅适用于"方法".
*
* @author <a href="mailto:[email protected]">Tao</a>
* @since 1.0
*/
@Documented
@Target (ElementType.METHOD)
@Retention (RetentionPolicy.RUNTIME)
public @interface UseCase
{
    /** 测试用例编号 */
    public int id();
    /** 测试用例描述 */
    public String description() default "no description";
}
4.最简单的注解
@interface Naked{}
5.允许子类继承父类的注解
/**允许子类继承父类的注解*/
@Inherited
public @interface TrustFather{}
总结,注解定义包含: 
主题声明,权限修饰符 + @interface + 注解名称 
注解描述
适用目标:@Target
保留级别:@Retention
是否产生JavaDoc:@Documented
允许子类继承父类的注解: @Inherited
其他注解
注解包含字段设定(例子2、3中)
+4.2使用
/*
*****************************************************************************
* This software is under the Apache License Version 2.0
* Author: Tao -  mail:[email protected]
* Spreading Your Heart
****************************************************************************
*/

package atao.annotation;

import java.util.ArrayList;
import java.util.List;

/**
* 一个使用如上注解密码工具类
*
* @author <a href="mailto:[email protected]">Tao</a>
* @since 1.0
*/
public class PasswordUtils
{
    @UseCase (id = 47, description = "密码必须包含至少一个数字")
    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 = "新密码不能和曾经用过的密码重复")
    public boolean checkForNewPassword (List <String> prevPasswords, String password)
    {
        return !prevPasswords.contains (password);
    }
   
    /**@deprecated 楼主决定不推荐使用此方法了,自己随便找个个其他的试试吧.*/
    @Deprecated
    public void deprecatedMethod(){}
   
    @SuppressWarnings ({ "unused", "rawtypes" })
    public void unsafeMethod ()
    {
        //@SuppressWarnings ({ "unused", "rawtypes" }) //用在这里仍然可以,因为此注解支持修饰类和本地变量等类型。
        List unsafeList = new ArrayList<String>();
    }
}
+4.3编写注解处理器
如下根据Java反射写出的一个简单的测试用例处理器
/*
*****************************************************************************
* This software is under the Apache License Version 2.0
* Author: Tao -  mail:[email protected]
* Spreading Your Heart
****************************************************************************
*/

package atao.annotation;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* 用例检测的注解处理器
*
* @author <a href="mailto:[email protected]">Tao</a>
* @since 1.0
*/
public class UseCaseChecker
{

    public static void trackUseCases (List <Integer> useCases, Class <?> cl)
    {
        for (Method m : cl.getDeclaredMethods ())
        {
            UseCase uc = m.getAnnotation (UseCase.class);
            if (uc != null)
            {
                System.out.println ("找到用例:" + uc.id () + " " + uc.description ());
                useCases.remove (new Integer (uc.id ()));
            }
        }
        for (int i : useCases)
        {
            System.out.println ("警告-- 缺失用例:" + i);
        }
    }

    public static void main (String[] args)
    {
        List <Integer> useCases = new ArrayList <Integer> ();
        Collections.addAll (useCases, 1, 2, 3, 4,5);
        trackUseCases (useCases, PasswordUtils.class);
    }

}
/**运行输出结果为:
找到用例:1 密码必须包含至少一个数字
找到用例:4 新密码不能和曾经用过的密码重复
找到用例:3 无描述
警告-- 缺失用例:2
警告-- 缺失用例:5
*/
至此,一个简单的Java注解使用流程我们已经走完,接下来我们谈谈注解的语言要素和相关Java API等.
+5语言要素
+5.1元注解
至JDk7,Java提供了3种内置标准注解(@Override,@Deprecate,@SuppressWarnings)和四种元注解(@Target,@Retention,@Documented,@Inherited)。细心的人会发现这些在上文叙述中已经提到。
@Target,标示该注解可用在什么地方。可用参数包括(构造方法、字段、本地变量、方法、包、参数、类型(类、接口、注解、枚举))。其用ElementType枚举参数,读者可到阅读ElementType枚举产看原版解释。
@Retention,标示注解的生命周期,其用RetentionPolicy枚举参数。包含三个(RetentionPolicy.SOURCE,RetentionPolicy.CLASS,RetentionPolicy.RUNTIME)。SOURCE,RUNTIME除前文代码中已经做出解释。至于CLASS表示注解会保存在class文件中,类加载时将被丢弃。事实上Java在其J2EE规范的API中已经广泛的使用了注解机制,但是通过文件查找工具确认JDK(j2se/j2ee)至1.7结束都没有提供可供参考的RetentionPolicy.CLASS生命周期的注解。这或许也预示着CLASSS生命周期的定义或许是一堆废柴。
@Documented,是包含在JavaDoc中
@Inherited,允许子类继承父类的注解
+5.2默认参数
当注解定义只包含一个字符串参数时,使用如下方式可以使用直接的字符串填充方式为参数赋值
//定义
@interface SimpleUseCase{String value();}
//使用
@SimpleUseCase("就是这样么简单,可不使用键值对赋值")
+5.3裸体的注解
如上例中裸体注解@Nacked,将默认使用@Target(所有的ElementType类型),@Retention(RetentionPolicy.CLASS).参见此处或Java Retention源代码中的解释。

+5.4可重复注解
从JDK8开始java提供了一种允许自修饰的可循环注解@Repeatable,注解定义使用了此注解修饰则允许在目标上连续多次使用本注解。注意请下载JDK8,以下代码方可编译。
@Repeatable(Schedules.class)
public @interface Schedule {
String dayOfMonth();
String dayOfWeek();
String hour();
}

@Schedule(dayOfMonth="last")
@Schedule(dayOfWeek="Fri", hour="23")
public void doPeriodicCleanup() { /**方法体*/ }
+6注解工具APT
APT全称 Annotation Processing Tool。注意:APT工具及com.sun.mirror包中相关的API自Java SE 7已经弃用。请使用javac工具中对应选项选项和javax.annotation.processing、 javax.lang.model包中的API。
APT工具是一个命令行实用的注解处理程序。它包括一组反射API,支持处理程序注解的常用操作(JSR 175)。这些反射API提供了一个构建时、基于源代码、只读视图的程序结构。它们被设计用来辅助Java泛型更清晰构造Java编程语言的类型系统数据模型(JSR 14)。
首先APT工具运行注解处理程序产生新的源代码和其他文件。接下来,APT会促使编译器编译原始和生成的源文件。这种方式将使开发周期更简单。
JSR 269,也被称为语言模型API,有两个基本部分:Java编程语言模型API和编写注解处理器API。可通过javac命令新选项使用这个功能;JSR 269有对应支持说明,javac现在取代了JDK5中的APT工具。
以下是相关API所在的包:
javax.annotation.processing
javax.lang.model
javax.lang.model.element
javax.lang.model.type
javax.lang.model.util
关于注解工具的使用,我将用另一篇的文章单独介绍。这里由于篇幅原因,就不再深入。感兴趣的读者可直接阅读Java APT Getting Started.
+7参考资料
Java编程思想第四版第20章
Java Annotations Tutorial from Oracle 
Jenkov Java Annotation Tutorial
Spring Annotations
what-is-the-list-of-valid-suppresswarnings-warning-names-in-java
Java 7 APT
Java Language specification 7
Java APT Getting Started.


引言
在用hibernate的时候发现idea能自动生成JavaBean,同时带有一些注解,这引起了我的好奇。当在学习Android的时候,我发现XUtils这个工具包中的DBUtils也能够使用类似hibernate的注解。于是乎在java编程思想中找了找有关注解的用法。

一 注解定义
注解(也称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的使用这些数据。注解来源于C#之类的其他语言。

注解的语法比较简单,除了@符号外,它与java的固有语法一致。javaSE5中内置了三种注解:

@Override:定义覆盖超类,当覆写对应不上被覆盖的方法,编译器发出错误提示。
@Deprecated:当使用了该注解,即表示这个方法已经不推荐被使用。
@SuppressWarnings:关闭不当的编译器警告。
二 基本语法
我们使用自定义的注解对一个方法进行注解:

public class Testable{
    public void execute()
    {
        System.out.println("execute...");
    }
    @WETest
    void taskStart()
    {
        execute();
    }
}
在上边的代码中,我们对taskStart方法使用了注解,接下来我们对WETest注解进行定义:

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WETest{}
三 定义注解
我们给上边的注解添加一些内容:

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WETest{
    public int id();
    public String Notes() default "there is no Notes";
}
同样,我们对Testable类使用最新的注解:

public class Testable{
    @WETest(id=666)
    public void execute()
    {
        System.out.println("execute...");
    }
    @WETest(id=666,Notes="this is a method")
    void taskStart()
    {
        execute();
    }
}
注解就是这么使用的,当注解内容没有填写时,他会使用默认的值,如execute方法,他没有定义Notes,那么Notes默认值为"there is no Notes"。

四 元注解
我们看到注解上边有两行内容,它们是元注解,专门对注解的解释。元注解一共有四种,分别是:

@Target:表示该注解可以用到哪些地方,ElementType,CONSTRUCTOR构造器声明,FIELD域声明(包括enum实例),LOCAL_VARIABLE局部变量声明,METHOD方法,PACKAGE包,PARAMETER参数,TYPE类、接口或enum。
@Retention:表示需要在什么级别上使用,RetentionPolicy,SOURCE注解会被编译器丢掉,CLASS在class文件中可用会被VM抛弃,RUNTIME在VM运行期也会保留可以通过反射获取注解信息。
@Documented:将注解包含在Javadoc中。
@Inherited:允许子类继承父类中的注解。
五 通过注解反射生成SQL语句
接下来,我用一个例子来解释注解的作用。先编写一些注解定义:

//DBTable.java            用来生成数据表
package annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
    public String name() default "";
}

//Constraints.java        用来定义约束项
package annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
    boolean primarykey() default false;
    boolean allownull() default true;
}

//PrimaryKey.java        将Constraints中的primarykey定义为真,表示为主键
package annotations;

import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PrimaryKey {
    Constraints constraints() default @Constraints(primarykey = true);
}

//SQLInteger.java         定义列的类型
package annotations;

import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
    String name() default "";
    Constraints constraints() default @Constraints;
}

//SQLString.java        定义列的类型
package annotations;

import java.lang.annotation.*;


@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
    int value() default 64;
    String name() default "";
    Constraints constraints() default @Constraints;
}
   接下来写一个javabean,使用上述注解:


//User.java
import annotations.Constraints;
import annotations.DBTable;
import annotations.SQLInteger;
import annotations.SQLString;

@DBTable(name="user")
public class User {
    @SQLInteger(name="id",constraints = @Constraints(primarykey=true))
    public Integer id;
    @SQLString(value=30)
    public String name;
    @SQLString(name="passwd",constraints=@Constraints(allownull=false))
    public String password;

    /*可以不用
    public void setId(Integer id) {
        this.id = id;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public Integer getId() {
        return id;
    }
    public String getName() {
        return name;
    }
    public String getPassword() {
        return password;
    }*/
}
我们看到注解中可以使用注解,在SQLInteger中我们使用了Constraints注解。

接下来我们写一个注解处理器:

//Test.java
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

import annotations.Constraints;
import annotations.DBTable;
import annotations.SQLInteger;
import annotations.SQLString;

public class Test {

    public static String getConstraints(Constraints con)
    {
        String constraints = "";
        if(!con.allownull())
        {
            constraints +=" NOT NULL";
        }
        if(con.primarykey())
        {
            constraints += " PRIMARY KEY";
        }
        return constraints;
    }

    public static void main(String[] args) throws ClassNotFoundException {
        Scanner s = new Scanner(System.in);
        String name = s.next();                                    //从控制台输入一个类名,我们输入User即可
        Class<?> cl = Class.forName(name);                         //加载类,如果该类不在默认路径底下,会报 java.lang.ClassNotFoundException
        DBTable dbTable = cl.getAnnotation(DBTable.class);         //从User类中获取DBTable注解
        if(dbTable == null){                                       //如果没有DBTable注解,则直接返回,我们写了,当然有
            return;
        }
        String tableName = (dbTable.name().length()<1)?cl.getName():dbTable.name();//获取表的名字,如果没有在DBTable中定义,则获取类名作为Table的名字
        List<String> columnDefs = new ArrayList<String>();
        for(Field field : cl.getDeclaredFields())                  //获取声明的属性
        {
            String columnName = null;
            Annotation[] anns = field.getDeclaredAnnotations();//获取注解,一个属性可以有多个注解,所以是数组类型
            if(anns.length < 1)
            {
                continue;
            }
            if(anns[0] instanceof SQLInteger)                //判断注解类型
            {
                SQLInteger sInt = (SQLInteger)anns[0];
                columnName = (sInt.name().length()<1)?field.getName():sInt.name();//获取列名称与获取表名一样
                columnDefs.add(columnName+" INT"+getConstraints(sInt.constraints()));//使用一个方法,自己写的getConstraints(Constraints constraints)获取列定义
            }
            if(anns[0] instanceof SQLString)
            {
                SQLString sStr = (SQLString)anns[0];
                columnName = (sStr.name().length()<1)?field.getName().toUpperCase():sStr.name();
                columnDefs.add(columnName + " VARCHAR("+sStr.value()+")"+getConstraints(sStr.constraints()));
            }
        }
        StringBuilder createCommand = new StringBuilder("CREATE TABLE "+tableName+"(");
        for(String columnDef :columnDefs)
        {
            createCommand.append("\n    "+columnDef+",");
        }
        String tableCreate = createCommand.substring(0,createCommand.length()-1)+"\n);";
        System.out.println(tableCreate);                        //打印出来
    }

}
我们可以采用上述方法动态的处理一些数据,例如创建数据表。

六 总结
注意:注解不支持继承例如 extends @xxx。
     注解的default默认值不可以为null
使用注解可以减少对xml等外部文件的依赖,使得对类的定义可以在一处实现,避免了一个类两处定义的麻烦。spring和hibernate就采用的这样的方法。

注解Annotation又叫元数据,是JDK5中引入的一种以通用格式为程序提供配置信息的方式。使用注解Annotation可以使元数据写在程序源码中,使得代码看起来简洁,同时编译器也提供了对注解Annotation的类型检查,使得在编译期间就可以排除语法错误。

1JDK内置的3中Annotation:

在JDK5中,内置了3个通用目的的注解Annotation,这三个内置的注解在java.lang包下:

(1).@Override:

这个注解常用在继承类或实现接口的子类方法上,表面该方法是子类覆盖父类的方法,该方法的方法签名要遵循覆盖方法的原则:即访问控制权限必能比父类更严格,不能比父类抛出更多的异常。

(2).@Deprecated:

这个注解告诉编译器该元素是过时的,即在目前的JDK版本中已经有新的元素代替该元素。

(3).@SuppressWarnings:

该注解关闭编译器中不合适的警告,即强行压制编译器的警告提示。

2.注解Annotation的定义:

注解Annotation的定义和接口类似,实际上,编译器也把注解Annotationn像接口一样编译成class字节码文件。例如:

[java] view plaincopyprint?import java.lang.annotation.*; 
 
@Target(ElementType.METHOD) 
@Retention(RetentionPolicy.RUNTIME) 
public @interface Test{} 
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{}


像这种没有任何元素的空注解Annotation叫做标记Annotation.

在声明注解的时候往往需要使用@Target,@Retention等注解,这种注解被称为注解的注解(元数据注解),即是专门用于处理注解Annotation本身的。

(1).@Target注解:

用于指示注解所应用的目标程序元素种类,该注解常和ElementType枚举类型一起联合使用,ElementType枚举提供了java程序中声明的元素类型如下:

ANNOTATION_TYPE:注释类型声明。
CONSTRUCTOR:构造方法声明。
FIELD:字段声明(包括枚举常量)。
LOCAL_VARIABLE:局部变量声明。
METHOD:方法声明。
PACKAGE:包声明。
PARAMETER:参数声明。
TYPE::类,接口或枚举声明。
(2).@Retention注解:

该注解用于指示所定义的注解类型的注释在程序声明周期中得保留范围,该注解常和RetentionPolicy枚举联合使用。RetentionPolicy枚举常量定义了注解在代码中的保留策略:

CLASS:编译器把注解记录在类文件中,但在运行时JVM不需要保留注解。
RUNTIME:编译器把注解记录在类文件中,在运行时JVM将保留注解,因此可以通过反射机制读取注解。
SOURCE:仅保留在源码中,编译器在编译时就要丢弃掉该注解。
3.创建和处理自定义注解Annotation:

正常使用注解时,需要在注解中定义元素,用于接收程序设置的值,正常定义注解的例子如下:

[java] view plaincopyprint?import java.lang.annotation.*; 
 
@Target(ElementType.METHOD) 
@Retention(RetentionPolicy.RUNTIME) 
public @interface UseCase{ 
    public int id(); 
    public String description() default “no description”; 

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的值,则程序使用其默认值。

上面UseCasee注解用于跟踪方法的测试用例说明,使用上面注解的例子如下:

[java] view plaincopyprint?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); 


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的例子如下:

[java] view plaincopyprint?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); 


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).上面类型的数组。

除了上面这些类型以外,如果在注解中定义其他类型的数据,编译器将会报错。

注意:注解中的元素要么指定默认值,要么由使用的类赋值,如果即没有默认值,使用类也没有赋值的话,注解元素是不会像普通类成员变量一样给定默认值,即必须赋值或者显示指定默认值。默认值例子如下:

[java] view plaincopyprint?import java.lang.annotation.*; 
 
@Target(ElementType.METHOD) 
@Retention(RetentionPolicy.RUNTIME) 
public @interface DefaultValue{ 
    public int id() default -1; 
    public String description() default “”; 

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).相关注解:

[java] view plaincopyprint?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;  

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注解的实体类:

[java] view plaincopyprint?@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; 

@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).实现自定义注解处理器:

[java] view plaincopyprint?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; 


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).注解:

[java] view plaincopyprint?package annotations; 
 
import java.lang.annotation.*; 
 
@Target(ElementType.TYPE) 
@Retention(RetentionPolicy.SOURCE) 
public @interface ExtractInterface{ 
    public String value(); 

package annotations;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface ExtractInterface{
public String value();
}

这个注解用于从使用该注解的类中抽象公共的方法,并为该类生成一个接口。

(2).使用注解的类:

[java] view plaincopyprint?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; 


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).注解处理器:

[java] view plaincopyprint?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); 





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工厂提供注解处理器:

[java] view plaincopyprint?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(); 

}    
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);

}






















猜你喜欢

转载自zhyp29.iteye.com/blog/2307749