当前位置:网站首页>5. Practice: jctree implements the annotation processor at compile time
5. Practice: jctree implements the annotation processor at compile time
2022-07-02 15:26:00 【At the beginning of dawn】
1. hum about
- Start learning APT when , My ultimate goal : Can be inside an existing class , add to Builder Class to implement Builder Design patterns
- in other words , I hope to learn APT Modify the existing Java Source code
- Access to information , Discovery can be modified by Java Grammar tree (JCTree) Realization Java Source code modification
- Last blog :4. JCTree Related knowledge learning , It introduces JCTree Knowledge about
- this , adopt @Value Note to see if it passes JCTree modify Java Source code
- @Value The role of annotations : For the wrong final Of String Fields are assigned default initial values
- Because this rookie thinks ,final Fields should be explicitly assigned : Initialize on declaration or through constructor
2. Preliminary knowledge : How to get the value of an element in an annotation
As previously described ,@Value Comments can be non final Of String Fields are assigned default initial values
@Value Notes are defined as follows , Contains a value Elements , To set the default initial value of the field
@Target(ElementType.FIELD) @Retention(RetentionPolicy.SOURCE) public @interface Value { String value(); }How to use it is as follows :
@Value(" Shenzhen ") private static String address;The problem is coming. , How to get @Value The value in
Shenzhen, So as to realize address Default initial value of field ?Oneself is a half irrigation , Every step needs to be checked . Thanks for the blog : utilize APT stay Java Get annotation information when compiling the file , Inspired myself
original ,Element Provides a
getAnnotation()Method , By specifying annotated Class type , You can get the corresponding annotation instanceGot the annotation example , Accessing the value of the element in the annotation is very simple . For details, please refer to the previous blog
1.3.3.4section :1. Java annotationValue valueAn = element.getAnnotation(Value.class); // obtain @Value Annotate examples value.value(); // obtain value
3. Code combat
3.1 Realization ValueProcessor
stay annotation-processor modular , be based on Google Of auto-service, establish ValueProcessor
@AutoService(Processor.class) @SupportedAnnotationTypes("sunrise.annotation.Value") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class ValueProcessor extends AbstractProcessor { private static int round; // Used to identify annotation processing round private Messager messager; private Context context; // establish TreeMaker and Names Required context private JavacTrees trees; // Java Tool class of syntax tree private TreeMaker treeMaker; // Create factory classes for syntax tree nodes private Names names; // Access to compiler name table , Provides some standard names and methods to create new names @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.messager = processingEnv.getMessager(); this.context = ((JavacProcessingEnvironment) processingEnv).getContext(); this.trees = JavacTrees.instance(processingEnv); this.treeMaker = TreeMaker.instance(context); this.names = new Names(context); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { messager.printMessage(Diagnostic.Kind.NOTE, ValueProcessor.class.getSimpleName() + " round " + (++round)); for (TypeElement annotation : annotations) { // adopt lambda expression , Process each element marked by annotations roundEnv.getElementsAnnotatedWith(annotation).forEach(element -> { // obtain value Value Value valueAnnotation = element.getAnnotation(Value.class); String value = valueAnnotation.value(); // Modify the syntax tree node : Directly modifying init, Assign default initial values to fields JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) trees.getTree(element); if (!jcVariableDecl.getModifiers().getFlags().contains(Modifier.FINAL) && jcVariableDecl.vartype.toString().equals("String")) { messager.printMessage(Diagnostic.Kind.NOTE, " Original field information : " + jcVariableDecl.toString()); jcVariableDecl.init = treeMaker.Literal(value); messager.printMessage(Diagnostic.Kind.NOTE, " Modified field information : " + jcVariableDecl.toString()); } else { messager.printMessage(Diagnostic.Kind.ERROR, " Current field : " + jcVariableDecl.toString() + "\[email protected] Annotations can only work on non final Of String Field !"); } }); } return roundEnv.processingOver(); } }adopt
mvn clean installCommand complete annotation-processor Module installation
3.2 Use @Value annotation
stay annotation-use Module creation ValueProcessorTest class , Use @Value annotation
public class ValueProcessorTest { @Value("mac os") public static String SYSTEM; @Value(" Zhang San ") private String name; @Value("21") // Wrong use , To verify whether the restriction of field type is effective private int age; public static void main(String[] args) { ValueProcessorTest test = new ValueProcessorTest(); // Print directly with initial value System.out.println("system: " + ValueProcessorTest.getSYSTEM() + ", name: " + test.getName() + ", age: " + test.getAge()); } // getter、setter Method ellipsis }adopt
mvn clean compileCommand complete annotation-use Module compilation , Compiler error . explain , The annotation processor implements the restriction of field types .
take age Field @Value Comment out , Compilation completed successfully .
adopt IDEA see target/classed In the directory ValueProcessorTest.class, The contents are as follows
package sunrise.annotation.use; public class ValueProcessorTest { public static String SYSTEM = "mac os"; private String name = " Zhang San "; private int age; public static void main(String[] args) { ValueProcessorTest test = new ValueProcessorTest(); System.out.println("system: " + getSYSTEM() + ", name: " + test.getName() + ", age: " + test.getAge()); } // Omit the default constructor 、getter、setter Method }perform main Method , give the result as follows

Whether from decompiled class file , Or from the execution results , It's all about : adopt JCTree, It's done @Value annotation
3.3 adopt visitor The mode assigns default initial values to fields
above process() In the method , Directly by modifying JCVariableDecl Of init Field , It realizes assigning default initial values to fields , Did not realize vistor Patterns in JCTree The role of
Below process() Method , Will pass through visitor The mode assigns default initial values to fields
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { messager.printMessage(Diagnostic.Kind.NOTE, ValueProcessor.class.getSimpleName() + " round " + (++round)); for (TypeElement annotation : annotations) { // adopt lambda expression , Process each element marked by annotations roundEnv.getElementsAnnotatedWith(annotation).forEach(element -> { // obtain value Value Value valueAnnotation = element.getAnnotation(Value.class); String value = valueAnnotation.value(); // adopt visitor The mode assigns default initial values to fields jcVariableDecl.accept(new TreeTranslator(){ @Override public void visitVarDef(JCTree.JCVariableDecl tree) { super.visitVarDef(tree); if (!jcVariableDecl.getModifiers().getFlags().contains(Modifier.FINAL) && jcVariableDecl.vartype.toString().equals("String")) { messager.printMessage(Diagnostic.Kind.NOTE, " Original field information : " + jcVariableDecl.toString()); jcVariableDecl.init = treeMaker.Literal(value); messager.printMessage(Diagnostic.Kind.NOTE, " Modified field information : " + jcVariableDecl.toString()); // Update syntax tree node this.result = jcVariableDecl; } else { messager.printMessage(Diagnostic.Kind.ERROR, "@Value Annotations can only work on non final Of String Field !"); } } }); }); } return roundEnv.processingOver(); }
4. Other examples
4.1 Realization @Getter annotation
lombok Medium @Getter annotation , Can be used to automatically generate fields getter Method
imitation ombok Of @Getter annotation , Do it @Getter annotation
Definition notes
@Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface Getter { }Custom annotation handler , I'm just showing you process Method
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { messager.printMessage(Diagnostic.Kind.NOTE, GetterProcessor.class.getSimpleName() + " round " + (++round)); for (TypeElement annotation : annotations) { roundEnv.getElementsAnnotatedWith(annotation).forEach(element -> { // Get the corresponding syntax tree JCTree jcTree = trees.getTree(element); // establish JCClassDecl Of visitor, Get the field and create getter Method jcTree.accept(new TreeTranslator() { @Override public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { super.visitClassDef(jcClassDecl); // Get variable List<JCTree.JCVariableDecl> jcVariableDecls = List.nil(); for (JCTree item : jcClassDecl.defs) { if (item.getKind() == Tree.Kind.VARIABLE) { JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) item; jcVariableDecls = jcVariableDecls.append(jcVariableDecl); } } // Create the corresponding getter Method jcVariableDecls.forEach(jcVariableDecl -> { // establish getter Method JCTree.JCMethodDecl jcMethodDecl = generateGetterMethod(jcVariableDecl); // Update the definition of the class jcClassDecl.defs = jcClassDecl.defs.append(jcMethodDecl); }); // to update jcClassDecl this.result = jcClassDecl; } }); }); } return false; } /** * adopt treeMaker establish getter Method **/ private JCTree.JCMethodDecl generateGetterMethod(JCTree.JCVariableDecl jcVariableDecl) { // In the construction method body statement, Then create the method body ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>(); JCTree.JCReturn jcReturn = treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName())); statements.add(jcReturn); JCTree.JCBlock body = treeMaker.Block(0, statements.toList()); // establish JCMethodDecl node Name methodName = getterMethodName(jcVariableDecl.getName()); // Generate... Based on the field name getter Method name JCTree.JCExpression returnType = jcVariableDecl.vartype; // Specifies the modifier for the method 、 Method name 、 Returns the parameter 、 The generic parameter 、 Enter the reference 、 Exception declaration 、 Method body 、defaultValue(null that will do ) JCTree.JCMethodDecl jcMethodDecl = treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), methodName, returnType, List.nil(), List.nil(), List.nil(), body, null); return jcMethodDecl; } /** * Create method name identifier **/ public Name getterMethodName(Name variableName) { String name = variableName.toString(); return names.fromString("get" + name.substring(0, 1).toUpperCase() + name.substring(1)); }The best reference links :Lombok Principle analysis and function realization
4.2 Realization setter Method
except @Getter annotation ,lombok also @Setter annotation , Can be non final Field generation setter Method
Customize @Setter annotation :
@Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface Setter { }Realization SetterProcessor, Show only process() Method
// Definition elementUtils Field , And in init() method private JavacElements elementUtils; this.elementUtils = (JavacElements) processingEnv.getElementUtils(); @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { messager.printMessage(Diagnostic.Kind.NOTE, SetterProcessor.class.getSimpleName() + " round " + (++round)); for (TypeElement annotation : annotations) { roundEnv.getElementsAnnotatedWith(annotation).forEach(element -> { // Get the corresponding syntax tree JCTree jcTree = trees.getTree(element); // adopt visitor Pattern , add to setter Method ; If final Field , Then it does not generate setter Method jcTree.accept(new TreeTranslator() { @Override public void visitClassDef(JCTree.JCClassDecl jcClassDecl) { super.visitClassDef(jcClassDecl); // 1. Get non final Field List<JCTree.JCVariableDecl> jcVariableDecls = List.nil(); for (JCTree item : jcClassDecl.defs) { if (item.getKind() == Tree.Kind.VARIABLE) { JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) item; if (!jcVariableDecl.getModifiers().getFlags().contains((Modifier.FINAL))) { jcVariableDecls = jcVariableDecls.append(jcVariableDecl); } } } // 2. Create the corresponding setter Method jcVariableDecls.forEach(jcVariableDecl -> { // Create the corresponding setter Method JCTree.JCMethodDecl jcMethodDecl = generateSetterMethod(jcVariableDecl); // Update class jcClassDecl.defs = jcClassDecl.defs.append(jcMethodDecl); }); // 3. to update jcClassDecl this.result = jcClassDecl; } }); }); } return roundEnv.processingOver(); } /** * adopt treeMaker establish setter Method **/ private JCTree.JCMethodDecl generateSetterMethod(JCTree.JCVariableDecl jcVariableDecl) { // 1. Create assignment statements , Build the method body ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>(); JCTree.JCExpressionStatement statement = treeMaker.Exec(treeMaker.Assign(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName()), treeMaker.Ident(jcVariableDecl.getName()))); statements.append(statement); JCTree.JCBlock body = treeMaker.Block(0, statements.toList()); // 2. Before generating method parameters , Indicate the position of the current syntax node in the syntax tree , Avoid exceptions java.lang.AssertionError: Value of x -1 treeMaker.pos = jcVariableDecl.pos; // 3. Create method Name methodName = setterMethodName(jcVariableDecl.getName()); JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), jcVariableDecl.getName(), jcVariableDecl.vartype, null); // Define input parameters in this way , There may be NullPointer error // JCTree.JCVariableDecl param = treeMaker.Param( jcVariableDecl.getName(), jcVariableDecl.vartype.type, jcVariableDecl.sym); // Two definitions void The method of return value is equivalent JCTree.JCExpression returnType = treeMaker.Type(new Type.JCVoidType()); // JCTree.JCExpression returnType = treeMaker.TypeIdent(TypeTag.VOID); // Specifies the modifier for the method 、 Method name 、 Returns the parameter 、 The generic parameter 、 Enter the reference 、 Exception declaration 、 Method body 、defaultValue(null that will do ) JCTree.JCMethodDecl jcMethodDecl = treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), methodName, returnType, List.nil(), List.of(param), List.nil(), body, null); return jcMethodDecl; } /** * Create method name identifier **/ private Name setterMethodName(Name variableName) { String name = variableName.toString(); return names.fromString("set" + name.substring(0, 1).toUpperCase() + name.substring(1)); }Reference link :Lombok Principle and implementation
4.3 Customize @Hello annotation
@Hello Comments act on Methods , You can let the method print the class name and method name before executing the code , similar :Hello, this is xxx
process() The method is defined as follows :
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { messager.printMessage(Diagnostic.Kind.NOTE, HelloProcessor.class.getSimpleName() + " round " + (++round)); for (TypeElement annotation : annotations) { roundEnv.getElementsAnnotatedWith(annotation).forEach(element -> { // Get method node JCTree.JCMethodDecl jcMethodDecl = (JCTree.JCMethodDecl) trees.getTree(element); // Get method name , Build what needs to be printed String methodName = jcMethodDecl.getName().toString(); String className = element.getEnclosingElement().getSimpleName().toString(); String content = String.format("Hello, this is %s() in %s", methodName, className); // pos I can't understand the function of treeMaker.pos = jcMethodDecl.pos; // structure System.out.println sentence JCTree.JCExpressionStatement printStatement = treeMaker.Exec( // Create executable statements treeMaker.Apply( // establish JCMethodInvocation List.nil(), treeMaker.Select( treeMaker.Select(treeMaker.Ident(elementUtils.getName("System")), elementUtils.getName("out")), // for the first time select, Locate the System.out elementUtils.getName("println")), // The second time select, Define the System.out.println List.of(treeMaker.Literal(content)))); // Update method body jcMethodDecl.body = treeMaker.Block(0, jcMethodDecl.body.getStatements().prepend(printStatement)); }); } return false; }Update effect :
// The original main Method @Hello public static void main(String[] args) { System.out.println("compile finished"); } // After the update ,class File decompiled main Method public static void main(String[] args) { System.out.println("Hello, this is main() in HelloProcessorTest"); System.out.println("compile finished"); }Thanks for the blog :java Use AbstractProcessor、 Compile time annotations and JCTree Compile time implementation of weaving code ( similar lombok) And implement Debug Their own Processor And compiled code
5. other
- How to get the representation of the whole .java Of documents JCCompilationUnit: About ast Abstract syntax tree JcImport and JCCompilationUnit Usage of
- Explain with real cases of the company , Many examples are also given :java Annotation processor —— Modify the syntax tree at compile time
- Lombok Introduction and use of
- lombok Principle ( By modifying the AST Realization ):Lombok brief introduction 、 Use 、 working principle 、 Advantages and disadvantages 、Lombok principle
- 【JSR269 actual combat 】 Compile time operation AST, Modify the bytecode file , To achieve and lombok Similar functions
- from JSR269 To Lombok, Learn annotation processor Annotation Processor Tool( attach Demo)
边栏推荐
- yolo格式数据集处理(xml转txt)
- Practice of compiling principle course -- implementing an interpreter or compiler of elementary function operation language
- How to write sensor data into computer database
- Why can't programmers who can only program become excellent developers?
- Let your HMI have more advantages. Fet-g2ld-c core board is a good choice
- 06_栈和队列转换
- Apprendre le Code de la méthode de conversion du calendrier lunaire grégorien en utilisant PHP
- How to find a sense of career direction
- How to avoid 7 common problems in mobile and network availability testing
- Huffman tree: (1) input each character and its weight (2) construct Huffman tree (3) carry out Huffman coding (4) find hc[i], and get the Huffman coding of each character
猜你喜欢

Btrace- (bytecode) dynamic tracking tool

Base64 coding can be understood this way

Yolov5 code reproduction and server operation

Why can't programmers who can only program become excellent developers?

08_ 串

让您的HMI更具优势,FET-G2LD-C核心板是个好选择

FPGA - 7系列 FPGA内部结构之Clocking -03- 时钟管理模块(CMT)

03_線性錶_鏈錶

06_栈和队列转换

03.golang初步使用
随机推荐
The traversal methods of binary tree mainly include: first order traversal, middle order traversal, second order traversal, and hierarchical traversal. First order, middle order, and second order actu
Key points of compilation principle examination in 2021-2022 academic year [overseas Chinese University]
搭载TI AM62x处理器,飞凌FET6254-C核心板首发上市!
06_ Stack and queue conversion
Btrace- (bytecode) dynamic tracking tool
CodeCraft-22 and Codeforces Round #795 (Div. 2)D,E
Yolo format data set processing (XML to txt)
PHP method to get the index value of the array item with the largest key value in the array
14_ Redis_ Optimistic lock
Tidb data migration scenario overview
Case introduction and problem analysis of microservice
【网络安全】网络资产收集
党史纪实主题公益数字文创产品正式上线
哈夫曼树:(1)输入各字符及其权值(2)构造哈夫曼树(3)进行哈夫曼编码(4)查找HC[i],得到各字符的哈夫曼编码
[noi Simulation Competition] scraping (dynamic planning)
Practice of compiling principle course -- implementing an interpreter or compiler of elementary function operation language
Mavn builds nexus private server
AtCoder Beginner Contest 254
11_ Redis_ Hyperloglog_ command
牛客练习赛101