從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虛擬機>>第十章關的插入式注解處理器相關內容

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM