JAVA 注解深入学习

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

        注解可以提供用来完整地描述程序所需的信息,而这些信息是无法用Java来表达的。因此,注解使得我们能够以将编译器来测试和验证的格式,存储有关程序的额外信息。注解可以用来生成描述符文件,甚至或是新的类定义,并且有助于减轻编写“样板”代码的负担。通过使用注解,我们可以将这些元数据保存在Java源代码中,并利用annotation API为自己的注解构造处理工具。

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

@Override,表示当前的方法定义将覆盖超类里的方法。如果拼写错误或者方法签名对不上被覆盖的方法,编译器就会发出错误提示。

@Deprecated,如果程序员使用了注解为它的元素,那么编译器会发出警告信息。

@SuppressWarnings,关闭不当的编译器警告信息。

1 基本语法

        注解的使用方法@Test:
public class Testable {
    public void execute() {
        System.out.println("Executing..");
    }

    @Test
    void testExecute() {
        execute();
    }
} ///:~
1.1 定义注解
注解的定义很像接口的定义。并且,和java借口一样,注解也会编译成class文件。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {} ///:~
如上例,定义注解时,需要一些 元注释,如@Target和@Retention。
@Target 用来定义你的注解将应用于什么地方(列入一个方法,或者一个域)。
@Retention 用来定义该注解在哪一个级别可用,在源代码中(SOURCE),类文件中(CLASS)或者运行时(RUNTIME)。

在注解中,一般都会包含一些元素以表示某些值。当分析处理注解时,程序或工具可以利用这些值。注解的元素看起来就像借口方法,唯一的区别是你可以为其指定默认值。
没有元素的注解称为 标记注解 ,上例中的@Test就是标记注解。

下面是一个简单的注解,我们可以用它来跟踪一个项目中的用例。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
  public int id();
  public String description() default "no description";
} ///:~

id和description类似方法定义。description有一个默认值。在下例,三个方法被注册为用例:

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't equal previously used ones")
    public boolean checkForNewPassword(
            List<String> prevPasswords, String password) {
        return !prevPasswords.contains(password);
    }
} 

注解的元素在使用时表现为名-值对应形式,并位于@UseCase声明之后的括号里。


1.2 元注解

java目前内置了三种标准注解,以及四种元注解。元注解专职负责注解其他的注解:

@Target  表示该注解用于什么地方。可能的ElementType参数包括:
  • CONSTRUCTOR : 构造器的声明
  • FIELD: 域声明(包括enum实例)
  • LOCAL_VARIABLE:局部变量声明
  • METHOD:方法声明
  • PACKAGE:包声明
  • PARAMETER:参数声明
  • TYPE:类、接口(包括注解类型)或enum声明
@Retention  表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括:
  • SOURCE:注解将被编译器丢弃
  • CLASS:注解在class文件中可用,但会被VM丢弃。
  • RUNTIME:VM将在运行期也保留注解,因此可用永拓反射机制读取注解的信息
@Documented    将此注解包含在Javadoc中。
@Inherited     允许子类继承父类的注解。

2 编写注解处理器

    如果没有用来读取注解的工具,那么注解也不会比注释更有用。使用注解的过程中,很重要的一个部分就是创建与使用注解处理器。
Java SE5扩展了反射机制的API,以帮助程序员构造这类工具。同时,它还提供了一个外部工具apt帮助程序员解析带有注解的Java源代码。
例子:
package annotations;//: annotations/UseCaseTracker.java

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

public class UseCaseTracker {
    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("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 ArrayList<Integer>();
        Collections.addAll(useCases, 47, 48, 49, 50);
        trackUseCases(useCases, PasswordUtils.class);
    }
} /* Output:
Found Use Case:47 Passwords must contain at least one numeric
Found Use Case:48 no description
Found Use Case:49 New passwords can't equal previously used ones
Warning: Missing use case-50
*///:~
上述例子使用了反射方法getDeclaredMethods()和getAnnotation(),获取了注解元素。

2.1 注解元素


拿注解@UserCase距离,他包含int元素id,String元素description。注解元素可用类型如下所示:
  • 所有基本类型(int,float,boolean)
  • String
  • Class
  • enum
  • Annotation
  • 以上类型的数组
如果使用了其他类型,编译器会报错。注意不能使用包装类型,另注解可用嵌套。

2.2 默认值限制

元素不能有不确定的值。要么具有默认值,要么在使用注解时提供元素的值。
对于非基本类型的元素,无论是在源代码声明时,或是在注解借口中定义默认值,都不允许以null为其值。如果需要表现出一个元素的缺失状态,则需要自己定义一些默认的值,如空字符串或者负数。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SimulatingNull {
    public int id() default -1;

    public String description() default "";
} ///:~

2.3 生成外部文件

假设你希望提供一些基本的对象/关系映射功能,能够自动生成数据库表,用以存储JavaBean对象。你可以选择使用XML描述文件,指明类的名字、每个成员以及数据库映射的相关信息。
然而,如果使用注解的话,你可以将所有信息都保存在JavaBean源文件中。为此,我们需要一些新的注解,用于定义与Bean关联的数据库表的名字,以及与Bean关联的列的名字和SQL类型。
数据库表注解:
package annotations.database;

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

@Target(ElementType.TYPE) // Applies to classes only
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
    public String name() default "";
} ///:~
修饰JavaBean域准备的注解:
Constraints:
//: annotations/database/Constraints.java
package annotations.database;

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;

    boolean unique() default false;
} ///:~
SQL String类型:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
    int value() default 0;

    String name() default "";

    Constraints constraints() default @Constraints;
} ///:~

SQL INTEGER类型:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
    String name() default "";

    Constraints constraints() default @Constraints;
} ///:~

由于数据库类型注解的constraints()元素的默认值是 @Constraints,即为默认注解。如果需要让嵌入的@Constraints注解中的unique()元素为true,并以此作为constraints()元素的默认值,则需要如下定义该元素:
public @interface Uniqueness {
  Constraints constraints()
    default @Constraints(unique=true);
} ///:~

下面是一个简单的Bean定义,我们在其中应用了以上这些注解:
@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 getHandle() { return handle; }
  public String getFirstName() { return firstName; }
  public String getLastName() { return lastName; }
  public String toString() { return handle; }
  public Integer getAge() { return age; }
} ///:~
以上@SQLString(30)运用了快捷方式,直接将30付给了value。

其他设计方法:
1、定义单一注解类@TableColumn,它带有一个enum元素,该枚举类定义STRING,INTEGER等枚举实例,这样就消除了每个SQL类型都需要一个@interface的负担,不过也使得以额外信息来修饰SQL类型的需求变得不可能,如长度或者精度。
2、我们可以用String元素来描述实际的SQL类型,比如VARCHAR(30)或INTEGER。这使得我们可以修饰SQL类型。同时它将JAVA类型与SQL类型绑定了在一起,如果更换数据库的话代码必须修改。
3、同时用2个注解来注解一个域,@Constraints和相应的SQL类型。这种方式可能会使代码有点乱,不过编译器允许。注意,同一个注解不能重复使用。

2.4 注解不支持继承

注解目前不支持继承。
 
 

2.5 实现处理器

https://github.com/xu509/Java-practise/blob/master/src/annotations/database/TableCreator.java
根据这个例子,可以根据Javabean得出建表语句

3 使用apt处理注解

注解处理工具apt,是sun为了帮助注解的处理过程而提供的工具。
要编译这一节出现的注解处理器,必须将tools.jar设置在你的classpath中,这个工具包含了com.sun.mirror.*接口。
如果使用maven的话配置如下:
 <dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <version>1.6.0_10</version>
            <scope>system</scope>
            <!--<systemPath>C:/Program Files/Java/jdk1.6.0_10/lib/tools.jar</systemPath>-->
            <systemPath>${env.JAVA_HOME}/lib/tools.jar</systemPath>
        </dependency>

下面是一个自定义注解,使用它可以把一个类中的public方法提取出来,构造成一个新的接口。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface ExtractInterface {
    public String value();
} ///:~

RetentionPolicy是SOURCE,因为当我们从一个使用了该注解的类抽取出接口之后,没有必要再保留这些注解信息。

下面的类有一个共有方法,我们将他抽取到一个有用的接口中去:
@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;
    }

    public static void main(String[] args) {
        Multiplier m = new Multiplier();
        System.out.println("11*16 = " + m.multiply(11, 16));
    }
}

处理器:
public class InterfaceExtractorProcessor
        implements AnnotationProcessor {
    private final AnnotationProcessorEnvironment env;
    private ArrayList<MethodDeclaration> interfaceMethods =
            new ArrayList<MethodDeclaration>();

    public InterfaceExtractorProcessor(
            AnnotationProcessorEnvironment 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())
                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 (ParameterDeclaration parm :
                                m.getParameters()) {
                            writer.print(parm.getType() + " " +
                                    parm.getSimpleName());
                            if (++i < m.getParameters().size())
                                writer.print(", ");
                        }
                        writer.println(");");
                    }
                    writer.println("}");
                    writer.close();
                } catch (IOException ioe) {
                    throw new RuntimeException(ioe);
                }
            }
        }
    }
} 
所有的工作都在process()方法中完成。

apt工具需要一个工厂类来为其指明正确的处理器,然后它才能调用处理器上的process()
public class InterfaceExtractorProcessorFactory
        implements AnnotationProcessorFactory {
    public AnnotationProcessor getProcessorFor(
            Set<AnnotationTypeDeclaration> atds,
            AnnotationProcessorEnvironment env) {
        return new InterfaceExtractorProcessor(env);
    }

    public Collection<String> supportedAnnotationTypes() {
        return
                Collections.singleton("annotations.ExtractInterface");
    }

    public Collection<String> supportedOptions() {
        return Collections.emptySet();
    }
} 

4.将访问者模式用于apt

import com.sun.mirror.apt.AnnotationProcessor;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.apt.AnnotationProcessorFactory;
import com.sun.mirror.declaration.AnnotationTypeDeclaration;
import com.sun.mirror.declaration.ClassDeclaration;
import com.sun.mirror.declaration.FieldDeclaration;
import com.sun.mirror.declaration.TypeDeclaration;
import com.sun.mirror.util.SimpleDeclarationVisitor;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;

import static com.sun.mirror.util.DeclarationVisitors.NO_OP;
import static com.sun.mirror.util.DeclarationVisitors.getDeclarationScanner;

public class TableCreationProcessorFactory
        implements AnnotationProcessorFactory {
    public AnnotationProcessor getProcessorFor(
            Set<AnnotationTypeDeclaration> atds,
            AnnotationProcessorEnvironment env) {
        return new TableCreationProcessor(env);
    }

    public Collection<String> supportedAnnotationTypes() {
        return Arrays.asList(
                "annotations.database.DBTable",
                "annotations.database.Constraints",
                "annotations.database.SQLString",
                "annotations.database.SQLInteger");
    }

    public Collection<String> supportedOptions() {
        return Collections.emptySet();
    }

    private static class TableCreationProcessor
            implements AnnotationProcessor {
        private final AnnotationProcessorEnvironment env;
        private String sql = "";

        public TableCreationProcessor(
                AnnotationProcessorEnvironment env) {
            this.env = env;
        }

        public void process() {
            for (TypeDeclaration typeDecl :
                    env.getSpecifiedTypeDeclarations()) {
                typeDecl.accept(getDeclarationScanner(
                        new TableCreationVisitor(), NO_OP));
                sql = sql.substring(0, sql.length() - 1) + ");";
                System.out.println("creation SQL is :\n" + sql);
                sql = "";
            }
        }

        private class TableCreationVisitor
                extends SimpleDeclarationVisitor {
            public void visitClassDeclaration(
                    ClassDeclaration d) {
                DBTable dbTable = d.getAnnotation(DBTable.class);
                if (dbTable != null) {
                    sql += "CREATE TABLE ";
                    sql += (dbTable.name().length() < 1)
                            ? d.getSimpleName().toUpperCase()
                            : dbTable.name();
                    sql += " (";
                }
            }

            public void visitFieldDeclaration(
                    FieldDeclaration d) {
                String columnName = "";
                if (d.getAnnotation(SQLInteger.class) != null) {
                    SQLInteger sInt = d.getAnnotation(
                            SQLInteger.class);
                    // Use field name if name not specified
                    if (sInt.name().length() < 1)
                        columnName = d.getSimpleName().toUpperCase();
                    else
                        columnName = sInt.name();
                    sql += "\n    " + columnName + " INT" +
                            getConstraints(sInt.constraints()) + ",";
                }
                if (d.getAnnotation(SQLString.class) != null) {
                    SQLString sString = d.getAnnotation(
                            SQLString.class);
                    // Use field name if name not specified.
                    if (sString.name().length() < 1)
                        columnName = d.getSimpleName().toUpperCase();
                    else
                        columnName = sString.name();
                    sql += "\n    " + columnName + " VARCHAR(" +
                            sString.value() + ")" +
                            getConstraints(sString.constraints()) + ",";
                }
            }

            private 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;
            }
        }
    }
} ///:~

mirror API提供了访问者设计模式的支持。

5.基于注解的单元测试

    单元测试是对类中的每个方法提供一个或多个测试的一种实践,其目的是为了有规律地测试一个或多个测试的一种实践,其目的是为了有规律地测试一个类的各个部分是否具备正确的行为。
    基于注解的测试框架叫做@Unit。用@Test来标记测试方法。测试方法不带参数,并返回boolean结果来说明测试成功或失败。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值