浅析Java插件化注解API原理与实战

一、Java编译期注解VS运行期注解

编译期注解运行时注解
优点模版化代码,代码更简洁,提高开发效率,性能较高可读性强,灵活,易调试
缺点代码侵入性强,调试困难,可维护性差性能较差

二、Javac生成.class文件流程

  1. 准备过程:初始化插入式注解处理器。
  2. 解析与填充符号表过程,包括:
    ·词法、语法分析。将源代码的字符流转变为标记集合,构造出抽象语法树。
    ·填充符号表。产生符号地址和符号信息。
  3. 插入式注解处理器的注解处理。
  4. 分析与字节码生成过程,包括:
    ·标注检查。对语法的静态信息进行检查。
    ·数据流及控制流分析。对程序动态运行过程进行检查。
    ·解语法糖。将简化代码编写的语法糖还原为原有的形式。
    ·字节码生成。将前面各个步骤所生成的信息转化成字节码。

com.sun.tools.javac.main.JavaCompiler中的核心编译代码

参考《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版) - 周志明》


三、插件化注解API(Pluggable Annotation Processing API)

1、概念

按照 JSR269标准提供的一种API来操作annotation,它旨在通过编译期注解模版化地生成代码。
可以把插入式注解处理器看作是一组编译器的插件,如果这些插件在处理注解期间对语法树进行过修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止。

2、使用步骤

  1. 自定义一个元注解为@Retention(RetentionPolicy.SOURCE)的注解
  2. 自定义一个继承至javax.annotation.processing.AbstractProcessor的Processor来处理注解
  3. 用自定义的Processor来编译Java文件,并指定其所支持的注解
  4. 将自定义注解标注在自己所需要的地方!

3、关键类

JCTree.java 语法树节点的基类

TreeMaker.java 用于构建语法树节点

TreeTranslator.java 继承自JCTree.Vistor类,可以访问所有语法树节点,并对其进行代码侵入

4、案例

Talk is cheap. Show me the code

  1. 这里以一个多模块maven项目为例,项目结构如下,启动类为DemoApplication.java
    这里的案例为做这件事:将HTTP请求和返回的空字符串统一置为null
  1. 仅在编译期生效的元注解
package com.demo.annotation;

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

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface RewriteEmptyStringAsNull {}
  1. 自定义Processor,
/*
 *MyProcessor.java
 */
package com.demo.processor;

import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;

import com.google.auto.service.AutoService;
import com.sun.source.util.Trees;
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.util.Context;
import com.sun.tools.javac.util.Names;

// Google提供的注解,用于注册Java SPI
@AutoService(Processor.class)
// Processor对哪些注解生效
@SupportedAnnotationTypes({"com.demo.annotation.*"})
public class MyProcessor extends AbstractProcessor {

    /**
     * 语法树集合
     */
    private Trees trees;
    /*
     * 修改语法树的工具类
     */
    private TreeMaker treeMaker;

    /**
     * 给语法树元素命名的工具类
     */
    private Names names;

    /**
     * 用来打印错误信息
     */
    private Messager messager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.messager = processingEnv.getMessager();
        this.trees = Trees.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) {
        if (!roundEnv.processingOver()) {
            // 遍历每个被注解标记的元素
            for (Element element : roundEnv.getRootElements()) {
                if (element.getKind().isClass()) {
                    // 获取语法树
                    JCTree tree = (JCTree) trees.getTree(element);
                    // 使用TreeTranslator遍历
                    tree.accept(new MyTreeTranslator(treeMaker, names));
                }
            }
        }
        // 是否继续让其他processor处理
        return false;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}

/*
* MyTreeTranslator.java
*/
package com.demo.processor;

import static com.sun.tools.javac.tree.JCTree.JCAnnotation;
import static com.sun.tools.javac.tree.JCTree.JCBlock;
import static com.sun.tools.javac.tree.JCTree.JCModifiers;
import static com.sun.tools.javac.tree.JCTree.JCVariableDecl;

import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.TypeTag;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Names;

import com.demo.annotation.RewriteEmptyStringAsNull;

public class MyTreeTranslator extends TreeTranslator {

    /**
     * 树节点创建工具类
     */
    private TreeMaker treeMaker;
    /**
     * 命名工具类
     */
    private Names names;
    /**
     * 需要插入的Setter方法
     */
    private List<JCTree> setters = List.nil();

    public MyTreeTranslator(TreeMaker treeMaker, Names names) {
        this.treeMaker = treeMaker;
        this.names = names;
    }

    /**
     * 遍历到类时执行
     */
    @Override
    public void visitClassDef(JCClassDecl jcClassDecl) {
        super.visitClassDef(jcClassDecl);
        // 插入setter方法
        if (!setters.isEmpty()) {
            jcClassDecl.defs = jcClassDecl.defs.appendList(this.setters);
        }
        this.result = jcClassDecl;
    }

    /**
     * 遍历成员时执行
     */
    @Override
    public void visitVarDef(JCVariableDecl jcVariableDecl) {
        super.visitVarDef(jcVariableDecl);
        JCModifiers modifiers = jcVariableDecl.getModifiers();
        List<JCAnnotation> annotations = modifiers.getAnnotations();
        if (annotations == null || annotations.size() <= 0) {
            return;
        }
        // 以下只是简单实现, 不考虑是否已存在setter方法
        for (JCAnnotation annotation : annotations) {
            if (RewriteEmptyStringAsNull.class.getName().equals(annotation.type.toString())) {
                // 生成getter方法
                JCMethodDecl setterMethod = createSetterMethod(jcVariableDecl);
                this.setters = this.setters.append(setterMethod);
            }

        }
    }

    /**
     * 创建Setter方法
     */
    private JCMethodDecl createSetterMethod(JCVariableDecl field) {
        // 定义方法传入的参数
        JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), field.name, field.vartype, null);

        /*
                 if("".equals(name)){
                        this.name = null;
                  }else{
                        this.name = name;
                  }
         */

        // "".equals(name)               ---方法调用节点
        JCTree.JCMethodInvocation judgeStat = treeMaker.Apply(
                // 参数类型列表
                List.nil(),
                // "".equals    ---调用语句
                treeMaker.Select(
                        // . 左边的内容, 被调用的实体
                        treeMaker.Literal(""),
                        // . 右边的内容, 调用方法名/变量名等
                        names.fromString("equals")
                ),
                // name  ---方法调用的传参
                List.of(treeMaker.Ident(field.name))
        );

        // this.name = null   --- 可执行语句节点
        JCTree.JCExpressionStatement ifStat = treeMaker.Exec(
                // 赋值语句节点
                treeMaker.Assign(
                        // = 左边的语句
                        treeMaker.Select(treeMaker.Ident(names.fromString("this")), field.name),
                        // = 右边的语句
                        treeMaker.Literal(TypeTag.BOT,null)
                )
        );

        // this.name = name   --- 可执行语句节点
        JCTree.JCExpressionStatement elseStat = treeMaker.Exec(
                // 赋值语句节点
                treeMaker.Assign(
                        // = 左边的语句
                        treeMaker.Select(treeMaker.Ident(names.fromString("this")), field.name),
                        // = 右边的语句
                        treeMaker.Ident(field.name)
                )
        );

        

        JCBlock methodBody = treeMaker.Block(0L, List.of(treeMaker.If(
                // 判断语句
                judgeStat,
                // 条件成立的语句
                ifStat,
                // 条件不成立的语句
                elseStat  
        )));

        return treeMaker.MethodDef(
                // public方法
                treeMaker.Modifiers(Flags.PUBLIC),
                // 方法名称
                names.fromString("set" + this.toTitleCase(field.getName().toString())),
                // 方法返回的类型
                treeMaker.Type(new Type.JCVoidType()),
                // 泛型参数
                List.nil(),
                // 方法参数
                List.of(param),
                // throw表达式
                List.nil(),
                // 方法体
                methodBody,
                // 默认值
                null
        );
    }

    /**
     * 首字母大写
     */
    public String toTitleCase(String str) {
        char first = str.charAt(0);
        if (first >= 'a' && first <= 'z') {
            first -= 32;
        }
        return first + str.substring(1);
    }
}

  1. 用自定义Processor来编译java文件
    推荐使用Java SPI来注册指定Processor,有两种方式
  • 在自定义的Processor上添加注解`@AutoService({Processor.class})
  • 新增文件src/main/resources/META-INF/services/javax.annotation.processing.Processor 内容为自定义Processor的全限定名,如com.demo.processor.MyProcessor
  1. 效果演示
/*
* HelloWorldController.java
*/
package com.demo.controller;

import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.demo.vo.Hello1Req;
import com.demo.vo.Hello2Req;

@RestController
public class HelloWorldController {

    @RequestMapping("/hello1")
    public Object hello1(@RequestBody Hello1Req hello1Req) {
        return hello1Req;
    }

    @RequestMapping("/hello2")
    public Object hello2(@RequestBody Hello2Req hello2Req) {
        return hello2Req;
    }
}

/*
* Hello1Req.java
*/
package com.demo.vo;

import lombok.Data;

@Data
public class Hello1Req {
	private String str1;
	private String str2;
}
/*
* Hello2Req.java
*/
package com.demo.vo;


import com.demo.annotation.RewriteEmptyStringAsNull;

import lombok.Getter;

public class Hello2Req {

	@Getter
	@RewriteEmptyStringAsNull
	private String str1;
	@Getter
	@RewriteEmptyStringAsNull
	private String str2;
}

4.1 Rebuild Project 查看class文件
成功生成自定义方法
请添加图片描述
4.2 接口调用,查看是否成功实现:将HTTP请求和返回的空字符串统一置为null
启动springboot项目,分别调用这两个方法
Request1

curl -X POST localhost:8080/hello1 \
--header 'Content-Type: application/json' \
--data '{
	"str1":"哈哈",
	"str2":""
}'

Request2

curl -X POST localhost:8080/hello2 \
--header 'Content-Type: application/json' \
--data '{
	"str1":"哈哈",
	"str2":""
}'

结果
在这里插入图片描述

5、Tips

  1. 如何debug自定义的processor?
    打好断点后,通过调试javac源码,即debug如下的main方法
public static void main(String[] args)throws Exception {
        com.sun.tools.javac.Main.main(new String[] {"-proc:only",
                "-processor",
                // processor全限定名
                "com.demo.processor.MyProcessor",
                // 需要编译的文件路径
                "/Users/Project/test/demo-main/src/main/java/com/demo/vo/Hello2Req.java"});

//        SpringApplication.run(DemoApplication.class, args);
    }
  1. 如何规避IDEA的编译期语法检查?

Preferences | Build, Execution, Deployment | Compiler
的Shared build process VM options 添加
-Djps.track.ap.dependencies=false

  1. 自定义Processor必须在注解标注的类之前编译,换句话说,注解标注的类必须经过Processor进行编译。因此建议把Processor写在另一个模块,通过maven引用此依赖来强制指定编译顺序。
目录 ................................................................................................................................................................................... 5  1.  通用注解API、客户程序和实现模型 ................................................................................................................. 7  1.1. 简介 ........................................................................................................................................................................... 7  1.2. 实现的元数据 ........................................................................................................................................................... 7  1.2.1. 服务元数据 ............................................................................................................................................................ 8  1.2.2.@Reference ........................................................................................................................................................... 8  1.2.3. @Property ............................................................................................................................................................. 9  1.2.4. 实现作用域:@Scope、@Init、@Destroy ....................................................................................................... 9  1.3 接口元数据 .............................................................................................................................................................. 10  1.3.1. @Remotable ....................................................................................................................................................... 10  1.3.2. @Conversational ................................................................................................................................................ 11  1.4. 客户 API .................................................................................................................................................................. 11  1.4.1. SCA构件访问服务 .............................................................................................................................................. 11  1.4.2. 非 SCA构件的实现访问服务 ............................................................................................................................ 11  1.5. 错误处理 ................................................................................................................................................................. 12  1.6. 异步与会话编程 ..................................................................................................................................................... 12  1.6.1. @OneWay ........................................................................................................................................................... 12 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

热心小伙chj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值