注解发生在编译阶段,它是把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);

}

}