Writing in the front
I remember the first time I came into contact with Lombok when I was working on a new project. The first impression was not good. But it’s not bad after the whole team uses it. Said to save time, in the use of it seems to have no feeling, a bit like the space and TAB, are all at once, may be a habit problem. Getting back to the point, I’m more curious about how Lombok was implemented than debating whether it was used or not.
What is the Annotation Processor Tool
The full name of APT is Pluggable Annotation Processing API (Pluggable Annotation Processing API), which is a set of API provided by JSR269 specification. It can customize the compilation output we expect at compile time. APT cannot modify existing source code, it can only add new source code. Help us do some checking code, generate template code or configuration files etc. Common applications include Lombok, YML files with CTRL + left mouse button to locate specific files (annotation processor generates Spring-configuration-metadatad. json), Google AutoService (described below), etc.
Second, the operation process and basic principles
In terms of the overall structure of the Javac code, the compilation process can be roughly divided into one preparation process and three processing processes, as shown below.
- Preparation: Initialize the plug-in annotation handler.
- The process of parsing and populating symbol tables, including lexical and grammatical analysis. The character stream of the source code is transformed into a collection of tags, an abstract syntax tree is constructed, and the symbol table is filled. Generates symbolic addresses and symbolic information.
- Annotation processing by the plug-in annotation processor.
- Analysis and bytecode generation process, including annotation inspection, data flow and control flow analysis, decoding, bytecode generation.
If these plug-ins make changes to the syntax tree during annotation processing, the compiler will go back to parsing and populating the symbol table and reprocess it until no more changes have been made to the syntax tree by any plug-in annotation processor. Each loop is called a Round, as shown in the figure above
The above process corresponding to the code, Javac compile action entrance is com. Sun. View Javac. Main. JavaCompiler class, the above three process code logic focused on this class of the compile () and compile2 () method, The main processing of the whole compilation process is done by the 8 methods highlighted in the figure below
Customize Lombok’s project build
Maven-compiler-plugin: Set proc: None maven Annotation Processing Processor not found
<dependencies>
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${java.home}/.. /lib/tools.jar</systemPath>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<executions>
<execution>
<id>default-compile</id>
<configuration>
<! Compile source files without comment handler -->
<compilerArgument>-proc:none</compilerArgument>
<source>1.8</source>
<target>1.8</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
Copy the code
Setter and Getter annotation handlers
1. The directory tree
├ ─ Java │ │ complie. Sh │ │ │ └ ─ com │ └ ─ hinotoyk │ ├ ─ jsr269 │ │ Getter. Java │ │ GetterProcessor. Java │ │ Setter. Java │ │ Java │ ├ ─ 0├ ─ sci-sci-imp. Java │ ├ ─ sci-sci-imp javax.annotation.processing.ProcessorCopy the code
2. Create Getter and Setter annotations
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Getter {
}
Copy the code
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Setter {
}
Copy the code
3. Create annotation handlers for getters and setters
import com.sun.source.tree.Tree;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.*;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;
// Get interested in getters
@SupportedAnnotationTypes("com.hinotoyk.jsr269.Getter")
// Support version, use 1.8 to write this
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class GetterProcessor extends AbstractProcessor {
// Enter logs at compile time
private Messager messager;
// A tool to convert Element to JCTree, which provides an abstract syntax tree to be processed
private JavacTrees trees;
// encapsulates methods for creating AST nodes
private TreeMaker treeMaker;
// provides methods to create identifiers
private Names names;
@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) {
// Get all elements marked by the @getter annotation (this element could be classes, variables, methods, and so on)
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Getter.class);
set.forEach(element -> {
// Convert Element to JCTree
JCTree jcTree = trees.getTree(element);
jcTree.accept(new TreeTranslator() {
/*** * jctree. Visitor has a number of methods that we can override to get the desired information (from the method's parameters) : * For example, override visitClassDef to get the class information; * Override the visitMethodDef method to get the method information; * Rewrite the visitVarDef method to get the variable information; *@param jcClassDecl
*/
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
// Create a List of variable syntax tree nodes
List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
// Iterate over the defs, which is a detailed statement of class definition, including field, method definition, and so on
for (JCTree tree : jcClassDecl.defs) {
if(tree.getKind().equals(Tree.Kind.VARIABLE)) { JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree; jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl); }}// Generate a method for a variable
jcVariableDeclList.forEach(jcVariableDecl -> {
messager.printMessage(Diagnostic.Kind.NOTE, "get " + jcVariableDecl.getName() + " has been processed");
treeMaker.pos = jcVariableDecl.pos;
// Class to append the generated Getter
jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));
});
super.visitClassDef(jcClassDecl); }}); });// We have modified the AST, so return true
return true;
}
private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
* JCBlock: statement block syntax tree node * JCReturn: return statement syntax tree node * JCClassDecl: class definition syntax tree node * JCVariableDecl: Field/variable definition syntax tree nodes * JCMethodDecl: method definition syntax tree nodes * JCModifiers: access flag syntax tree nodes * JCExpression: expression syntax tree nodes, common subclasses are as follows * JCAssign: Assignment statement syntax tree node * JCIdent: Identifier syntax tree node, which can be variables, types, keywords, and so on */
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: access flags
getNewMethodName(jcVariableDecl.getName()),//name: method name
jcVariableDecl.vartype,//restype: Return type
List.nil(),//typarams: generic parameter list
List.nil(),//params: parameter list
List.nil(),Thrown: List of exception declarations
body,/ / the method 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())); }}Copy the code
A Settter annotation handler is similar to a Getter, except that the steps to generate a set method are different. See OpenJDK-7 TreeMaker for details
import com.sun.source.tree.Tree;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.*;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;
@SupportedAnnotationTypes("com.hinotoyk.jsr269.Setter")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class SetterProcessor extends AbstractProcessor {
private Messager messager;
private JavacTrees trees;
private TreeMaker treeMaker;
private Names names;
@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());
// Generate input parameters
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), // The method limits the value
setNewMethodName(jcVariableDecl.getName()), / / the method name
treeMaker.Type(new Type.JCVoidType()), // Return type
List.nil(),
paramList, / / into the refs
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())); }}Copy the code
4. Create a test Class
import com.hinotoyk.jsr269.Getter;
import com.hinotoyk.jsr269.Setter;
@Setter
@Getter
public class TestDemo {
private String name;
public static void main(String[] args) {
TestDemo testDemo = new TestDemo();
testDemo.setName("yk"); System.out.println(testDemo.getName()); }}Copy the code
5. Run
Running it directly is bound to generate an error, because this is actually supposed to be a separate project, an annotation processor project and a business project. We need to use the compiled annotation processor project to process other projects during compilation time. If we compile it together, we will definitely fail. We can write shell script as follows:
#! /usr/bin bash
if [ -d classes ]; then
rm -rf classes;
fi
Create an output directory
mkdir classes
Compile the annotation processor-specific classes first, and -d specifies the output to the classes folder
javac -encoding UTF-8 -cp ${JAVA_HOME}/lib/tools.jar com/hinotoyk/jsr269/*.java -d classes/
-cp specifies the class path (fully qualified class name at runtime) -processor specifies the annotation class to compile the test class, and the class file is also specified to the classes folder
javac -cp classes -processor com.hinotoyk.jsr269.GetterProcessor,com.hinotoyk.jsr269.SetterProcessor com/hinotoyk/test/TestDemo.java -d classes/
#-cp specifies the class path and runs it
java -cp classes com.hinotoyk.test.TestDemo
Copy the code
You can clearly see that after executing the script, it runs normally, printing yk correctly
6. Packaging (SPI mechanism)
SPI (Service Provider Interface) is a built-in Service discovery mechanism in JDK. It is mainly used by developers of the framework, such as the java.sql.Driver interface, which can be implemented by database vendors. Of course, in order to let the system know the existence of specific implementation classes, it needs to use fixed storage rules. You need to create a file named after the service interface in the Meta-INF /services/ directory under Resources. The contents of this file are the implementation classes of the interface
The operation of packaging is relatively simple, Java has an SPI mechanism, including JDBC, Spring, Dubbo are used extensively. For example, you can see the jar package of Mysql connection, the corresponding directory has been found
Create a meta-INF folder under resources and a service folder below, Finally create a named javax.mail. The annotation. Processing. After the file Processor, write the fully qualified name in this file implementation class can, if multiple implementation classes, press enter branch to fill in
└ ─ resources └ ─ meta-inf └ ─ services └ ─ javax.mail. Annotation. Processing. The ProcessorCopy the code
com.hinotoyk.jsr269.GetterProcessor
com.hinotoyk.jsr269.SetterProcessor
Copy the code
Comment out the test class before packaging, otherwise the compiler will report an error
Packaging: the clean – > complie – > package – > install
Annotation processor SPI specific implementation can view the source code, the path is as follows: com.sun.tools.javac.main.JavaCompiler->compile()->this.initProcessAnnotations()-> this.procEnvImpl.setProcessors()->this.initProcessorIterator()->JavacProcessingEnvironment.ServiceIterator()
5. Use custom Lombok for engineering projects
1. Create a New Maven project and introduce dependencies
<dependency>
<groupId>com.hinotoyk.jsr269</groupId>
<artifactId>jsr269Demo</artifactId>
<version>1.0 the SNAPSHOT</version>
</dependency>
Copy the code
2. Create and run Demo
package com.hinotoyk.nichijou.Test;
import com.hinotoyk.jsr269.Getter;
import com.hinotoyk.jsr269.Setter;
@Getter
@Setter
public class Demo {
private String name;
public Demo(a){}public static void main(String[] args) {
Demo a = new Demo();
a.setName("ykkkkkk"); System.out.println(a.getName()); }}Copy the code
Run directly, as shown in the figure
Vi. Introduction: Use of AutoService
AutoService’s Github and Readme have tutorials
1. Delete the mate-INF folder and modify the POM file
Delete the mate-INF folder we created above
Add corresponding dependencies and modify maven’s compiled plug-in
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hinotoyk.jsr269</groupId>
<artifactId>jsr269Demo</artifactId>
<version>1.0 the SNAPSHOT</version>
<packaging>jar</packaging>
<name>jsr269Demo</name>
<! -- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${java.home}/.. /lib/tools.jar</systemPath>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service-annotations</artifactId>
<version>1.0-rc7</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0-rc7</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
Copy the code
2. Add an autoService annotation
Add @AutoService(Processor.class) to both SetterProcessor and getterProcessor classes
Pack 3.
Clean ->complie->package->install
Let’s look at the compiled output, which is actually generated automatically by the annotation processor for us
Write in the last
This article finally touch out, when writing this article to understand a lot of knowledge points, but also including the knowledge of the compiler did not contact before, looked at a lot of technical masters of the article, also read a rough “in-depth understanding of Java virtual machine” inside the relevant chapter, income bandits shallow.
The above.
The resources
JVM series 6 (custom plug-in annotator) Javac command first look at Java annotation processor — modify syntax tree at compile time Advanced JVM Features and Best Practices (3rd edition)