Spring中的100个问题-注解是如何工作的(二)

分类: SPRING 发布于:

注解定义了很多影响编译器行为的方式,也使得bean的行为看起来不那么透明。

注解是从编译阶段开始的

Java编译器(java compiler)工作时要经过几个阶段:

  • 解析(Parse)阶段[词法、语法解析阶段]

扫描 *.java 源文件, 映射源代码为AST-Nodes(抽象树)

  • 生成符号表(symbol table)

  • 处理注解(process annotations)

javac默认会处理针对特定语法单位上的注解,比如 class,function,field

  • 处理语法树的的属性

这个过程包括名称解析,类型检查和常量打包等

  • 数据流(dataflow analysis)分析

  • desugar-重写语法书(AST Rewrite), 翻译一些语法糖

  • 最后生成classfile

从java6开始, 在JSR269中定义了如何在编译阶段自定义可插拔(pluggable)的注解API。

在javac的默认输出中可以确认到这个信息

-g                         生成所有调试信息
  -g:none                    不生成任何调试信息
  -g:{lines,vars,source}     只生成某些调试信息
  -nowarn                    不生成任何警告
  -verbose                   输出有关编译器正在执行的操作的消息
  -deprecation               输出使用已过时的 API 的源位置
  -classpath <路径>            指定查找用户类文件和注释处理程序的位置
  -cp <路径>                   指定查找用户类文件和注释处理程序的位置
  -sourcepath <路径>           指定查找输入源文件的位置
  -bootclasspath <路径>        覆盖引导类文件的位置
  -extdirs <目录>              覆盖所安装扩展的位置
  -endorseddirs <目录>         覆盖签名的标准路径的位置
  -proc:{none,only}          控制是否执行注释处理和/或编译。
  -processor <class1>[,<class2>,<class3>...] 要运行的注释处理程序的名称; 绕过默认的搜索进程
  -processorpath <路径>        指定查找注释处理程序的位置
  -parameters                生成元数据以用于方法参数的反射
  -d <目录>                    指定放置生成的类文件的位置
  -s <目录>                    指定放置生成的源文件的位置
  -h <目录>                    指定放置生成的本机标头文件的位置
  -implicit:{none,class}     指定是否为隐式引用文件生成类文件
  -encoding <编码>             指定源文件使用的字符编码
  -source <发行版>              提供与指定发行版的源兼容性
  -target <发行版>              生成特定 VM 版本的类文件
  -profile <配置文件>            请确保使用的 API 在指定的配置文件中可用
  -version                   版本信息
  -help                      输出标准选项的提要
  -A关键字[=值]                  传递给注释处理程序的选项
  -X                         输出非标准选项的提要
  -J<标记>                     直接将 <标记> 传递给运行时系统
  -Werror                    出现警告时终止编译
  @<文件名>                     从文件读取选项和文件名

以下两个输入参数与注解有关。

-processor [,,...] 要运行的注释处理程序的名称; 绕过默认的搜索进程

-processorpath <路径> 指定查找注释处理程序的位置

如何实现一个注解处理器(annotation procssors)

定义一个继承自AbstractProcessor的子类,同时必须包含两个注解(SupportedAnnotationTypes、SupportedSourceVersion)

必须覆盖的两个接口:

public synchronized void init(ProcessingEnvironment processingEnv)

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)

这两个方法在编译阶段被java compiler 调用(仅调用一次)

在maven编译阶段(compiler:compile),显式指定processor可以通过 这两个可选项实现 [compiler:compile](https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html)

编译阶段检查代码格式的例子

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface Setter {
}
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;

@SupportedAnnotationTypes({"annotation.Setter"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class SetterProcessor extends AbstractProcessor {

    private Messager messager;

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // get elements annotated with the @Setter annotation
        Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(Setter.class);

        for (Element element : annotatedElements) {
            if (element.getKind() == ElementKind.METHOD) {
                // only handle methods as targets
                checkMethod((ExecutableElement) element);
            }
        }

        // don't claim annotations to allow other processors to process them
        return false;
    }

    private void checkMethod(ExecutableElement method) {
        // check for valid name
        String name = method.getSimpleName().toString();
        if (!name.startsWith("set")) {
            printError(method, "setter name must start with \"set\"");
        } else if (name.length() == 3) {
            printError(method, "the method name must contain more than just \"set\"");
        } else if (Character.isLowerCase(name.charAt(3))) {
            if (method.getParameters().size() != 1) {
                printError(method, "character following \"set\" must be upper case");
            }
        }

        // check, if setter is public
        if (!method.getModifiers().contains(Modifier.PUBLIC)) {
            printError(method, "setter must be public");
        }

        // check, if method is static
        if (method.getModifiers().contains(Modifier.STATIC)) {
            printError(method, "setter must not be static");
        }
    }

    private void printError(Element element, String message) {
        messager.printMessage(Diagnostic.Kind.ERROR, message, element);
    }

    @Override
    public void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);

        // get messager for printing errors
        messager = processingEnvironment.getMessager();
    }
}
public class AnnotationProcessorTest {
    
    @Setter
    private void noValue(String value) {}

    @Setter
    public void setString(String value) {}
    
    @Setter
    public static void main(String[] args) {}
    
}

执行以上代码

javac -Xprint  -XprintRounds  -XprintProcessorInfo   -classpath . -processor SetterProcessor  AnnotationProcessorTest.java 

demo git:(master) ✗ javac -Xprint  -XprintRounds  -XprintProcessorInfo   -classpath . -processor SetterProcessor  AnnotationProcessorTest.java
循环 1:
	输入文件: {AnnotationProcessorTest}
	注释: [Setter]
	最后一个循环: false

OpenJDK8对注解的支持

jdk8u/langtools/src/share/classes/javax

.
├── annotation
│   └── processing
│       ├── AbstractProcessor.java
│       ├── Completion.java
│       ├── Completions.java
│       ├── Filer.java
│       ├── FilerException.java
│       ├── Messager.java
│       ├── ProcessingEnvironment.java
│       ├── Processor.java
│       ├── RoundEnvironment.java
│       ├── SupportedAnnotationTypes.java
│       ├── SupportedOptions.java
│       ├── SupportedSourceVersion.java
│       └── package-info.java
├── lang
│   └── model
│       ├── AnnotatedConstruct.java
│       ├── SourceVersion.java
│       ├── UnknownEntityException.java
│       ├── element
│       │   ├── AnnotationMirror.java
│       │   ├── AnnotationValue.java
│       │   ├── AnnotationValueVisitor.java
│       │   ├── Element.java
│       │   ├── ElementKind.java
│       │   ├── ElementVisitor.java
│       │   ├── ExecutableElement.java
│       │   ├── Modifier.java
│       │   ├── Name.java
│       │   ├── NestingKind.java
│       │   ├── PackageElement.java
│       │   ├── Parameterizable.java
│       │   ├── QualifiedNameable.java
│       │   ├── TypeElement.java
│       │   ├── TypeParameterElement.java
│       │   ├── UnknownAnnotationValueException.java
│       │   ├── UnknownElementException.java
│       │   ├── VariableElement.java
│       │   └── package-info.java
│       ├── overview.html
│       ├── package-info.java
│       ├── type
│       │   ├── ArrayType.java
│       │   ├── DeclaredType.java
│       │   ├── ErrorType.java
│       │   ├── ExecutableType.java
│       │   ├── IntersectionType.java
│       │   ├── MirroredTypeException.java
│       │   ├── MirroredTypesException.java
│       │   ├── NoType.java
│       │   ├── NullType.java
│       │   ├── PrimitiveType.java
│       │   ├── ReferenceType.java
│       │   ├── TypeKind.java
│       │   ├── TypeMirror.java
│       │   ├── TypeVariable.java
│       │   ├── TypeVisitor.java
│       │   ├── UnionType.java
│       │   ├── UnknownTypeException.java
│       │   ├── WildcardType.java
│       │   └── package-info.java
│       └── util
│           ├── AbstractAnnotationValueVisitor6.java
│           ├── AbstractAnnotationValueVisitor7.java
│           ├── AbstractAnnotationValueVisitor8.java
│           ├── AbstractElementVisitor6.java
│           ├── AbstractElementVisitor7.java
│           ├── AbstractElementVisitor8.java
│           ├── AbstractTypeVisitor6.java
│           ├── AbstractTypeVisitor7.java
│           ├── AbstractTypeVisitor8.java
│           ├── ElementFilter.java
│           ├── ElementKindVisitor6.java
│           ├── ElementKindVisitor7.java
│           ├── ElementKindVisitor8.java
│           ├── ElementScanner6.java
│           ├── ElementScanner7.java
│           ├── ElementScanner8.java
│           ├── Elements.java
│           ├── SimpleAnnotationValueVisitor6.java
│           ├── SimpleAnnotationValueVisitor7.java
│           ├── SimpleAnnotationValueVisitor8.java
│           ├── SimpleElementVisitor6.java
│           ├── SimpleElementVisitor7.java
│           ├── SimpleElementVisitor8.java
│           ├── SimpleTypeVisitor6.java
│           ├── SimpleTypeVisitor7.java
│           ├── SimpleTypeVisitor8.java
│           ├── TypeKindVisitor6.java
│           ├── TypeKindVisitor7.java
│           ├── TypeKindVisitor8.java
│           ├── Types.java
│           └── package-info.java
└── tools
    ├── Diagnostic.java
    ├── DiagnosticCollector.java
    ├── DiagnosticListener.java
    ├── DocumentationTool.java
    ├── FileObject.java
    ├── ForwardingFileObject.java
    ├── ForwardingJavaFileManager.java
    ├── ForwardingJavaFileObject.java
    ├── JavaCompiler.java
    ├── JavaFileManager.java
    ├── JavaFileObject.java
    ├── OptionChecker.java
    ├── SimpleJavaFileObject.java
    ├── StandardJavaFileManager.java
    ├── StandardLocation.java
    ├── Tool.java
    ├── ToolProvider.java
    ├── overview.html
    └── package-info.java

篇幅原因,下一篇分析spring中的注解。