注解发生在编译阶段,它是把parse和enter阶段生成的AST语法树,经过AbstractProcessor类处理生成修改过的语法树,再交给下游进行处理。Lombook就是用这种方式实现的,注解暂时不支持继承。
一、JDK内置的支持
内置的三种标准注解
- @SuppressWarnings("unchecked"):unchecked执行了未检查的转换时的警告,例如当使用集合时没有用泛型来指定集合保存的类型;Unused没有被访问过的元素,去除警告;Fallthrough当Switch程序块直接通往下一种情况二没有Break时的警告;Path在类路径,源文件路径等中有不存在的路径时的警告;Serial当在可序列化的类上缺少serialVersionUID定义时的警告;Finally任何finally子句不能正常完成时的警告;All关于上边所有情况的警告
- @Override:当前的方法覆盖超类中的方法;
- @Deprecated:当前的方法已过时,编译器会发出警告
- @SafeVarargs:参数安全,阻止编译过程中发现警告
四种元数据
- @Targer:表示此注解可以用在什么地方,用ElementType.做为前缀
- CONSTRUCTOR构造器声明
- FIELD域声明,
- LOCAL_VARIABLE局部变量声明
- METHOD方法声明
- PACKAGE包声明
- PARAMETER参数声明
- TYPE类、接口或enum声明
- ANNOTATION注解
- @Retention:表示需要在什么级别保存该注解信息,用RetentionPolicy.做为前缀
- SOURCE:注解只在源码阶段生效,会在编译成class中不显示
- CLASS:注解在class文件中可用,但不会加载到VM中
- RUNTIME:VM将在运行期也保留注解,因此可以通过反射机制读取注解的信息
- @Documented:将此注解包含在javaDoc中
- @Inherited:允许子类继承父类中的注解如果一个使用了@Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该class 的子类。
- @Repeatable:1.8的新特性,同一注意会以数组形式运行,类似过滤器链
属性
在注解中只可定义属性不可定义方法,且属性为基础值,比如public @interface Trace:
- int id() default 1:在类上可以用@Trace(1)或@Trace(id=1)
- 如果无参数:在类上可以用@Trace
编译时自动启动方式
其原理就是JVM在编译过程中会扫描所有.jar包META-INF/services/javax.annotation.processiong.Processor文件,在这个文件中指定注解类就可以了。以lombook为例。其文件结构如下:
gradle: incremental.annotation.processors MANIFEST.MF services: javax.annotation.processing.Processor lombok.core.LombokApp lombok.core.PostCompilerTransformation lombok.core.runtimeDependencies.RuntimeDependencyInfo lombok.eclipse.EclipseAnnotationHandler lombok.eclipse.EclipseASTVisitor lombok.eclipse.handlers.EclipseSingularsRecipes$EclipseSingularizer lombok.installer.IdeLocationProvider lombok.javac.handlers.JavacSingularsRecipes$JavacSingularizer lombok.javac.JavacAnnotationHandler lombok.javac.JavacASTVisitor org.mapstruct.ap.spi.AstModifyingAnnotationProcessor |
javax.annotation.processing.Processor 内容如下,指定了注解类,其核心源码如下 |
lombok.launch.AnnotationProcessorHider$AnnotationProcessor |
//创建一个类加载器,它会加载.SCL.lombok文件(其实就是.class文件),存放在lombok.javac路径下。这个类加载器的作用是为了避免污染项目的命名空间 private static AbstractProcessor createWrappedInstance() { ClassLoader cl = Main.getShadowClassLoader();
try { Class<?> mc = cl.loadClass("lombok.core.AnnotationProcessor"); return (AbstractProcessor)mc.getDeclaredConstructor().newInstance(); } catch (Throwable var2) { if (var2 instanceof Error) { throw (Error)var2; } else if (var2 instanceof RuntimeException) { throw (RuntimeException)var2; } else { throw new RuntimeException(var2); } } } |
二、自定义注解
改写AST语法树
<dependency> <groupId>com.sun</groupId> <artifactId>tools</artifactId> <version>1.8</version> <scope>provided</scope> </dependency>
<dependency> <groupId>com.sun</groupId> <artifactId>tools</artifactId> <version>1.8</version> <scope>system</scope> <systemPath>${JAVA_HOME}/lib/tools.jar</systemPath> </dependency> |
@Target(ElementType.TYPE) @Retentioin(RetentionPolicy.SOURCE) public @interface Data { } |
@SupportedAnnotationTypes("com.Data") //注解类的全限定名 @SupportedSourceVersion(SourceVersion.RELEASE_8)//最高支持1.8的JDK编译出来的类 public class DataAnnoProcessor extends AbstractProcessor {
/*语法树元素的基类*/ private JavacTrees javacTrees;
/*创建方法树的方法*/ private TreeMaker treeMaker;
/*提供了访问标识符的方法,比如this或super这样的引用*/ private Names names;
/*完成初始化工作*/ @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); Context context = (Context) ((JavacProcessingEnvironment)processingEnv).getContext(); javacTrees = JavacTrees.instance(processingEnv); treeMaker = TreeMaker.instance(context); names = Names.instance(context); }
/*做AST语法树的修改*/ @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { //取得所有被Data注释的类的集合 Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Data.class);
for(Element element : set){ //得到当前类的AST树 JCTree tree = javacTrees.getTree(element); //这个相当于事件,当遍历AST过程会触发相应的方法 tree.accept(new TreeTranslator(){ @Override public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { // jcClassDecl.defs = jcClassDecl.defs.prepend(genMethod()); super.visitClassDef(jcClassDecl); } }); }
return false; }
//生成新方法 private JCTree.JCMethodDecl genMethod(){ return null; } } |
javac -processor com.jsr269.DataAnnoProcessor Test.java 需要用-processor指定要使用的的注解,或在maven-compiler-plugin中配置compilerArgument参数指定值为-proc:none |
Spring扫描实现
注解一般都需要一个解析器,上面是一种实现,这也是一种实现,一般需要借助spring等启动类来达到运行的目的;
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface UseCase { public int id() default -1;//由于default不能为null,所以一般用””或负数来表示特殊值 public String description() default "no description"; } ///:~ |
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("FoundCase:" + 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); } } |