JSR-269
原理浅析
初次使用lombok时,都需要在idea安装lombok插件,这让我们怀疑lombok的实现是通过提供自己的编译器实现的,然而实际情况并非如此,在脱离idea使用javac编译时,只要类路径有lombok的jar包,项目也可以正常编译通过。其原理在于JSR-269
规范。
Java6开始纳入了JSR-269
规范:Pluggable Annotation Processing API
(插件式注解处理器)。JSR-269
提供一套标准API来处理Annotations,具体来说,我们只需要继承AbstractProcessor
类,重写process方法实现自己的注解处理逻辑,并且在META-INF/services目录下创建javax.annotation.processing.Processor
文件注册自己实现的Annotation Processor
,在javac编译过程中编译器便会调用我们实现的Annotation Processor
,从而使得我们有机会对java编译过程中生产的抽象语法树进行修改。
javac的编译过程,大致可以分为3个过程:
- 解析与填充符号表过程
- 插入式注解处理器的注解处理过程
- 分析与字节码生成过程
解析与填充符号表过程会将源码转换为一棵抽象语法树(Abstract Syntax Tree
,AST),AST是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代表着程序代码中的一个语法结构(Construct),例如包、类型、修饰符、运算符、接口、返回值甚至代码注释等都可以是一个语法结构。在插入式注解处理器的注解处理过程中,lombok对第一步骤得到的AST进行处理,找到类似@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter等方法的相应树节点,javac使用修改后的抽象语法树(AST)生成字节码文件,因此最终生成的class文件中包含了这些自动生成的getter,setter方法。
创建注解处理器项目
- 添加依赖
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0-rc2</version>
</dependency>
- 添加注解
@Retention(RetentionPolicy.SOURCE)
public @interface HelloWorld {
}
- 自定义注解处理器
@SupportedAnnotationTypes("com.charles.annotations.HelloWorld")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
Messager messager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Hello World 123!");
return false;
}
}
- maven clean install
创建项目引入依赖
<dependency>
<groupId>com.charles</groupId>
<artifactId>demo-annotation-processor</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
调试自定义处理器
方式一:
- 命令行执行
mvnDebug clean package
- 设置idea远程调试模式,端口设置为8000
- debug启动远程调试(
MyProcessor
打断点)
方式二:
public class DemoAnnotationProcessorApplication {
public static void main(String[] args) {
javax.tools.JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
String[] strings = {"-d", "./", "src/main/java/com/charles/demo/HelloWorldDemo.java"};
// 0 表示成功, 其他表示出现了错误
int i = javaCompiler.run(null, null, null, strings);
if (i == 0) {
System.out.println("成功");
} else {
System.out.println("错误");
}
}
}
AutoService
的使用
AutoService
是一个注解处理器。AutoService
框架的作用是自动生成SPI清单文件(META-INF/services下的文件)。不用它也行,如果不使用它就需要手动去创建这个文件、手动往这个文件里添加服务(接口实现)。
AutoService源码分析
主要逻辑在AutoServiceProcessor#process
方法中,通过实现AbstractProcessor
的process方法来实现功能。获取所有的AutoService
的元素,将接口和实现存放在providers
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
try {
return processImpl(annotations, roundEnv);
} catch (Exception e) {
// We don't allow exceptions of any kind to propagate to the compiler
StringWriter writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
fatalError(writer.toString());
return true;
}
}
private boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
generateConfigFiles();
} else {
processAnnotations(annotations, roundEnv);
}
return true;
}
private void processAnnotations(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);
log(annotations.toString());
log(elements.toString());
for (Element e : elements) {
// TODO(gak): check for error trees?
TypeElement providerImplementer = (TypeElement) e;
AnnotationMirror providerAnnotation = getAnnotationMirror(e, AutoService.class).get();
DeclaredType providerInterface = getProviderInterface(providerAnnotation);
TypeElement providerType = (TypeElement) providerInterface.asElement();
log("provider interface: " + providerType.getQualifiedName());
log("provider implementer: " + providerImplementer.getQualifiedName());
if (!checkImplementer(providerImplementer, providerType)) {
String message = "ServiceProviders must implement their service provider interface. "
+ providerImplementer.getQualifiedName() + " does not implement "
+ providerType.getQualifiedName();
error(message, e, providerAnnotation);
}
String providerTypeName = getBinaryName(providerType);
String providerImplementerName = getBinaryName(providerImplementer);
log("provider interface binary name: " + providerTypeName);
log("provider implementer binary name: " + providerImplementerName);
providers.put(providerTypeName, providerImplementerName);
}
}
AutoServiceProcessor#generateConfigFiles
,生成SPI注册文件。如果SPI路径上文件已经存在,先要把已存在的SPI清单读进内存,再把新的provider加进去,然后全部写出,覆盖原来的文件。
private void generateConfigFiles() {
Filer filer = processingEnv.getFiler();
for (String providerInterface : providers.keySet()) {
String resourceFile = "META-INF/services/" + providerInterface;
log("Working on resource file: " + resourceFile);
try {
SortedSet<String> allServices = Sets.newTreeSet();
try {
// would like to be able to print the full path
// before we attempt to get the resource in case the behavior
// of filer.getResource does change to match the spec, but there's
// no good way to resolve CLASS_OUTPUT without first getting a resource.
FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "",
resourceFile);
log("Looking for existing resource file at " + existingFile.toUri());
Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
log("Existing service entries: " + oldServices);
allServices.addAll(oldServices);
} catch (IOException e) {
// According to the javadoc, Filer.getResource throws an exception
// if the file doesn't already exist. In practice this doesn't
// appear to be the case. Filer.getResource will happily return a
// FileObject that refers to a non-existent file but will throw
// IOException if you try to open an input stream for it.
log("Resource file did not already exist.");
}
Set<String> newServices = new HashSet<String>(providers.get(providerInterface));
if (allServices.containsAll(newServices)) {
log("No new service entries being added.");
return;
}
allServices.addAll(newServices);
log("New service file contents: " + allServices);
FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "",
resourceFile);
OutputStream out = fileObject.openOutputStream();
ServicesFiles.writeServiceFile(allServices, out);
out.close();
log("Wrote to: " + fileObject.toUri());
} catch (IOException e) {
fatalError("Unable to create " + resourceFile + ", " + e);
return;
}
}
}
模拟lombok生成get/set方法
GetterProcessor
,Getter
注解的处理器
//对Getter感兴趣
@SupportedAnnotationTypes("com.charles.processor.Getter")
//支持的版本,使用1.8就写这个
//@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class GetterProcessor extends AbstractProcessor {
// 编译时期输入日志的
private Messager messager;
// 将Element转换为JCTree的工具,提供了待处理的抽象语法树
private JavacTrees trees;
// 封装了创建AST节点的一些方法
private TreeMaker treeMaker;
// 提供了创建标识符的方法
private Names names;
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.messager = processingEnv.getMessager();
this.trees = JavacTrees.instance(processingEnv);
Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
this.treeMaker = TreeMaker.instance(context);
this.names = Names.instance(context);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 获取被@Getter注解标记的所有元素(这个元素可能是类、变量、方法等等)
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Getter.class);
set.forEach(element -> {
// 将Element转换为JCTree
JCTree jcTree = trees.getTree(element);
jcTree.accept(new TreeTranslator() {
/***
* JCTree.Visitor有很多方法,我们可以通过重写对应的方法,(从该方法的形参中)来获取到我们想要的信息:
* 如: 重写visitClassDef方法, 获取到类的信息;
* 重写visitMethodDef方法, 获取到方法的信息;
* 重写visitVarDef方法, 获取到变量的信息;
* @param jcClassDecl
*/
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
//创建一个变量语法树节点的List
List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
// 遍历defs,即是类定义的详细语句,包括字段、方法的定义等等
for (JCTree tree : jcClassDecl.defs) {
if (tree.getKind().equals(Tree.Kind.VARIABLE)) {
JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree;
jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
}
}
// 对于变量进行生成方法的操作
jcVariableDeclList.forEach(jcVariableDecl -> {
messager.printMessage(Diagnostic.Kind.NOTE, "get " + jcVariableDecl.getName() + " has been processed");
treeMaker.pos = jcVariableDecl.pos;
//类里的前面追加生成的Getter方法
jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));
});
super.visitClassDef(jcClassDecl);
}
});
});
//我们有修改过AST,所以返回true
return true;
}
private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
/***
* JCStatement:声明语法树节点,常见的子类如下
* JCBlock:语句块语法树节点
* JCReturn:return语句语法树节点
* JCClassDecl:类定义语法树节点
* JCVariableDecl:字段/变量定义语法树节点
* JCMethodDecl:方法定义语法树节点
* JCModifiers:访问标志语法树节点
* JCExpression:表达式语法树节点,常见的子类如下
* JCAssign:赋值语句语法树节点
* JCIdent:标识符语法树节点,可以是变量,类型,关键字等等
*/
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>(); statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName())));
JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
return treeMaker.MethodDef(
treeMaker.Modifiers(Flags.PUBLIC),//mods:访问标志
getNewMethodName(jcVariableDecl.getName()),//name:方法名
jcVariableDecl.vartype,//restype:返回类型
List.nil(),//typarams:泛型参数列表
List.nil(),//params:参数列表
List.nil(),//thrown:异常声明列表
body,//方法体
null);
}
private Name getNewMethodName(Name name) {
String s = name.toString();
return names.fromString("get" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
}
}
SetterProcessor
,Setter
注解的处理器
@SupportedAnnotationTypes("com.charles.processor.Setter")
//@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class SetterProcessor extends AbstractProcessor {
private Messager messager;
private JavacTrees trees;
private TreeMaker treeMaker;
private Names names;
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.messager = processingEnv.getMessager();
this.trees = JavacTrees.instance(processingEnv);
Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
this.treeMaker = TreeMaker.instance(context);
this.names = Names.instance(context);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Setter.class);
set.forEach(element -> {
JCTree jcTree = trees.getTree(element);
jcTree.accept(new TreeTranslator() {
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
for (JCTree tree : jcClassDecl.defs) {
if (tree.getKind().equals(Tree.Kind.VARIABLE)) {
JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree;
jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
}
}
jcVariableDeclList.forEach(jcVariableDecl -> {
messager.printMessage(Diagnostic.Kind.NOTE, "set " + jcVariableDecl.getName() + " has been processed");
treeMaker.pos = jcVariableDecl.pos;
jcClassDecl.defs = jcClassDecl.defs.prepend(makeSetterMethodDecl(jcVariableDecl));
});
super.visitClassDef(jcClassDecl);
}
});
});
return true;
}
private JCTree.JCMethodDecl makeSetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
statements.append(
treeMaker.Exec(
treeMaker.Assign(
treeMaker.Select(
treeMaker.Ident(names.fromString("this")),
names.fromString(jcVariableDecl.name.toString())
),
treeMaker.Ident(names.fromString(jcVariableDecl.name.toString()))
)
)
);
JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
// 生成入参
JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), jcVariableDecl.getName(),jcVariableDecl.vartype, null);
List<JCTree.JCVariableDecl> paramList = List.of(param);
return treeMaker.MethodDef(
treeMaker.Modifiers(Flags.PUBLIC), // 方法限定值
setNewMethodName(jcVariableDecl.getName()), // 方法名
treeMaker.Type(new Type.JCVoidType()), // 返回类型
List.nil(),
paramList, // 入参
List.nil(),
body,
null
);
}
private Name setNewMethodName(Name name) {
String s = name.toString();
return names.fromString("set" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
}
}