Java深度历险:Java注解

    在开发Java程序,尤为是Java EE应用的时候,老是免不了与各类配置文件打交道。以Java EE中典型的S(pring)S(truts)H(ibernate)架构来讲,Spring、Struts和Hibernate这三个框架都有本身的 XML格式的配置文件。java

annotation使用三步曲,
    1.define annotation interface
    2.define parser annotation class
    3.use annotation and it's parser
数组

        在开发Java程序,尤为是Java EE应用的时候,老是免不了与各类配置文件打交道。以Java EE中典型的S(pring)S(truts)H(ibernate)架构来讲,Spring、Struts和Hibernate这三个框架都有本身的 XML格式的配置文件。这些配置文件须要与Java源代码保存同步,不然的话就可能出现错误。并且这些错误有可能到了运行时刻才被发现。把同一份信息保存在两个地方,老是个坏的主意。理想的状况是在一个地方维护这些信息就行了。其它部分所需的信息则经过自动的方式来生成。JDK 5中引入了源代码中的注解(annotation)这一机制。注解使得Java源代码中不但能够包含功能性的实现代码,还能够添加元数据。注解的功能相似于代码中的注释,所不一样的是注解不是提供代码功能的说明,而是实现程序功能的重要组成部分。Java注解已经在不少框架中获得了普遍的使用,用来简化程序中的配置。架构

使用注解框架

在通常的Java开发中,最常接触到的可能就是@Override和@SupressWarnings这两个注解了。使用@Override的时候只须要一个简单的声明便可。这种称为标记注解(marker annotation ),它的出现就表明了某种配置语义。而其它的注解是能够有本身的配置参数的。配置参数以名值对的方式出现。使用 @SupressWarnings的时候须要相似@SupressWarnings({"uncheck", "unused"})这样的语法。在括号里面的是该注解可供配置的值。因为这个注解只有一个配置参数,该参数的名称默认为value,而且能够省略。而花括号则表示是数组类型。在JPA中的@Table注解使用相似@Table(name = "Customer", schema = "APP")这样的语法。从这里能够看到名值对的用法。在使用注解时候的配置参数的值必须是编译时刻的常量。ide

从某种角度来讲,能够把注解当作是一个XML元素,该元素能够有不一样的预约义的属性。而属性的值是能够在声明该元素的时候自行指定的。在代码中使用注解,就至关于把一部分元数据从XML文件移到了代码自己之中,在一个地方管理和维护。工具

开发注解ui

在通常的开发中,只须要经过阅读相关的API文档来了解每一个注解的配置参数的含义,并在代码中正确使用便可。在有些状况下,可能会须要开发本身的注解。这在库的开发中比较常见。注解的定义有点相似接口。下面的代码给出了一个简单的描述代码分工安排的注解。经过该注解能够在源代码中记录每一个类或接口的分工和进度状况。this

如下是代码片断:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Assignment {
String assignee();
int effort();
double finished() default 0;
}

@interface用来声明一个注解,其中的每个方法其实是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型。能够经过default来声明参数的默认值。在这里能够看到@Retention和@Target这样的元注解,用来声明注解自己的行为。 @Retention用来声明注解的保留策略,有CLASS、RUNTIME和SOURCE这三种,分别表示注解保存在类文件、JVM运行时刻和源代码中。只有当声明为RUNTIME的时候,才可以在运行时刻经过反射API来获取到注解的信息。@Target用来声明注解能够被添加在哪些类型的元素上,如类型、方法和域等。spa

处理注解命令行

在程序中添加的注解,能够在编译时刻或是运行时刻来进行处理。在编译时刻处理的时候,是分红多趟来进行的。若是在某趟处理中产生了新的Java 源文件,那么就须要另一趟处理来处理新生成的源文件。如此往复,直到没有新文件被生成为止。在完成处理以后,再对Java代码进行编译。JDK 5中提供了apt工具用来对注解进行处理。apt是一个命令行工具,与之配套的还有一套用来描述程序语义结构的Mirror API。Mirror API(com.sun.mirror.*)描述的是程序在编译时刻的静态结构。经过Mirror API能够获取到被注解的Java类型元素的信息,从而提供相应的处理逻辑。具体的处理工做交给apt工具来完成。编写注解处理器的核心是 AnnotationProcessorFactory和AnnotationProcessor两个接口。后者表示的是注解处理器,而前者则是为某些注解类型建立注解处理器的工厂。

以上面的注解Assignment为例,当每一个开发人员都在源代码中更新进度的话,就能够经过一个注解处理器来生成一个项目总体进度的报告。 首先是注解处理器工厂的实现。

如下是代码片断:
public class AssignmentApf implements AnnotationProcessorFactory {
public AnnotationProcessor getProcessorFor(Set atds,? AnnotationProcessorEnvironment env) {
if (atds.isEmpty()) {
return AnnotationProcessors.NO_OP;
}
return new AssignmentAp(env); //返回注解处理器
}
public Collection supportedAnnotationTypes() {
return Collections.unmodifiableList(Arrays.asList("annotation.Assignment"));
}
public Collection supportedOptions() {
return Collections.emptySet();
}
}
AnnotationProcessorFactory接口有三个方法:getProcessorFor是根据注解的类型来返回特定的注解处理器;supportedAnnotationTypes是返回该工厂生成的注解处理器所能支持的注解类型;supportedOptions用来表示所支持的附加选项。在运行apt命令行工具的时候,能够经过-A来传递额外的参数给注解处理器,如-Averbose=true。当工厂经过 supportedOptions方法声明了所能识别的附加选项以后,注解处理器就能够在运行时刻经过 AnnotationProcessorEnvironment的getOptions方法获取到选项的实际值。注解处理器自己的基本实现以下所示。

如下是代码片断:
public class AssignmentAp implements AnnotationProcessor {
private AnnotationProcessorEnvironment env;
private AnnotationTypeDeclaration assignmentDeclaration;
public AssignmentAp(AnnotationProcessorEnvironment env) {
this.env = env;
assignmentDeclaration = (AnnotationTypeDeclaration) env.getTypeDeclaration("annotation.Assignment");
}
public void process() {
Collection declarations = env.getDeclarationsAnnotatedWith(assignmentDeclaration);
for (Declaration declaration : declarations) {
processAssignmentAnnotations(declaration);
}
}
private void processAssignmentAnnotations(Declaration declaration) {
Collection annotations = declaration.getAnnotationMirrors();
for (AnnotationMirror mirror : annotations) {
if (mirror.getAnnotationType().getDeclaration().equals(assignmentDeclaration)) {
Map values = mirror.getElementValues();
String assignee = (String) getAnnotationValue(values, "assignee"); //获取注解的值
}
}
}
}

注解处理器的处理逻辑都在process方法中完成。经过一个声明(Declaration)的getAnnotationMirrors方法就能够获取到该声明上所添加的注解的实际值。获得这些值以后,处理起来就不难了。

在建立好注解处理器以后,就能够经过apt命令行工具来对源代码中的注解进行处理。 命令的运行格式是apt -classpath bin -factory annotation.apt.AssignmentApf src/annotation/work/*.java,即经过-factory来指定注解处理器工厂类的名称。实际上,apt工具在完成处理以后,会自动调用javac来编译处理完成后的源代码。

JDK 5中的apt工具的不足之处在于它是Oracle提供的私有实现。在JDK 6中,经过JSR 269把自定义注解处理器这一功能进行了规范化,有了新的javax.annotation.processing这个新的API。对Mirror API也进行了更新,造成了新的javax.lang.model包。注解处理器的使用也进行了简化,不须要再单独运行apt这样的命令行工具,Java 编译器自己就能够完成对注解的处理。对于一样的功能,若是用JSR 269的作法,只须要一个类就能够了。

如下是代码片断:
@SupportedSourceVersion(SourceVersion.RELEASE_6)
@SupportedAnnotationTypes("annotation.Assignment")
public class AssignmentProcess extends AbstractProcessor {
private TypeElement assignmentElement;
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Elements elementUtils = processingEnv.getElementUtils();
assignmentElement = elementUtils.getTypeElement("annotation.Assignment");
}
public boolean process(Set annotations, RoundEnvironment roundEnv) {
Set elements = roundEnv.getElementsAnnotatedWith(assignmentElement);
for (Element element : elements) {
processAssignment(element);
}
}
private void processAssignment(Element element) {
List annotations = element.getAnnotationMirrors();
for (AnnotationMirror mirror : annotations) {
if (mirror.getAnnotationType().asElement().equals(assignmentElement)) {
Map values = mirror.getElementValues();
String assignee = (String) getAnnotationValue(values, "assignee"); //获取注解的值
}
}
}
}
仔细比较上面两段代码,能够发现它们的基本结构是相似的。不一样之处在于JDK 6中经过元注解@SupportedAnnotationTypes来声明所支持的注解类型。另外描述程序静态结构的javax.lang.model包使用了不一样的类型名称。使用的时候也更加简单,只须要经过javac -processor annotation.pap.AssignmentProcess Demo1.java这样的方式便可。

上面介绍的这两种作法都是在编译时刻进行处理的。而有些时候则须要在运行时刻来完成对注解的处理。这个时候就须要用到Java的反射API。反射API提供了在运行时刻读取注解信息的支持。不过前提是注解的保留策略声明的是运行时。Java反射API的AnnotatedElement接口提供了获取类、方法和域上的注解的实用方法。好比获取到一个Class类对象以后,经过getAnnotation方法就能够获取到该类上添加的指定注解类型的注解。

实例分析

下面经过一个具体的实例来分析说明在实践中如何来使用和处理注解。假定有一个公司的雇员信息系统,从访问控制的角度出发,对雇员的工资的更新只能由具备特定角色的用户才能完成。考虑到访问控制需求的广泛性,能够定义一个注解来让开发人员方便的在代码中声明访问控制权限。

如下是代码片断:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredRoles {
String[] value();
}

下一步则是如何对注解进行处理,这里使用的Java的反射API并结合动态代理。下面是动态代理中的InvocationHandler接口的实现。

如下是代码片断: public class AccessInvocationHandler implements InvocationHandler { final T accessObj; public AccessInvocationHandler(T accessObj) { this.accessObj = accessObj; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { RequiredRoles annotation = method.getAnnotation(RequiredRoles.class); //经过反射API获取注解 if (annotation != null) { String[] roles = annotation.value(); String role = AccessControl.getCurrentRole(); if (!Arrays.asList(roles).contains(role)) { throw new AccessControlException("The user is not allowed to invoke this method."); } } return method.invoke(accessObj, args); } }
在具体使用的时候,首先要经过Proxy.newProxyInstance方法建立一个EmployeeGateway的接口的代理类,使用该代理类来完成实际的操做。
相关文章
相关标签/搜索