• 从maven的debug compile到java的编译时注解(与springboot项目整合)


      事情的开始要从周一说起,那天晚上我正常编译打包准备更换部件,这时突然发现maven有个选项是debug maven compile,遂感到奇怪,这玩意有啥用??,唯一能想到的是编译时进行debug,但具体的应用场景不清楚,自从架构升级到中台之后,我们负责的模块再也没有控制器了,统一放到了网关部件,网关只依赖各个部件的"能力层",即只依赖接口,需要执行业务逻辑时需要解析各个部件的实现层提供的api文件,这个文件就是一个json数组,但是这玩意很烦,需要手动去配置,我通常是拷贝一个再去修改,但经常翻车,忘记把service或者method改掉,这时我突然想到编译时生成配置文件然后打包不就完事了吗?

    1  {
    2     "uri": "/api/test/ok",
    3     "operation": "hello",
    4     "description": "hello",
    5     "service": "xxxx.service",
    6     "method": "sayHello"
    7   }

      第一个想到的实现方式就是注解,但之前用的最多的注解类型是RUNTIME类型的,可以结合spring,反射+point实现注解逻辑,但编译时注解怎么操作?原理是javac编译的过程是一个独立的虚拟机进程,jdk提供了一个AbstractProcessor留给我们去继承重写相关方法(熟悉设计模式的估计已经想到模板模式了),达到编译时执行我们的注解处理器逻辑的目的.怎么做的问题解决了,可如何让虚拟机去加载我们的注解处理器呢?答案是SPI(service provider interfacse),如果你熟悉dubbo,或者spring,你会经常发现他们的jar包中的META-INF下面总是别有洞天,来两张图感受下

     那到底啥是SPI,大白话就是第三方框架或者jsr相关规范定义好了接口,你直接来实现,并且告诉别人这个接口的实现类必须是我写的实现类,怎么操作呢?原始的java SPI网上一搜一堆,META-INF下新建services文件夹,新建文件名称为接口的全限定名,内容为实现类的全限定名,之后使用ServiceLoader进行加载,不过这玩意很烦,只要你写到这个文件类的实现都会被加载,所以dubbo做了优化,改成了键值对的方式,具体的可以看dubbo.internal下的实现

     

    ok,把话说回来,SPI的作用就是XXInterface service = your impl,当你作为一个第三方框架的服务提供者,你就感受到这样做的好处了,后面会再写一篇文章详细介绍,等不及的可以先去看effective java,静态工厂方法那一块也有相关SPI讲解

      照猫画虎,只要我们的注解处理器也这样操作就可以了,刚刚说到我们继承了AbstractProcessor, 那么我们的spi文件名就是他实现的接口全限定名了,内容是我们的注解处理器的全限定名了,so

     

    如果你感觉这样麻烦采用guava提供的注解@AutoService即可,这个注解帮我们生成services下的文件,编码会贴在下面,不过在此之前要先说说怎么整合到我们的项目中?

      首先给待整合的项目加入我们注解处理器项目的依赖,然后我们需要配置下maven的打包插件在编译时执行我们的注解处理器,在整合的过程中发现,由于使用了lombok,其自身也是借助编译处理器生成getter,setter代码所以我们也要让maven执行lombok的注解处理器,否则编译时各种找不到符号,可是lombok的注解处理器叫啥名?别着急,找到lombok的jar包然后去找META-INF下的services

       看到了熟悉的文件名,查看内容就有了下面的注解处理器配置,问题解决,最后贴下配置和代码

     1 <dependency>
     2     <groupId>xxx</groupId>
     3     <artifactId>api-build</artifactId>
     4     <version>1.0-SNAPSHOT</version>
     5     <scope>provided</scope>
     6 </dependency>
     7 
     8 
     9 <plugin>
    10     <artifactId>maven-compiler-plugin</artifactId>
    11     <version>3.3</version>
    12     <configuration>
    13         <source>1.8</source>
    14         <target>1.8</target>
    15         <encoding>UTF-8</encoding>
    16         <annotationProcessors>
    17             <annotationProcessor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor</annotationProcessor>
    18             <annotationProcessor>lombok.launch.AnnotationProcessorHider$ClaimingProcessor</annotationProcessor>
    19             <annotationProcessor>你的注解处理器限定名称</annotationProcessor>
    20         </annotationProcessors>
    21     </configuration>
    22 </plugin>

    注解定义,注意是编译时注解RetenionPolicy.CLASS

     1 @Documented
     2 @Target(ElementType.METHOD)
     3 @Retention(RetentionPolicy.CLASS)
     4 public @interface ApiFunction
     5 {
     6 
     7     String uri() default "";
     8 
     9     String operation() default "";
    10 
    11     String description() default "";
    12 
    13    /* String service() default "";
    14 
    15     String method() default "";*/
    16 
    17     String group() default "";
    18 
    19     String version() default "";
    20 
    21     String[] authorities() default {};
    22 
    23     boolean needAuth() default true;
    24 
    25     int priority() default 0;
    26 
    27 }

    注解处理器相关代码与依赖

     1 <!--@AutoService-->
     2 <dependency>
     3     <groupId>com.google.auto.service</groupId>
     4     <artifactId>auto-service</artifactId>
     5     <version>1.0-rc6</version>
     6 </dependency>
     7 
     8 <!--注解处理器自身是个processor,所以编译时不可再去指定processor,否则循环调用自己,xx not found exception,gg-->
     9 <plugin>
    10     <artifactId>maven-compiler-plugin</artifactId>
    11     <version>3.8.0</version>
    12     <configuration>
    13         <encoding>UTF-8</encoding>
    14         <source>1.8</source>
    15         <target>1.8</target>
    16         <proc>none</proc>
    17     </configuration>
    18 </plugin>
      1 /**
      2  * @author tele
      3  * @Description
      4  * @create 2020-09-07
      5  */
      6 @AutoService(Processor.class)
      7 public class ApiInfoGenerateProcessor extends AbstractProcessor
      8 {
      9     /**
     10      * application.properties中的key,是否允许使用注解生成api-define.json
     11      */
     12     private static final String SWITCH = "api.auto-generate.enable";
     13 
     14     /**
     15      * 文件路径
     16      */
     17     private static final String API_PATH = "config/api-define.json";
     18 
     19     /**
     20      * 配置项路径
     21      */
     22     private static final String APPLICATION_PATH = "application.properties";
     23 
     24     private static boolean enable;
     25 
     26     private FileObject apiInfoFile;
     27 
     28     @Override
     29     public synchronized void init(ProcessingEnvironment processingEnv)
     30     {
     31         try
     32         {
     33             // 文件写入到编译后路径
     34             apiInfoFile = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT,"",API_PATH);
     35             // 从源码位置读取配置开关
     36             InputStream inputStream = processingEnv.getFiler().getResource(StandardLocation.CLASS_PATH, "", APPLICATION_PATH).openInputStream();
     37             Properties properties = new Properties();
     38             properties.load(inputStream);
     39             enable = Boolean.valueOf(properties.getProperty(SWITCH));
              // TODO close and check
    40 } 41 catch (IOException e) 42 { 43 e.printStackTrace(); 44 } 45 } 46 47 /** 48 * 指定支持的jdk版本 49 * @return 50 */ 51 @Override 52 public SourceVersion getSupportedSourceVersion() 53 { 54 return SourceVersion.latestSupported(); 55 } 56 57 /** 58 * 处理哪种类型的注解, * 表示处理所有类型的注解 59 * @return 60 */ 61 @Override 62 public Set<String> getSupportedAnnotationTypes() 63 { 64 return Sets.newHashSet(ApiFunction.class.getCanonicalName()); 65 } 66 67 /** 68 * 69 * @param annotations getSupportedAnnotationTypes返回的注解处理器集合 70 * @param roundEnv 获取扫描到的注解节点 71 * @return 当你有多个注解处理器时,返回true表示其他注解处理器不再对该类型的注解进行处理 72 */ 73 @Override 74 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) 75 { 76 if(!annotations.isEmpty() && enable) { 77 Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(ApiFunction.class); 78 List<String> apiInfoList = elementsAnnotatedWith.stream().filter(e -> e.getEnclosingElement().getKind().equals(ElementKind.CLASS)).map(e -> { 79 // service 获得父元素,注解加在方法上,父元素就是类或者接口了 80 TypeElement element = (TypeElement)e.getEnclosingElement(); 81 // method 82 String methodName = String.valueOf(e.getSimpleName()); 83 ApiFunction annotation = e.getAnnotation(ApiFunction.class); 84 Map<String, Object> map = new LinkedHashMap<>(8); 85 map.put("uri", annotation.uri()); 86 map.put("operation", annotation.operation()); 87 map.put("description", annotation.description()); 88 map.put("service", String.valueOf(element.getQualifiedName())); 89 map.put("method", methodName); 90 if(annotation.group() != null && !"".equals(annotation.group())) { 91 map.put("group", annotation.group()); 92 } 93 if(annotation.version() != null && !"".equals(annotation.version())) { 94 map.put("version", annotation.version()); 95 } 96 if(annotation.authorities() != null && annotation.authorities().length != 0) { 97 map.put("authorities",Arrays.asList(annotation.authorities())); 98 } 99 // access 默认解析为true 100 if(!annotation.needAuth()) { 101 map.put("needAuth", annotation.needAuth()); 102 } 103 if(annotation.priority() != 0) { 104 map.put("priority", annotation.priority()); 105 } 106 return JSONUtil.toJson(map,true); 107 }).collect(Collectors.toList()); 108 OutputStream outputStream = null; 109 try 110 { 111 File file = new File(apiInfoFile.toUri()); 112 if(!file.exists()) { 113 file.getParentFile().mkdirs(); 114 file.createNewFile(); 115 } 116 System.out.println(String.format("find api:%d", apiInfoList.size())); 117 outputStream = new FileOutputStream(file); 118 IOUtils.write(String.valueOf(apiInfoList), outputStream, StandardCharsets.UTF_8); 119 outputStream.flush(); 120 } 121 catch (IOException e) 122 { 123 e.printStackTrace(); 124 }finally 125 { 126 try 127 { 128 IOUtils.close(outputStream); 129 } 130 catch (IOException e) 131 { 132 e.printStackTrace(); 133 } 134 } 135 } 136 return true; 137 } 138 }

    说回开头,maven的debug问题,当你在依赖注解处理器的项目上执行debug maven compile时,只要给注解处理器代码打断点就ok了更复杂的注解处理器应用可以参考https://juejin.im/post/6844903879524483086,也可以参考<<深入理解java虚拟机>>第十章关的插入式注解处理器相关内容

  • 相关阅读:
    设计模式java----单例模式
    创建三个线程按顺序输出1-60,每个线程输出5个数
    java笔记----线程状态转换函数
    java笔记----常见的异常
    java一个数分解的质因数java
    MapReduce ----数据去重
    MapReduce ----倒排索引
    报错org.apache.hadoop.mapreduce.lib.input.FileSplit cannot be cast to org.apache.hadoop.mapred.FileSplit
    NumPy的使用(一)
    python----csv的使用
  • 原文地址:https://www.cnblogs.com/tele-share/p/13658188.html
Copyright © 2020-2023  润新知