什么是Java Annotation 注解(Annotation)机制从Java 5开始被引入,它能够向源代码中添加句法(syntactic)元数据。类、方法、变量(variables)、参数(parameters)、包(packages)都可以被注解。与Javadoc标签不同,Java注解(Annotation)能够被反射,因为它们能够被嵌入在编译器生成的class文件中,并且可以被Java虚拟机保留使得在运行时也可获取。
Java定义了一系列内建的注解,分为两类:
①用于Java代码的注解
@Override
@Deprecated
@SuppressWarnings
@SafeVarargs
@FunctionalInterface
②用于其他注解的注解(也就是“元注解”(Meta Annotation))
@Retention
@Documented
@Target
@Inherited
@Repeatable
如何实现自定义注解 自定义注解的实现非常简单,以下是一个简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Edible (true ) Item item = new Carrot(); public @interface Edible { boolean value () default false ; } @Author (first = "Oompah" , last = "Loompah" ) Book book = new Book(); public @interface Author { String first () ; String last () ; }
上面的代码定义了2个注解,并使用了它们。当注解中使用的属性名为value时,对其赋值时可以不指定属性的名称而直接写上属性值接口;除了value以外的变量名都需要使用name=value的方式赋值。
另外,可以用@Retention和@Target元注解来分别制定自定义注解的保留策略和作用范围。示例如下:
1 2 3 4 5 @Retention (RetentionPolicy.RUNTIME) @Target ({ElementType.METHOD}) public @interface Tweezable {}
@Retention元注解的可以取以下值:
CLASS: 编译器将把注释记录在类文件中,但在运行时 VM 不需要保留注释。 RUNTIME: 编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取。 SOURCE: 编译器要丢弃的注释。
@Target可以取以下值:
TYPE:Class, interface or enum declaration. FIELD:Field declaration. METHOD:Method declaration. PARAMETER:Parameter declaration. CONSTRUCTOR:Constructor declaration. LOCAL_VARIABLE:Local variable declaration. ANNOTATION_TYPE:Annotation type declaration. PACKAGE:Package declaration.
定义并使用了注解后,我们要取到注解中的值并处理它,这个自定义注解才有意义。
反射的方式使用注解 这是最简单最常见的使用自定义注解的方式,具体方法是把自定义注解的@Retention定义为RUNTIME,然后在代码中利用java反射机制读取注解的属性值。
下面是一个完整的例子: ①定义@Test和@TesterInfo这2个注解,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.mkyong.test.core;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Retention (RetentionPolicy.RUNTIME)@Target (ElementType.METHOD) public @interface Test { public boolean enabled () default true ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.mkyong.test.core;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Retention (RetentionPolicy.RUNTIME)@Target (ElementType.TYPE) public @interface TesterInfo { public enum Priority { LOW, MEDIUM, HIGH } Priority priority () default Priority.MEDIUM ; String[] tags() default "" ; String createdBy () default "Mkyong" ; String lastModified () default "03/01/2014" ; }
②创建一个简单的单元测试TestExample.java,使用上面定义的两个注解,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.mkyong.test;import com.mkyong.test.core.Test;import com.mkyong.test.core.TesterInfo;import com.mkyong.test.core.TesterInfo.Priority;@TesterInfo ( priority = Priority.HIGH, createdBy = "mkyong.com" , tags = {"sales" ,"test" } ) public class TestExample { @Test void testA () { if (true ) throw new RuntimeException("This test always failed" ); } @Test (enabled = false ) void testB () { if (false ) throw new RuntimeException("This test always passed" ); } @Test (enabled = true ) void testC () { if (10 > 1 ) { } } }
③使用Java反射API读取并处理第②步中使用的自定义注解,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 package com.mkyong.test;import java.lang.annotation.Annotation;import java.lang.reflect.Method;import com.mkyong.test.core.Test;import com.mkyong.test.core.TesterInfo;public class RunTest { public static void main (String[] args) throws Exception { System.out.println("Testing..." ); int passed = 0 , failed = 0 , count = 0 , ignore = 0 ; Class<TestExample> obj = TestExample.class; if (obj.isAnnotationPresent(TesterInfo.class)) { Annotation annotation = obj.getAnnotation(TesterInfo.class); TesterInfo testerInfo = (TesterInfo) annotation; System.out.printf("%nPriority :%s" , testerInfo.priority()); System.out.printf("%nCreatedBy :%s" , testerInfo.createdBy()); System.out.printf("%nTags :" ); int tagLength = testerInfo.tags().length; for (String tag : testerInfo.tags()) { if (tagLength > 1 ) { System.out.print(tag + ", " ); } else { System.out.print(tag); } tagLength--; } System.out.printf("%nLastModified :%s%n%n" , testerInfo.lastModified()); } for (Method method : obj.getDeclaredMethods()) { if (method.isAnnotationPresent(Test.class)) { Annotation annotation = method.getAnnotation(Test.class); Test test = (Test) annotation; if (test.enabled()) { try { method.invoke(obj.newInstance()); System.out.printf("%s - Test '%s' - passed %n" , ++count, method.getName()); passed++; } catch (Throwable ex) { System.out.printf("%s - Test '%s' - failed: %s %n" , ++count, method.getName(), ex.getCause()); failed++; } } else { System.out.printf("%s - Test '%s' - ignored%n" , ++count, method.getName()); ignore++; } } } System.out.printf("%nResult : Total : %d, Passed: %d, Failed %d, Ignore %d%n" , count, passed, failed, ignore); } }
运行上面的示例代码,结果如下:
Testing…
Priority :HIGH CreatedBy :mkyong.com Tags :sales, test LastModified :03/01/2014
1 - Test ‘testA’ - failed: java.lang.RuntimeException: This test always failed 2 - Test ‘testC’ - passed 3 - Test ‘testB’ - ignored
Result : Total : 3, Passed: 1, Failed 1, Ignore 1
与编译器结合的方法来使用注解 反射的方法局限性较大。首先,它必须定义@Retention为RetentionPolicy.RUNTIME,只能在运行时通过反射来获取注解值,使得运行时代码效率降低。其次,如果想在编译阶段利用注解来进行一些检查,对用户的某些不合理代码给出错误报告,反射的使用方法就无能为力了。
要把注解与Java编译器结合使用,Java也给出了解决方案。Java 5在引入annotation的同时,也引入了Annotation Processing Tool (apt),这是一个命令行工具,能够提供构建时(build-time)、基于源代码(source-based)对程序结构的读取功能,能够通过运行注解处理器来生成新的中间文件进而影响编译进程。不过,apt在Java 8中被移除了。
作为替代方案,可以使用JSR 269 annotation processing facility,这个机制能够在编译阶段使用注解信息,该机制是从JDK 6开始被引入的。JSR 269的标题是Pluggable Annotation Processing API,定义了一套可插拔的接口用来开发构建时注解处理器,用插件机制来扩展Java编译器。
JSR 269分为两部分: ①模拟Java编程语言的API ②编写注解处理器的API
这2部分分别位于包javax.lang.model.*和javax.annotation.processing中。
JSR 269的功能通过javac命令行的新选项暴露出来:
选项
说明
-proc:{none,only}
控制是否执行注释处理和/或编译。
-processor [,,…]
要运行的注释处理程序的名称; 绕过默认的搜索进程
-processorpath <路径>
指定查找注释处理程序的位置
javac默认启用了注解处理。如果需要仅运行注解处理而不把源代码编译成class文件,可以使用-proc:only
选项。
下面是一个简单的例子:
①定义一个名为HelloWorld的注解:
1 2 3 public @interface HelloWorld { }
②在类Dummy中使用上面定义的注解:
1 2 3 4 @HelloWorld public class Dummy { }
③继承AbstractProcessor实现自定义Processor,并使用@SupportedAnnotationTypes和@SupportedSourceVersion来标识该Processor要处理的注解和支持的java版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import java.util.Set; import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; @SupportedAnnotationTypes ("HelloWorld" ) @SupportedSourceVersion (SourceVersion.RELEASE_6) public class HelloWorldProcessor extends AbstractProcessor { @Override public synchronized void init (ProcessingEnvironment processingEnv) { super .init(processingEnv); } @Override public boolean process (Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { if (!roundEnv.processingOver()) { processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Hello Worlds!" ); } return true ; } }
④运行上面的例子:
1 javac HelloWorldProcessor.java
javac -processor HelloWorldProcessor *.java
得到的输出为:
Note: Hello Worlds!
JSR 269定义的API的详细信息,可以在Java Community Process网站 查看。