<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          項(xiàng)目終于用上了插入式注解,真香!

          共 7949字,需瀏覽 16分鐘

           ·

          2024-06-05 07:40

          關(guān)注我們,設(shè)為星標(biāo),每天7:40不見(jiàn)不散,架構(gòu)路上與您共享

          回復(fù)架構(gòu)師獲取資源


          大家好,我是你們的朋友架構(gòu)君,一個(gè)會(huì)寫(xiě)代碼吟詩(shī)的架構(gòu)師。


          來(lái)源:碼猿技術(shù)專(zhuān)欄

          插入式注解處理器在《深入理解Java虛擬機(jī)》一書(shū)中有一些介紹(前端編譯篇有提到),但一直沒(méi)有機(jī)會(huì)使用,直到碰到這個(gè)需求,覺(jué)得再合適不過(guò)了,就簡(jiǎn)單用了一下,這里做個(gè)記錄。

          了解過(guò)lombok底層原理的都知道其使用的就是的插入式注解,那么今天筆者就以真實(shí)場(chǎng)景演示一下插入式注解的使用。

          需求

          我們?yōu)楣咎峁┝艘惶淄ㄓ玫腏AVA基礎(chǔ)組件包,組件包內(nèi)有不同的模塊,比如熔斷模塊、負(fù)載均模塊、rpc模塊等等,這些模塊均會(huì)被打成jar包,然后發(fā)布到公司的內(nèi)部代碼倉(cāng)庫(kù)中,供其他人引入使用。

          這份代碼會(huì)不斷的迭代,我們希望可以通過(guò)promethus來(lái)監(jiān)控現(xiàn)在公司內(nèi)使用各版本代碼庫(kù)的比例,希望達(dá)到的效果圖如下:

          圖片

          我們希望看到每一個(gè)版本的使用率,這有利于我們做版本兼容,必要的時(shí)候可以對(duì)古早版本使用者溯源。

          問(wèn)題

          需求似乎很簡(jiǎn)單,但真要獲取自身的jar版本號(hào)還是挺麻煩的,有個(gè)比較簡(jiǎn)單但陰間的辦法,就是給每一個(gè)組件都加上當(dāng)前的jar版本號(hào),寫(xiě)到配置文件里或者直接設(shè)置成常量,這樣上報(bào)promethus時(shí)就可以直接獲取到j(luò)ar包版本號(hào)了,這個(gè)方法雖然可以解決問(wèn)題,但每次迭代版本都要跟著改一遍所有組件包的版本號(hào)數(shù)據(jù),過(guò)于麻煩。

          有沒(méi)有更好的解決辦法呢?比如我們可不可以在gradle打包構(gòu)建時(shí)拿到j(luò)ar包的版本號(hào),然后注入到每個(gè)組件中去呢?就像lombok那樣,不需要寫(xiě)get、set方法,只需要加個(gè)注解標(biāo)記就可以自動(dòng)注入get、set方法。

          比如我們可以給每個(gè)組件定義一個(gè)空常量,加上自定義的注解:

          @TrisceliVersion
          public static final String version = "";

          然后像lombok生成set/get方法那樣注入真正的版本號(hào):

          @TrisceliVersion
          public static final String version = "1.0.31-SNAPSHOT";

          參考lombok的實(shí)現(xiàn),這其實(shí)是可以做到的,下面來(lái)看解決方案。

          解決

          java中解析一個(gè)注解的方式主要有兩種:編譯期掃描、運(yùn)行期反射,這是lombok @Setter的實(shí)現(xiàn):

          @Target({ElementType.FIELD, ElementType.TYPE})
          @Retention(RetentionPolicy.SOURCE)
          public @interface Setter {
             // 略...
          }

          可以看到@SetterRetentionSOURCE類(lèi)型的,也就是說(shuō)這個(gè)注解只在編譯期有效,它甚至不會(huì)被編入class文件,所以lombok無(wú)疑是第一種解析方式,那用什么方式可以在編譯期就讓注解被解析到并執(zhí)行我們的解析代碼呢?答案就是定義插入式注解處理器(通過(guò)JSR-269提案定義的Pluggable Annotation Processing API實(shí)現(xiàn))

          插入式注解處理器的觸發(fā)點(diǎn)如下圖所示:

          圖片

          也就是說(shuō)插入式注解處理器可以幫助我們?cè)诰幾g期修改抽象語(yǔ)法樹(shù)(AST)!所以現(xiàn)在我們只需要自定義一個(gè)這樣的處理器,然后其內(nèi)部拿到j(luò)ar版本信息(因?yàn)槭蔷幾g期,可以找到源碼的path,源碼里隨便搞個(gè)文件存放版本號(hào),然后用java io讀取進(jìn)來(lái)即可),再將注解對(duì)應(yīng)語(yǔ)法樹(shù)上的常量值設(shè)置成jar包版本號(hào),語(yǔ)法樹(shù)變了,最終生成的字節(jié)碼也會(huì)跟著變,這樣就實(shí)現(xiàn)了我們想在編譯期給常量version注入值的愿望。

          自定義一個(gè)插入式注解處理器也很簡(jiǎn)單,首先要將自己的注解定義出來(lái):

          @Documented
          @Retention(RetentionPolicy.SOURCE) //只在編譯期有效,最終不會(huì)打進(jìn)class文件中
          @Target({ElementType.FIELD}) //僅允許作用于類(lèi)屬性之上
          public @interface TrisceliVersion {
          }

          然后定義一個(gè)繼承了AbstractProcessor的處理器:

          /**
           * {@link AbstractProcessor} 就屬于 Pluggable Annotation Processing API
           */
          public class TrisceliVersionProcessor extends AbstractProcessor {

              private JavacTrees javacTrees;
              private TreeMaker treeMaker;
              private ProcessingEnvironment processingEnv;

              /**
               * 初始化處理器
               *
               * @param processingEnv 提供了一系列的實(shí)用工具
               */
              @SneakyThrows
              @Override
              public synchronized void init(ProcessingEnvironment processingEnv) {
                  super.init(processingEnv);
                  this.processingEnv = processingEnv;
                  this.javacTrees = JavacTrees.instance(processingEnv);
                  Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
                  this.treeMaker = TreeMaker.instance(context);
              }


              @Override
              public SourceVersion getSupportedSourceVersion() {
                  return SourceVersion.latest();
              }

              @Override
              public Set<String> getSupportedAnnotationTypes() {
                  HashSet<String> set = new HashSet<>();
                  set.add(TrisceliVersion.class.getName()); // 支持解析的注解
                  return set;
              }

              @Override
              public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
                  for (TypeElement t : annotations) {
                      for (Element e : roundEnv.getElementsAnnotatedWith(t)) { // 獲取到給定注解的element(element可以是一個(gè)類(lèi)、方法、包等)
                          // JCVariableDecl為字段/變量定義語(yǔ)法樹(shù)節(jié)點(diǎn)
                          JCTree.JCVariableDecl jcv = (JCTree.JCVariableDecl) javacTrees.getTree(e);
                          String varType = jcv.vartype.type.toString();
                          if (!"java.lang.String".equals(varType)) { // 限定變量類(lèi)型必須是String類(lèi)型,否則拋異常
                              printErrorMessage(e, "Type '" + varType + "'" + " is not support.");
                          }
                          jcv.init = treeMaker.Literal(getVersion()); // 給這個(gè)字段賦值,也就是getVersion的返回值
                      }
                  }
                  return true;
              }

              /**
               * 利用processingEnv內(nèi)的Messager對(duì)象輸出一些日志
               *
               * @param e element
               * @param m error message
               */
              private void printErrorMessage(Element e, String m) {
                  processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, m, e);
              }

              private String getVersion() {
                  /**
                   * 獲取version,這里省略掉復(fù)雜的代碼,直接返回固定值
                   */
                  return "v1.0.1";
              }

          定義好的處理器需要SPI機(jī)制被發(fā)現(xiàn),所以需要定義META.services

          圖片

          測(cè)試

          新建測(cè)試模塊,引入剛才寫(xiě)好的代碼包:

          圖片

          這是Test類(lèi):

          圖片

          現(xiàn)在我們只需要讓gradle build一下,新得到的字節(jié)碼中該字段就有值了:

          圖片

          這只是插入式注解處理器 功能的冰山一角,既然它可以通過(guò)修改抽象語(yǔ)法樹(shù)來(lái)控制生成的字節(jié)碼,那么自然就有人能充分利用其特性來(lái)實(shí)現(xiàn)一些很酷的插件,比如lombok,我們?cè)僖膊挥脤?xiě)諸如set/get這種模板式的代碼了,只要我們足夠有創(chuàng)意,就可以讓基于這一套API實(shí)現(xiàn)的插件在功能上有很大的發(fā)揮空間。

          到此文章就結(jié)束了。Java架構(gòu)師必看一個(gè)集公眾號(hào)、小程序、網(wǎng)站(3合1的文章平臺(tái),給您架構(gòu)路上一臂之力)。如果今天的文章對(duì)你在進(jìn)階架構(gòu)師的路上有新的啟發(fā)和進(jìn)步,歡迎轉(zhuǎn)發(fā)給更多人。歡迎加入架構(gòu)師社區(qū)技術(shù)交流群,眾多大咖帶你進(jìn)階架構(gòu)師,在后臺(tái)回復(fù)“加群”即可入群。



          這些年小編給你分享過(guò)的干貨


          1.idea2023.3.4永久激活碼(親測(cè)可用)

          2.優(yōu)質(zhì)ERP系統(tǒng)帶進(jìn)銷(xiāo)存財(cái)務(wù)生產(chǎn)功能(附源碼)

          3.優(yōu)質(zhì)SpringBoot帶工作流管理項(xiàng)目(附源碼)

          4.最好用的OA系統(tǒng),拿來(lái)即用(附源碼)

          5.SBoot+Vue外賣(mài)系統(tǒng)前后端都有(附源碼

          6.SBoot+Vue可視化大屏拖拽項(xiàng)目(附源碼)


          轉(zhuǎn)發(fā)在看就是最大的支持??

          瀏覽 35
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  豆花视频入口 | 日韩一级特别 | 人人澡人人妻人人爽人人蜜桃 | 日本视频,日本高清视频 | 欧美成人高潮一二区在线看 |