如何用 APT(Annotation Processing Tool)自动生成代码

简介: 如何用 APT(Annotation Processing Tool)自动生成代码

我们很多人都写过代码自动生成的工具,比如用python结合moko模板引擎,或者java 结合freemarker模板引擎,protoc 等,实现解析策划数据类,proto协议类,或者数据库层的实体类。大大节省了我们开发的时间,让我们可以懒得光明正大。

那么,你有没有办法当你写好几个协议后,只要一保存编译,相关的协议接收和发送类的接口就自动实现了呢?答案是有的。

组件化开发

注解处理器是(Annotation Processor)是javac的一个工具,用来在编译时扫描和编译和处理注解(Annotation)。你可以自己定义注解和注解处理器去搞一些事情。一个注解处理器它以Java代码或者(编译过的字节码)作为输入,生成文件(通常是java文件)。这些生成的java文件不能修改,并且会同其手动编写的java代码一样会被javac编译。看到这里加上之前理解,应该明白大概的过程了,就是把标记了注解的类,变量等作为输入内容,经过注解处理器处理,生成想要生成的java代码。Annotation Process的实质用处就是在编译时通过注解获取相关数据

什么是APT?

APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,根据注解自动生成代码。 Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成的源文件,将它们一起生成class文件。

理解了以上的理论,我们来实践一下:

需求:

通过实现协议类,让客户端请求工具和服务端接收处Controller自动添加代码。

1.首先添加依赖包

// annotation
    implementation "com.google.auto.service:auto-service:1.0.1"

2.添加annotation类:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * http proto注解,会被{@link ProtoServiceProcessor}处理
 *
 * @author Allen Jiang
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface HttpProto {
}

添加Processor处理类:

/**
 * 用于自动生成admin和业务进程通讯的底层代码
 *
 * @author Allen Jiang
 */
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({"com.gamioo.http.annotation.HttpProto"})
public class ProtoServiceProcessor extends AbstractProcessor {
    public static final String C_2_S_MSG = "_C2S_Msg";
    public static final String S_2_C_MSG = "_S2C_Msg";
    public static final String HTTP_PROCESSOR = "IHttpProcessor";
    public static final String GAME_CLIENT = "GameClient";
    private Messager messager;
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        messager = processingEnvironment.getMessager();
    }
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (roundEnv.processingOver()) {
            return false;
        }
        for (TypeElement annotation : annotations) {
            messager.printMessage(Diagnostic.Kind.NOTE, String.format("process annotation start: %s", annotation));
            try {
                List<HttpProtoDTO> list = this.getMethodList(annotation, roundEnv);
                this.buildClass(list, HTTP_PROCESSOR);
                this.buildClass(list, GAME_CLIENT);
            } catch (Exception e) {
                messager.printMessage(Diagnostic.Kind.ERROR, ExceptionUtils.getStackTrace(e));
            }
            messager.printMessage(Diagnostic.Kind.NOTE, String.format("process annotation end: %s", annotation));
            return false;
        }
        return false;
    }
    /**
     * 构建模板
     */
    private void buildClass(List<HttpProtoDTO> list, String className) throws IOException, TemplateException {
        messager.printMessage(Diagnostic.Kind.NOTE, String.format("build class start: %s", className));
        StatDTO<HttpProtoDTO> dto = new StatDTO<>();
        dto.setArray(list);
        String content = ViewTemplateUtils.getContentFromJar(className + ".ftl", dto);
        //  messager.printMessage(Diagnostic.Kind.NOTE, String.format("content: %s", content));
        FileObject fileObject = processingEnv.getFiler().createSourceFile(className);
        try (PrintWriter writer = new PrintWriter(fileObject.openWriter())) {
            writer.write(content);
            writer.flush();
            messager.printMessage(Diagnostic.Kind.NOTE, String.format("build class end: %s", className));
        } catch (IOException e) {
            throw e;
        }
    }
    /**
     * 获取协议列表
     */
    private List<HttpProtoDTO> getMethodList(TypeElement annotation, RoundEnvironment roundEnv) throws IOException {
        List<HttpProtoDTO> ret = new ArrayList<>();
        FileObject fileObject = processingEnv.getFiler().getResource(StandardLocation.SOURCE_OUTPUT, "", HTTP_PROCESSOR + ".java");
        Map<String, Integer> store = new HashMap<>();
        try {
            CharSequence content = fileObject.getCharContent(false);
            String[] list = StringUtils.substringsBetween(content.toString(), "(", C_2_S_MSG);
            for (String e : list) {
                store.put(StringUtils.uncapitalize(e), 2);
            }
        } catch (Exception e) {
            messager.printMessage(Diagnostic.Kind.NOTE, e.getMessage() + ",ignore it");
        }
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(annotation);
        for (Element element : elements) {
            //   messager.printMessage(Diagnostic.Kind.NOTE, String.format("element: %s", element.getSimpleName().toString()));
            String method = StringUtils.uncapitalize(StringUtils.substringBefore(element.getSimpleName().toString(), "_"));
            Integer num = store.get(method);
            if (num == null) {
                num = 1;
            } else {
                num += 1;
            }
            store.put(method, num);
        }
        messager.printMessage(Diagnostic.Kind.NOTE, String.format("element: %s", JSONUtils.toJSONString(store)));
        store.forEach((method, value) -> {
            String prefix = StringUtils.capitalize(method);
            String c2s = prefix + C_2_S_MSG;
            String s2c = prefix + S_2_C_MSG;
            HttpProtoDTO protoDTO = new HttpProtoDTO(c2s, s2c, method);
            ret.add(protoDTO);
        });
        messager.printMessage(Diagnostic.Kind.NOTE, String.format("message number: %s", ret.size()));
        // fileObject.delete();
        return ret;
    }
}

辅助类:

public class HttpProtoDTO {
    private String c2s;
    private String s2c;
    private String method;
    public HttpProtoDTO() {
    }
    public HttpProtoDTO(String c2s, String s2c, String method) {
        this.c2s = c2s;
        this.s2c = s2c;
        this.method = method;
    }
    public String getC2s() {
        return c2s;
    }
    public void setC2s(String c2s) {
        this.c2s = c2s;
    }
    public String getS2c() {
        return s2c;
    }
    public void setS2c(String s2c) {
        this.s2c = s2c;
    }
    public String getMethod() {
        return method;
    }
    public void setMethod(String method) {
        this.method = method;
    }
    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
    }

以上类所在的模块我们要编译出一个jar.

依赖的项目dependencies中添加

annotationProcessor project(':gamioo-core')

接下去我们说如何使用。

首先,定义协议:

import com.gamioo.http.annotation.HttpProto;
/**
 * 执行脚本命令.
 */
@HttpProto
public class ExecScript_C2S_Msg {
    private String script;
    public String getScript() {
        return script;
    }
    public void setScript(String script) {
        this.script = script;
    }
}
/**
 * @author Allen Jiang
 */
@HttpProto
public class ExecScript_S2C_Msg {
    private String result;
    private long interval;
    public long getInterval() {
        return interval;
    }
    public void setInterval(long interval) {
        this.interval = interval;
    }
    public String getResult() {
        return result;
    }
    public void setResult(String result) {
        this.result = result;
    }
}

然后在com.gamioo.common.http.proto包执行一下编译,就自动生成了com.gamioo.admin.network.GameClient和com.gamioo.common.http.IHttpProcessor的代码

client 端:

com.gamioo.admin.network.GameClient

admin:

public ExecScript_S2C_Msg execScript(ExecScript_C2S_Msg msg) {
    ExecScript_S2C_Msg ret = null;
    String content = this.sendWithReturn(msg);
    if (content != null) {
        ret = JSONUtils.parseObject(content, ExecScript_S2C_Msg.class);
    }
    return ret;
}

server端:

com.yorha.common.http.IHttpProcessor
ExecScript_S2C_Msg execScript(ExecScript_C2S_Msg msg);

以上两个文件都会生成在generated目录下,并且不支持文件修改,这样就完成的代码自动生成,后续就可以进行协议的具体逻辑实现和调用测试。

Q1: 自定义jsr-269注解处理器 Error:服务配置文件不正确,或构造处理程序对象javax.annotation.processing.Processor: Provider not found

出现的原因

自定义处理器还没有被编译就被调用,所以报 not found

在根据配置寻找自定义的注解处理器时,自定义处理器还未被编译

解决方式

maven项目可以配置编译插件,在编译项目之前先编译处理器,或者编译项目时跳过执行处理器

参考:https://stackoverflow.com/questions/38926255/maven-annotation-processing-processor-not-found

Q2: gradle项目可以将自定义处理器分离出去,单独作为一个项目,并且打包成jar包,将这个项目build后作为依赖使用

例如:

dependencies {
  compile project(':anno-project-core')
  annotationProcessor project(':anno-project-core')
}

Q3: 如何进到Processor调试?

需要用gradle的assemble功能,然后选择调试。

参考资料如下:

注解入坑笔记:关于注解使用必须了解的——Annotation、AbstraceProcessor、APT

IDEA+Gradle使用Annotation Processor

Annotation Tool(注解工具-IntelliJ IDEA 插件)

目录
相关文章
|
监控 druid Java
Spring Boot 3 集成 Druid 连接池详解
在现代的Java应用中,使用一个高效可靠的数据源是至关重要的。Druid连接池作为一款强大的数据库连接池,提供了丰富的监控和管理功能,成为很多Java项目的首选。本文将详细介绍如何在Spring Boot 3项目中配置数据源,集成Druid连接池,以实现更高效的数据库连接管理。
11812 2
Spring Boot 3 集成 Druid 连接池详解
|
Java Spring
smiley-http-proxy-servlet 反向代理
背景  反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
4038 0
|
8月前
|
存储 安全 Java
2025 最新史上最全 Java 面试题独家整理带详细答案及解析
本文从Java基础、面向对象、多线程与并发等方面详细解析常见面试题及答案,并结合实际应用帮助理解。内容涵盖基本数据类型、自动装箱拆箱、String类区别,面向对象三大特性(封装、继承、多态),线程创建与安全问题解决方法,以及集合框架如ArrayList与LinkedList的对比和HashMap工作原理。适合准备面试或深入学习Java的开发者参考。附代码获取链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
4529 50
|
前端开发 Java Maven
【异常解决】org.yaml.snakeyaml.scanner.ScannerException: while scanning for the next token found character
【异常解决】org.yaml.snakeyaml.scanner.ScannerException: while scanning for the next token found character
2504 0
|
监控 安全 Java
【开发者必备】Spring Boot中自定义注解与处理器的神奇魔力:一键解锁代码新高度!
【8月更文挑战第29天】本文介绍如何在Spring Boot中利用自定义注解与处理器增强应用功能。通过定义如`@CustomProcessor`注解并结合`BeanPostProcessor`实现特定逻辑处理,如业务逻辑封装、配置管理及元数据分析等,从而提升代码整洁度与可维护性。文章详细展示了从注解定义、处理器编写到实际应用的具体步骤,并提供了实战案例,帮助开发者更好地理解和运用这一强大特性,以实现代码的高效组织与优化。
734 0
|
Linux 数据安全/隐私保护 C语言
新手向导:轻松离线搭建最新版OpenVPN(含一键安装脚本)
OpenVPN 是常用的虚拟私有网络工具,通过 Docker 搭建非常简单。但常用的 kylemanna/openvpn 镜像已三年未更新,停留在 OpenVPN 2.4 版本。为了升级到最新版本(如 2024 年 2 月发布的 v2.6.9),可以通过官方开源社区获取最新安装包并手动编译安装。步骤包括安装依赖、下载并编译 OpenSSL 和 OpenVPN、生成证书和配置文件等。此外,GitHub 上有一键安装脚本 openvpn-install.sh,简化了安装过程,但其版本可能不是最新的。安装完成后,还需配置 iptables 以确保客户端能正常使用代理网络。
16022 1
|
消息中间件 JSON Java
Spring Boot、Spring Cloud与Spring Cloud Alibaba版本对应关系
Spring Boot、Spring Cloud与Spring Cloud Alibaba版本对应关系
32270 0
|
存储 安全 Java
Spring Boot 编写 API 的 10条最佳实践
本文总结了 10 个编写 Spring Boot API 的最佳实践,包括 RESTful API 设计原则、注解使用、依赖注入、异常处理、数据传输对象(DTO)建模、安全措施、版本控制、文档生成、测试策略以及监控和日志记录。每个实践都配有详细的编码示例和解释,帮助开发者像专业人士一样构建高质量的 API。
512 9
|
消息中间件 测试技术 领域建模
DDD - 一文读懂DDD领域驱动设计
DDD - 一文读懂DDD领域驱动设计
47611 6
|
设计模式 Java 编译器
【Java 基础】Java SPI 二 之 Java APT原理及APT实战 - 一步步教你写ButterKnife
Java APT 是 Java 技术设计的一个 APT 架构, APT(Annotation Processing Tool)即注解处理器,它是一种处理注解的工具,也是javac中的一个工具,用于在编译阶段未生成class之前对源码中的注解进行扫描和处理。 APT可以用来在编译时扫描和处理注解, 它可以用来获取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动的生成一些代码,省去了手动编写。 在Android中有如ButterKnife、Dagger、EventBus等第三方框架,都采用了APT。
1062 0