欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

如何用APT自动生成代码

时间:2023-08-10

我们很多人都写过代码自动生成的工具,比如用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;@Target({ElementType.TYPE})@Retention(RetentionPolicy.CLASS)public @interface HttpProto {}

添加Processor处理类:

@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 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 list, String className) throws IOException, TemplateException { messager.printMessage(Diagnostic.Kind.NOTE, String.format("build class start: %s", className)); StatDTO 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 getMethodList(TypeElement annotation, RoundEnvironment roundEnv) throws IOException { List ret = new ArrayList<>(); FileObject fileObject = processingEnv.getFiler().getResource(StandardLocation.SOURCE_OUTPUT, "", HTTP_PROCESSOR + ".java"); Map 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;@HttpProtopublic class Execscript_C2S_Msg { private String script; public String getscript() { return script; } public void setscript(String script) { this.script = script; }}@HttpProtopublic 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.IHttpProcessorExecscript_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 插件)

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。