I learned to use compile time annotations to generate new files a long time ago, such as the butter knife in Android, but my goal is not to generate new classes, but to modify bytecode at compile time and make plug-ins like Lombok. Recently, I took time to find some materials and finally wrote my own compile time bytecode modification annotations
reference resources: https://liuyehcf.github.io/2018/02/02/Java-JSR-269-%E6%8F%92%E5%85%A5%E5%BC%8F%E6%B3%A8%E8%A7%A3%E5%A4%84%E7%90%86%E5%99%A8/
I wrote about the corresponding mavan configuration earlier. There are a lot on the Internet, so I won't repeat it here
Note that before writing, you must first introduce the jdk's own tools Jar file
<dependencies> <dependency> <groupId>jdk.tools</groupId> <artifactId>jdk.tools</artifactId> <version>${java.version}</version> <scope>system</scope> <systemPath>${env.JAVA_HOME}/lib/tools.jar</systemPath> </dependency> </dependencies>
NoArgsConstructorProcessor
@SupportedAnnotationTypes("annotations.NoArgsConstructor") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class NoArgsConstructorProcessor extends AbstractProcessor { private JavacTrees javacTrees; private TreeMaker treeMaker; private Names names; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); javacTrees=JavacTrees.instance(context);//Convert Element to JCTree treeMaker=TreeMaker.instance(context);// Construct syntax tree node names=Names.instance(context);// Used to generate identifiers } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(NoArgsConstructor.class); elements.forEach((Consumer<Element>) element -> javacTrees.getTree(element).accept(new TreeTranslator(){ @Override//Use visitor mode to access the definition of the class public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { super.visitClassDef(jcClassDecl); if(!hasNoArgsConstructor(jcClassDecl)){ // Note that the List returns a new object each time jcClassDecl.defs=jcClassDecl.defs.append(createNoArgsConstructor(jcClassDecl)); } } })); return true; } private JCTree.JCMethodDecl createNoArgsConstructor(JCTree.JCClassDecl jcClassDecl) { treeMaker.pos=jcClassDecl.pos;//Note that the offset must be aligned JCTree.JCBlock block=treeMaker.Block(0, List.nil());//Empty code block return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),//Access identity names.fromString("<init>"),//Method name treeMaker.TypeIdent(TypeTag.VOID),//return type List.nil(),//Generic list List.nil(),//parameter list List.nil(),//Exception thrown block,//Code block null); } // Determine whether there is a default null parameter constructor private boolean hasNoArgsConstructor(JCTree.JCClassDecl jcClassDecl) { for (JCTree jcTree:jcClassDecl.defs){ if(jcTree.getKind().equals(Tree.Kind.METHOD)){ JCTree.JCMethodDecl methodDecl= (JCTree.JCMethodDecl) jcTree; if(methodDecl.getName().toString().equals("<init>")&&methodDecl.getParameters().isEmpty()){ return true; } } } return false; } }
AllArgsConstructorProcessor
@SupportedAnnotationTypes("annotations.AllArgsConstructor") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class AllArgsConstructorProcessor extends AbstractProcessor { private JavacTrees javacTrees; private TreeMaker treeMaker; private Names names; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); javacTrees=JavacTrees.instance(context); treeMaker=TreeMaker.instance(context); names=Names.instance(context); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AllArgsConstructor.class); elements.forEach(new Consumer<Element>() { @Override public void accept(Element element) { javacTrees.getTree(element).accept(new TreeTranslator(){ @Override public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { super.visitClassDef(jcClassDecl); jcClassDecl.defs=jcClassDecl.defs.append(createAllArgsConstructor(jcClassDecl)); } }); } }); return true; } private JCTree.JCMethodDecl createAllArgsConstructor(JCTree.JCClassDecl jcClassDecl) { treeMaker.pos=jcClassDecl.pos; List<JCTree.JCVariableDecl> param=getParam(jcClassDecl); // ListBuffer is similar to Java Native list. You can directly use append or use toList to convert to tools List under jar ListBuffer<JCTree.JCStatement> buffer=new ListBuffer<>(); param.forEach(variableDecl -> buffer.append( treeMaker.Exec(treeMaker.Assign(//Assignment operation treeMaker.Select(treeMaker.Ident(names.fromString("this")),variableDecl.name),// this.<name> treeMaker.Ident(variableDecl.name)))));//<param.name> JCTree.JCBlock block = treeMaker.Block(0, buffer.toList()); return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),names.fromString("<init>"), treeMaker.TypeIdent(TypeTag.VOID), List.nil(),param, List.nil(),block,null); } // Get all non static Final variables private List<JCTree.JCVariableDecl> getParam(JCTree.JCClassDecl jcClassDecl) { ListBuffer<JCTree.JCVariableDecl> buffer = new ListBuffer<>(); for (JCTree tree:jcClassDecl.defs){ if(tree.getKind().equals(Tree.Kind.VARIABLE)){ JCTree.JCVariableDecl variableDecl= (JCTree.JCVariableDecl) tree; Set<Modifier> flags = variableDecl.mods.getFlags(); if(!flags.contains(Modifier.FINAL)&&!flags.contains(Modifier.STATIC)){ buffer.append(treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER),variableDecl.name, variableDecl.vartype,null)); } } } return buffer.toList(); } }
DataProcessor
@SupportedAnnotationTypes("annotations.Data") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class DataProcessor extends AbstractProcessor { private JavacTrees javacTrees; private TreeMaker treeMaker; private Names names; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); javacTrees=JavacTrees.instance(context); treeMaker=TreeMaker.instance(context); names=Names.instance(context); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { roundEnv.getElementsAnnotatedWith(Data.class).forEach(new Consumer<Element>() { @Override public void accept(Element element) { javacTrees.getTree(element).accept(new TreeTranslator(){ @Override public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { super.visitClassDef(jcClassDecl); List<JCTree.JCVariableDecl> param=getParam(jcClassDecl); param.forEach(variableDecl -> { // Add getter setter jcClassDecl.defs=jcClassDecl.defs.append(createGetter(variableDecl)); jcClassDecl.defs=jcClassDecl.defs.append(createSetter(variableDecl)); }); } }); } }); return true; } private JCTree.JCMethodDecl createSetter(JCTree.JCVariableDecl variableDecl) { treeMaker.pos=variableDecl.pos; JCTree.JCBlock block = treeMaker.Block(0, List.of(treeMaker.Exec(treeMaker.Assign( treeMaker.Select(treeMaker.Ident(names.fromString("this")), variableDecl.name), treeMaker.Ident(variableDecl.name))))); return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), names.fromString(toSetter(variableDecl.getName().toString())),treeMaker.TypeIdent(TypeTag.VOID), List.nil(),List.of(variableDecl),List.nil(),block,null); } private String toSetter(String name) { return "set"+name.substring(0,1).toUpperCase()+name.substring(1); } private JCTree.JCMethodDecl createGetter(JCTree.JCVariableDecl variableDecl) { treeMaker.pos=variableDecl.pos; JCTree.JCStatement statement=treeMaker.Return(// return treeMaker.Select(treeMaker.Ident(names.fromString("this")),variableDecl.name));// this.<name> JCTree.JCBlock block = treeMaker.Block(0, List.of(statement)); return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), names.fromString(toGetter(variableDecl.getName().toString())),variableDecl.vartype,List.nil(), List.nil(),List.nil(),block,null); } private String toGetter(String name) { return "get"+name.substring(0,1).toUpperCase()+name.substring(1); } private List<JCTree.JCVariableDecl> getParam(JCTree.JCClassDecl jcClassDecl) { ListBuffer<JCTree.JCVariableDecl> buffer = new ListBuffer<>(); for (JCTree tree:jcClassDecl.defs){ if(tree.getKind().equals(Tree.Kind.VARIABLE)){ JCTree.JCVariableDecl variableDecl= (JCTree.JCVariableDecl) tree; Set<Modifier> flags = variableDecl.mods.getFlags(); if(!flags.contains(Modifier.FINAL)&&!flags.contains(Modifier.STATIC)){ buffer.append(treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER),variableDecl.name, variableDecl.vartype,null)); } } } return buffer.toList(); } }
Study notes
Document address
https://liuyehcf.github.io/2018/02/02/Java-JSR-269-%E6%8F%92%E5%85%A5%E5%BC%8F%E6%B3%A8%E8%A7%A3%E5%A4%84%E7%90%86%E5%99%A8/
prepare
You must import jdk's tools Jar file
<dependency> <groupId>jdk.tools</groupId> <artifactId>jdk.tools</artifactId> <version>${java.version}</version> <scope>system</scope> <systemPath>${env.JAVA_HOME}/lib/tools.jar</systemPath> </dependency>
Add packaging plug-ins to prevent dead circulation
<build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <configuration> <source>1.8</source> <target>1.8</target> <!-- Disable annotation processing for ourselves.--> <compilerArgument>-proc:none</compilerArgument> </configuration> </plugin> </plugins> </build>
Javax under MATE-INF/services annotation. processing. Processor register annotation processor
Basic concepts
Java compilation process
Parsing and filling symbol table process
Annotation processing process of plug-in annotation processor
Analysis and bytecode generation process
JCTree
The base class and the attribute * * pos * * specify the position in the syntax tree. Therefore, each syntax component cannot be new directly and needs to be generated using TreeMake
Common subclasses
- JCStatement: declare syntax tree nodes. Common subclasses are as follows
- JCBlock: statement block syntax tree node
- JCReturn: return statement syntax tree node
- JCClassDecl: class definition syntax tree node
- JCVariableDecl: field / variable definition syntax tree node
- JCMethodDecl: method definition syntax tree node
- JCModifiers: access flag syntax tree node
- JCExpression: expression syntax tree node. Common subclasses are as follows
- JCAssign: assignment statement syntax tree node
- JCIdent: identifier syntax tree node, which can be variable, type, keyword, etc
TreeMaker
It is used to create syntax tree nodes. pos will be set for the created nodes. Context sensitive new cannot be used directly
TreeMaker.Modifiers
Access identifier flags: access flag annotations: annotation list
TreeMaker.ClassDef
Used to define class mods: access flag name: class name typarams: generic parameter list extending: parent class implementing: interface list defs: detailed statement of class definition, including field, method definition, etc
TreeMaker.MethodDef
mods: access flag name: method name restype: return type typeparams: generic parameter list params: parameter list throw: exception declaration list body: method body defaultValue: default method (it may be the default in the interface) m: Method symbol mtype: method type. It contains multiple types, including generic parameter type, method parameter type, exception parameter type and return parameter type
TreeMaker.VarDef
Used to create a field or variable mods: access flag vartype: type init: initialization statement v: variable symbol
TreeMaker.Ident
Used to create an identifier
TreeMaker.Return
Used to create a return statement
TreeMaker.Select
Get object property selected: The expression to the left of the operator selector: The name to the right of the operator
TreeMaker.NewClass
encl for creating new syntax tree: I don't quite understand the meaning of this parameter typeargs: parameter type list clazz: type of object to be created args: parameter list def: class definition
TreeMaker.Apply
Method call typeargs: parameter type list fn: call statement args: parameter list
TreeMaker.Assign
Create assignment statement lhs: expression on the left of assignment statement rhs: expression on the right of assignment statement
TreeMaker.Exec
JCExpressionStatement for executing executable syntax tree
TreeMaker.Block
Used to create the syntax tree node of combined statements flags: access flag stats: statement list
com.sun.tools.javac.util.List
A special List returns a new List each time an object is added
com.sun.tools.javac.util.ListBuffer
It is similar to Java Native list and provides a method to convert to list
example
Create method
treeMaker.MethodDef( treeMaker.Modifiers(Flags.PUBLIC), //Access flag names.fromString("<init>"), //name treeMaker.TypeIdent(TypeTag.VOID), //Return type List.nil(), //Generic parameter list List.nil(), //parameter list List.nil(), //Exception list jcBlock, //Method body null //Default method (may be the default in the interface) );
Method parameters
treeMaker.VarDef( treeMaker.Modifiers(Flags.PARAMETER), //Access flag. Extremely pit Dad!!! prototypeJCVariable.name, //name prototypeJCVariable.vartype, //type null //Initialization statement );
Assignment statement
treeMaker.Exec( treeMaker.Assign( treeMaker.Select( treeMaker.Ident(names.fromString(THIS)), jcVariable.name ), treeMaker.Ident(jcVariable.name) ) )
new object
treeMaker.NewClass( null, //The meaning is unclear List.nil(), //Generic parameter list treeMaker.Ident(builderClassName), //Class name created List.nil(), //parameter list null //Class definition is estimated to be used to create anonymous inner classes )
Method call
treeMaker.Exec( treeMaker.Apply( List.nil(), treeMaker.Select( treeMaker.Ident(getNameFromString(IDENTIFIER_DATA)), jcMethodDecl.getName() ), List.of(treeMaker.Ident(jcVariableDecl.getName())) //Incoming parameter collection ) )
extend
Java compiler compilation process
Parser: map the symbol stream to an AST
Enter: find all definitions of the current range and convert to symbols
Process Annotations: optional. It processes compile time annotations. Reflection operations are not allowed
Attribute: adds an attribute to the generated AST
Flow: check the data flow
Desugar: reducing sugar
Generate: generate class files