JDKQjdk1.7.0_02CGLIB创徏代理对象速度大概比JDK Proxy?倍,执行速度是JDK Proxy?倍以?/span>
CGLIBQ和spring2.0.6 使用同样的cglib-nodep-2.1_3.jar
CPUQP8400 2.53GHz 2.53GHz
试l果4Q?nbsp;Create JDK Proxy: 43 ms
Create CGLIB Proxy: 129 ms
Run JDK Proxy: 940 ms, 1,500,069 t/s
Run CGLIB Proxy: 299 ms, 4,715,937 t/s
在一般的Java开发中Q最常接触到的可能就?a style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px;">@Override?a style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px;">@SupressWarningsq两个注解了。用@Override的时候只需要一个简单的声明卛_。这U称为标记注解(marker annotation Q,它的出现׃表了某种配置语义。而其它的注解是可以有自己的配|参数的。配|参C名值对的方式出现。?@SupressWarnings的时候需要类似@SupressWarnings({"uncheck", "unused"})q样的语法。在括号里面的是该注解可供配|的倹{由于这个注解只有一个配|参敎ͼ该参数的名称默认为valueQƈ且可以省略。而花括号则表C是数组cd。在JPA中的@Table注解使用cM@Table(name = "Customer", schema = "APP")q样的语法。从q里可以看到名值对的用法。在使用注解时候的配置参数的值必L~译时刻的常量?/p>
从某U角度来_可以把注解看成是一个XML元素Q该元素可以有不同的预定义的属性。而属性的值是可以在声明该元素的时候自行指定的。在代码中用注解,q当于把一部分元数据从XML文gUd了代码本w之中,在一个地方管理和l护?/p>
在一般的开发中Q只需要通过阅读相关的API文档来了解每个注解的配置参数的含义,q在代码中正用即可。在有些情况下,可能会需要开发自q注解。这在库的开发中比较常见。注解的定义有点cM接口。下面的代码l出了一个简单的描述代码分工安排的注解。通过该注解可以在源代码中记录每个cL接口的分工和q度情况?br />
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Assignment { String assignee(); int effort(); double finished() default 0; }
@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配|参数。方法的名称是参数的名Uͼq回值类型就是参数的cd。可以通过default来声明参数的默认倹{在q里可以看到@Retention?a style="text-decoration: none; color: #286ab2; margin: 0px; border: 0px; padding: 0px; outline: none !important;">@Targetq样的元注解Q用来声明注解本w的行ؓ。@Retention用来声明注解的保留策略,?a style="text-decoration: none; color: #286ab2; margin: 0px; border: 0px; padding: 0px; outline: none !important;">CLASS?a style="text-decoration: none; color: #286ab2; margin: 0px; border: 0px; padding: 0px; outline: none !important;">RUNTIME?a style="text-decoration: none; color: #286ab2; margin: 0px; border: 0px; padding: 0px; outline: none !important;">SOURCEq三U,分别表示注解保存在类文g、JVMq行时刻和源代码中。只有当声明为RUNTIME的时候,才能够在q行时刻通过反射API来获取到注解的信息。@Target用来声明注解可以被添加在哪些cd的元素上Q如cd、方法和域等?/p>
在程序中d的注解,可以在编译时L是运行时Lq行处理。在~译时刻处理的时候,是分成多来q行的。如果在某趟处理中生了新的Java源文Ӟ那么需要另外一处理来处理新生成的源文件。如此往复,直到没有新文件被生成为止。在完成处理之后Q再对Java代码q行~译。JDK 5中提供了apt工具用来Ҏ解进行处理。apt是一个命令行工具Q与之配套的q有一套用来描q程序语义结构的Mirror API。Mirror APIQcom.sun.mirror.*Q描q的是程序在~译时刻的静态结构。通过Mirror API可以获取到被注解的Javacd元素的信息,从而提供相应的处理逻辑。具体的处理工作交给apt工具来完成。编写注解处理器的核心是AnnotationProcessorFactory?a style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px;">AnnotationProcessor两个接口。后者表C的是注解处理器Q而前者则是ؓ某些注解cd创徏注解处理器的工厂?/p>
以上面的注解AssignmentZQ当每个开发h员都在源代码中更新进度的话,可以通过一个注解处理器来生成一个项目整体进度的报告?首先是注解处理器工厂的实现?br />
public class AssignmentApf implements AnnotationProcessorFactory { public AnnotationProcessor getProcessorFor(Set<AnnotationTypeDeclaration> atds,? AnnotationProcessorEnvironment env) { if (atds.isEmpty()) { return AnnotationProcessors.NO_OP; } return new AssignmentAp(env); //q回注解处理? } public Collection<String> supportedAnnotationTypes() { return Collections.unmodifiableList(Arrays.asList("annotation.Assignment")); } public Collection<String> supportedOptions() { return Collections.emptySet(); } }
AnnotationProcessorFactory接口有三个方法:getProcessorFor是根据注解的cd来返回特定的注解处理器;supportedAnnotationTypes是返回该工厂生成的注解处理器所能支持的注解cdQsupportedOptions用来表示所支持的附加选项。在q行apt命o行工L时候,可以通过-A来传递额外的参数l注解处理器Q如-Averbose=true。当工厂通过 supportedOptionsҎ声明了所能识别的附加选项之后Q注解处理器可以在q行时刻通过AnnotationProcessorEnvironment的getOptionsҎ获取到选项的实际倹{注解处理器本n的基本实现如下所C?br />
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<Declaration> declarations = env.getDeclarationsAnnotatedWith(assignmentDeclaration); for (Declaration declaration : declarations) { processAssignmentAnnotations(declaration); } } private void processAssignmentAnnotations(Declaration declaration) { Collection<AnnotationMirror> annotations = declaration.getAnnotationMirrors(); for (AnnotationMirror mirror : annotations) { if (mirror.getAnnotationType().getDeclaration().equals(assignmentDeclaration)) { Map<AnnotationTypeElementDeclaration, AnnotationValue> values = mirror.getElementValues(); String assignee = (String) getAnnotationValue(values, "assignee"); //获取注解的? } } } }
注解处理器的处理逻辑都在processҎ中完成。通过一个声明(DeclarationQ的getAnnotationMirrorsҎ可以获取到该声明上所d的注解的实际倹{得到这些g后,处理h׃难了?/p>
在创建好注解处理器之后,可以通过apt命o行工hҎ代码中的注解q行处理?命o的运行格式是apt -classpath bin -factory annotation.apt.AssignmentApf src/annotation/work/*.javaQ即通过-factory来指定注解处理器工厂cȝ名称。实际上Qapt工具在完成处理之后,会自动调用javac来编译处理完成后的源代码?/p>
JDK 5中的apt工具的不之处在于它是Oracle提供的私有实现。在JDK 6中,通过JSR 269把自定义注解处理器这一功能q行了规范化Q有了新?a style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px;">javax.annotation.processingq个新的API。对Mirror API也进行了更新QŞ成了新的javax.lang.model包。注解处理器的用也q行了简化,不需要再单独q行aptq样的命令行工具QJava~译器本w就可以完成Ҏ解的处理。对于同L功能Q如果用JSR 269的做法,只需要一个类可以了?br />
@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<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(assignmentElement); for (Element element : elements) { processAssignment(element); } } private void processAssignment(Element element) { List<? extends AnnotationMirror> annotations = element.getAnnotationMirrors(); for (AnnotationMirror mirror : annotations) { if (mirror.getAnnotationType().asElement().equals(assignmentElement)) { Map<? extends ExecutableElement, ? extends AnnotationValue> values = mirror.getElementValues(); String assignee = (String) getAnnotationValue(values, "assignee"); //获取注解的? } } } }
仔细比较上面两段代码Q可以发现它们的基本l构是类似的。不同之处在于JDK 6中通过元注?a style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px;">@SupportedAnnotationTypes来声明所支持的注解类型。另外描q程序静态结构的javax.lang.model包用了不同的类型名U。用的时候也更加单,只需要通过javac -processor annotation.pap.AssignmentProcess Demo1.javaq样的方式即可?/p>
上面介绍的这两种做法都是在编译时刻进行处理的。而有些时候则需要在q行时刻来完成对注解的处理。这个时候就需要用到Java的反API。反API提供了在q行时刻d注解信息的支持。不q前提是注解的保留策略声明的是运行时。Java反射API?a style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px;">AnnotatedElement接口提供了获取类、方法和域上的注解的实用Ҏ。比如获取到一个Classcd象之后,通过getAnnotationҎ可以获取到该类上添加的指定注解cd的注解?/p>
下面通过一个具体的实例来分析说明在实践中如何来使用和处理注解。假定有一个公司的雇员信息pȝQ从讉K控制的角度出发,寚w员的工资的更新只能由h特定角色的用h能完成。考虑到访问控刉求的普遍性,可以定义一个注解来让开发h员方便的在代码中声明讉K控制权限?br />
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredRoles
{ String[] value(); }
下一步则是如何对注解q行处理Q这里用的Java的反APIq结?a style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px;">动态代?/a>。下面是动态代理中的InvocationHandler接口的实现?br />
public class AccessInvocationHandler<T> 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的接口的代理c,使用该代理类来完成实际的操作?/p>