Annotation Processing Tool自动生成代码

简介: 本文介绍了一种利用Java注解处理器(Annotation Processor)自动生成协议接收与发送类接口的方法,显著提升开发效率。注解处理器能在编译阶段扫描并处理特定注解,生成所需Java代码。文中详细展示了如何通过自定义`HttpProto`注解及对应的处理器`ProtoServiceProcessor`,实现在保存协议类后自动生成客户端请求工具和服务端控制器代码。此外,还提供了具体实现步骤、依赖配置及常见问题解决方案,如处理“服务配置文件不正确”错误和Gradle项目的配置方法。此技术特别适用于需要频繁处理协议或数据交互的应用场景。

我们很多人都写过代码自动生成的工具,比如用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 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;
      /**
  • 执行脚本命令.
    */
    @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://www.xx-ph.com/sitemap/post.xml
    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 插件)
相关文章
IDEA报错:Lombok Requires Annotation Processing
IDEA报错:Lombok Requires Annotation Processing
1102 0
IDEA报错:Lombok Requires Annotation Processing
|
Java Maven
Maven - Error:java: Annotation processing is not supported for module cycles. Please ensure that all
Maven - Error:java: Annotation processing is not supported for module cycles. Please ensure that all
933 0
Maven - Error:java: Annotation processing is not supported for module cycles. Please ensure that all
|
10月前
|
Java Maven 数据库
如何用 APT(Annotation Processing Tool)自动生成代码
如何用 APT(Annotation Processing Tool)自动生成代码
158 0
|
C++
VS Code注释插件doxygen documentation generator
VS Code注释插件doxygen documentation generator
690 0
VS Code注释插件doxygen documentation generator
Mockito框架学习 - how does expected annotation work
Mockito框架学习 - how does expected annotation work
Mockito框架学习 - how does expected annotation work
|
XML Android开发 数据格式
ABAP Development Tool前后台交互的原理
ABAP Development Tool前后台交互的原理
ABAP Development Tool前后台交互的原理
|
Java API
SAP ABAP CGLIB(Code Generation Library)的模拟实现
SAP ABAP CGLIB(Code Generation Library)的模拟实现
SAP ABAP CGLIB(Code Generation Library)的模拟实现
ABAP代码静态分析工具SQF - Support Query Framework
ABAP代码静态分析工具SQF - Support Query Framework
122 0
ABAP代码静态分析工具SQF - Support Query Framework