<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>

          趁周末,來學點進階知識:Java 動態(tài)編譯

          共 4730字,需瀏覽 10分鐘

           ·

          2021-07-13 20:41

          來源 | https://zhenbianshu.github.io

          問題

          之前的文章從Spring 的環(huán)境到 Spring Cloud 的配置中提到過,我們在使用 Spring Cloud 進行動態(tài)化配置,它的實現(xiàn)步驟是先將動態(tài)配置通過 @Value 注入到一個動態(tài)配置 Bean,并將這個 Bean 用注解標記為 @RefreshScope,在配置變更后,這些動態(tài)配置 Bean 會被統(tǒng)一銷毀。

          之后 Spring Cloud 的 ContextRefresher 會將變更后的配置作為一個新的 Spring Environment 加載進 ApplicationContext,由于 Scoped Bean 都是 Lazy Init 的,它們會在下一次使用時被使用新的 Environment 重新創(chuàng)建。

          這套動態(tài)配置加載流程在使我們服務更加靈活的同時,也帶來了很大的風險。首先從業(yè)務上,修改配置不像上線這么”重量級”,不必要找 QA 進行回歸測試,這就有可能引發(fā)一系列奇怪的 Bug,而且長時間發(fā)現(xiàn)不了,另外,Spring Cloud 本身沒有 “fallback” 機制,一旦配置的數(shù)據(jù)類型出了問題,就會導致服務不可用。

          為此,我給 Spring Cloud 提了個 issue,但作者認為變動太大,不好改也不必改。

          其實我也明白這個問題的困境,每個人都得為自己要修改的配置負責,即使框架支持了 fallback,但將錯誤吞掉,配置修改后不生效也沒什么變化可能也并不符合用戶的期望。所以,盡量讓用戶要修改的配置正確成為了新的目標。

          基于這種需求,我添加了一個動態(tài)配置的校驗器,但實現(xiàn)里一部分代碼來自 github,所以本文在總結思路的同時,也幫助我理解所有代碼。

          另外,如果您正在學習Spring Cloud,推薦一個連載多年還在繼續(xù)更新的免費教程:https://blog.didispace.com/spring-cloud-learning/

          整體思路

          由于框架層沒法做太多事情,所以我的計劃是將這些配置取出來,構造出一個獨立的 Java 類,并在服務外新建一個 ApplicationContext 試圖通過構造出來的 Java 類初始化一個 Spring Bean,如果這個 Spring Bean 初始化過程中報錯了,說明配置是有問題的。

          動態(tài)編譯

          通過配置構造 Java 類

          首先要通過 .properties 文件構造出一個 Java 類,但問題是在配置里我們是不知道這些配置將要被怎么使用的,不知道它要被 Spring EL 如何處理,又將被轉成什么類型。

          這里我采用的策略是給配置添加注釋,注釋里使用一定的格式聲明 EL 表達式和要生成的字段類型,當然這種實現(xiàn)有點 low,有人提議把這些信息放到配置項的 key 里,之后會再進行優(yōu)化。

          把各個字段解析完成后放到準備到的類模板中,就生成了一個 Config.java 類字符串,之后就要將這個字符串編譯成字節(jié)碼并由 Spring 加載成 Bean。

          JavaCompiler

          由于 Config.java 是在運行時生成的,所以編譯也只能在運行時了,萬幸 Java 有提供 javax.util.JavaCompiler 類進行 Java 類的動態(tài)編譯,省去了”寫入文件 —— 命令行編譯 —— 類加載 —— 清理文件” 的復雜流程。

          JavaCompiler 的典型應用示例如下:

          JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
          JavaFileManager fileManager = javaCompiler.getStandardFileManager(null, null, null);
          CompilationTask task = javaCompiler.getTask(out, fileManager, diagnosticListener, options, classes, compilationUnits);
          task.call();
          FileObject outputFile = fileManager.getFileForOutput(null, null, null, null);
          outputFile.getCharContent(true);

          流程如下圖:JavaCompiler 通過 JavaFileManager 管理輸入和輸出文件,使用時通過 getTask() 方法提交一個異步 CompilationTask 進行代碼編譯,代碼編譯時,JavaCompiler 通過 getCharContent() 從傳入的 compilationUnits 獲取到 .java 文件內容,把編譯后的結果調用 CompiledByteCode 的 openOutputStream() 方法寫到 CompiledByteCode 對象里。

          委托模式

          由于 JavaCompiler 的默認實現(xiàn)都是通過文件進行的,這不符合我的期望,我需要的是輸入和輸出都在內存進行,所以需要修改 JavaCompiler 的實現(xiàn),JavaCompiler、JavaFileManager、JavaFileObject(Input/Output) 分別使用委托模式實現(xiàn)。其中 JavaFileManager 已經(jīng)有 ForwardingJavaFileManager 的實現(xiàn),JavaFileObject 也有 SimpleJavaFileObject 的實現(xiàn),我們繼承其實現(xiàn)后重寫部分方法即可。


          我參考的源碼:https://github.com/trung/InMemoryJavaCompiler

          如果您正在學習Spring Boot,推薦一個連載多年還在繼續(xù)更新的免費教程:http://blog.didispace.com/spring-boot-learning-2x/

          Spring Bean 實例化

          要將 Config 類實例化成 Bean,我們可以在 xml 里預定義它,在編譯結束后創(chuàng)建一個簡易的 FileSystemXmlApplicationContext 實例化這個 xml 內的 Bean。

          類加載器

          首先要讓 Spring 能夠加載到這些編譯好的字節(jié)碼,這就需要 ClassLoader 的配合。

          類加載器的默認實現(xiàn)不可能知道去加載我們內存里編譯好的字節(jié)碼,只好新加一個 ClassLoader,實現(xiàn)也很簡單,繼承 ClassLoader 抽象類,并實現(xiàn) findClass 方法即可。

          class MemoryClassLoader extends ClassLoader {
           @Override
           protected Class<?> findClass(String name) throws ClassNotFoundException {
               // 在 CompiledByteCode 類里將編譯后的字節(jié)碼放到 classLoader 的 classBytes 字段內。
            byte[] buf = classBytes.get(name);
            if (buf == null) {
             return super.findClass(name);
            }
            return defineClass(name, buf, 0, buf.length);
           }
          }

          配置和實現(xiàn)

          由于 Config Bean 的初始化依賴動態(tài)配置,我們還要把這些配置也添加到 Spring 環(huán)境內,我們知道 Spring 環(huán)境配置是由多個 PropertySource 構成的,向里面添加一個實現(xiàn)即可。然后就可以調用 application 的 refresh() 方法初始化上下文了,另外 Config Bean 被設置為懶加載了,不要忘記 get 一下使其被創(chuàng)建。

          最終的代碼如下:

          FileSystemXmlApplicationContext applicationContext = new FileSystemXmlApplicationContext();
          applicationContext.setClassLoader(memoryClassLoader);
          applicationContext.setConfigLocation("classpath*:/test.xml");
          Map<String, Object> propertyMap = buildDynamicPropertyMap();
          MapPropertySource mapPropertySource = new MapPropertySource("validate_source", propertyMap);
          applicationContext.getEnvironment().getPropertySources().addFirst(mapPropertySource);
          applicationContext.refresh();
          applicationContext.getBean("config");

          小結

          小項目完成的過程中,復習了很多知識,也嘗試了業(yè)務代碼中幾乎不會用到的設計模式,充滿了挑戰(zhàn)性。

          當然它現(xiàn)在還有配置不夠方便、錯誤提示不夠明確、沒解決配置 namespace 等問題,留到后面慢慢優(yōu)化吧~


          往期推薦

          程序員寫代碼崩潰,路過的暖心美團騎手:我?guī)湍憧纯矗?/p>

          群友:事務中的異常不也拋出了,為什么沒catch到而回滾?

          開源認證授權管理平臺 Keycloak 初體驗

          Spring發(fā)布新成員:Spring GraphQL!高調出場的GraphQL能火起來了嗎?

          推薦一本DD剛擼完的書,順便送一波!


          喜歡本文歡迎轉發(fā),關注我訂閱更多精彩

          關注我回復「加群」,加入Spring技術交流群

          瀏覽 80
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产精品亚洲五月天丁香 | 欧美精品国产动漫 | 99综合97 | 亚洲肏屄视频 | 好操逼|