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

          動(dòng)態(tài)上傳jar包熱部署實(shí)戰(zhàn)

          共 5583字,需瀏覽 12分鐘

           ·

          2022-06-08 07:13



          來源:blog.csdn.net/zhangzhiqiang_0912/

          article/details/106980080

          • 定義簡單的接口
          • 該接口的一個(gè)簡單的實(shí)現(xiàn)
          • 反射方式熱部署
          • 注解方式熱部署
          • 刪除jar時(shí),需要同時(shí)刪除spring容器中注冊的bean
          • 測試

          近期開發(fā)系統(tǒng)過程中遇到的一個(gè)需求,系統(tǒng)給定一個(gè)接口,用戶可以自定義開發(fā)該接口的實(shí)現(xiàn),并將實(shí)現(xiàn)打成jar包,上傳到系統(tǒng)中。系統(tǒng)完成熱部署,并切換該接口的實(shí)現(xiàn)。

          定義簡單的接口

          這里以一個(gè)簡單的計(jì)算器功能為例,接口定義比較簡單,直接上代碼。

          public?interface?Calculator?{
          ????int?calculate(int?a,?int?b);
          ????int?add(int?a,?int?b);
          }

          該接口的一個(gè)簡單的實(shí)現(xiàn)

          考慮到用戶實(shí)現(xiàn)接口的兩種方式,使用spring上下文管理的方式,或者不依賴spring管理的方式,這里稱它們?yōu)樽⒔夥绞胶头瓷浞绞健?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">calculate方法對應(yīng)注解方式,add方法對應(yīng)反射方式。計(jì)算器接口實(shí)現(xiàn)類的代碼如下:

          @Service
          public?class?CalculatorImpl?implements?Calculator?{
          ????@Autowired
          ????CalculatorCore?calculatorCore;
          ????/**
          ?????*?注解方式
          ?????*/

          ????@Override
          ????public?int?calculate(int?a,?int?b)?{
          ????????int?c?=?calculatorCore.add(a,?b);
          ????????return?c;
          ????}
          ????/**
          ?????*?反射方式
          ?????*/

          ????@Override
          ????public?int?add(int?a,?int?b)?{
          ????????return?new?CalculatorCore().add(a,?b);
          ????}
          }

          這里注入CalculatorCore的目的是為了驗(yàn)證在注解模式下,系統(tǒng)可以完整的構(gòu)造出bean的依賴體系,并注冊到當(dāng)前spring容器中。CalculatorCore的代碼如下:

          @Service
          public?class?CalculatorCore?{
          ????public?int?add(int?a,?int?b)?{
          ????????return?a+b;
          ????}
          }

          反射方式熱部署

          用戶把jar包上傳到系統(tǒng)的指定目錄下,這里定義上傳jar文件路徑為jarAddress,jar的Url路徑為jarPath。

          private?static?String?jarAddress?=?"E:/zzq/IDEA_WS/CalculatorTest/lib/Calculator.jar";
          private?static?String?jarPath?=?"file:/"?+?jarAddress;

          并且可以要求用戶填寫jar包中接口實(shí)現(xiàn)類的完整類名。接下來系統(tǒng)要把上傳的jar包加載到當(dāng)前線程的類加載器中,然后通過完整類名,加載得到該實(shí)現(xiàn)的Class對象。然后反射調(diào)用即可,完整代碼:

          /**
          ?*?熱加載Calculator接口的實(shí)現(xiàn)?反射方式
          ?*/

          public?static?void?hotDeployWithReflect()?throws?Exception?{
          ????URLClassLoader?urlClassLoader?=?new?URLClassLoader(new?URL[]{new?URL(jarPath)},?Thread.currentThread().getContextClassLoader());
          ????Class?clazz?=?urlClassLoader.loadClass("com.nci.cetc15.calculator.impl.CalculatorImpl");
          ????Calculator?calculator?=?(Calculator)?clazz.newInstance();
          ????int?result?=?calculator.add(1,?2);
          ????System.out.println(result);
          }

          注解方式熱部署

          如果用戶上傳的jar包含了spring的上下文,那么就需要掃描jar包里的所有需要注入spring容器的bean,注冊到當(dāng)前系統(tǒng)的spring容器中。其實(shí),這就是一個(gè)類的熱加載+動(dòng)態(tài)注冊的過程。

          直接上代碼:

          /**
          ?*?加入jar包后?動(dòng)態(tài)注冊bean到spring容器,包括bean的依賴
          ?*/

          public?static?void?hotDeployWithSpring()?throws?Exception?{
          ????Set?classNameSet?=?DeployUtils.readJarFile(jarAddress);
          ????URLClassLoader?urlClassLoader?=?new?URLClassLoader(new?URL[]{new?URL(jarPath)},?Thread.currentThread().getContextClassLoader());
          ????for?(String?className?:?classNameSet)?{
          ????????Class?clazz?=?urlClassLoader.loadClass(className);
          ????????if?(DeployUtils.isSpringBeanClass(clazz))?{
          ????????????BeanDefinitionBuilder?beanDefinitionBuilder?=?BeanDefinitionBuilder.genericBeanDefinition(clazz);
          ????????????defaultListableBeanFactory.registerBeanDefinition(DeployUtils.transformName(className),?beanDefinitionBuilder.getBeanDefinition());
          ????????}
          ????}
          }

          在這個(gè)過程中,將jar加載到當(dāng)前線程類加載器的過程和之前反射方式是一樣的。然后掃描jar包下所有的類文件,獲取到完整類名,并使用當(dāng)前線程類加載器加載出該類名對應(yīng)的class對象。判斷該class對象是否帶有spring的注解,如果包含,則將該對象注冊到系統(tǒng)的spring容器中。

          DeployUtils包含讀取jar包所有類文件的方法、判斷class對象是否包含sping注解的方法、獲取注冊對象對象名的方法。代碼如下:

          /**
          ?*?讀取jar包中所有類文件
          ?*/

          public?static?Set?readJarFile(String?jarAddress)?throws?IOException?{
          ????Set?classNameSet?=?new?HashSet<>();
          ????JarFile?jarFile?=?new?JarFile(jarAddress);
          ????Enumeration?entries?=?jarFile.entries();//遍歷整個(gè)jar文件
          ????while?(entries.hasMoreElements())?{
          ????????JarEntry?jarEntry?=?entries.nextElement();
          ????????String?name?=?jarEntry.getName();
          ????????if?(name.endsWith(".class"))?{
          ????????????String?className?=?name.replace(".class",?"").replaceAll("/",?".");
          ????????????classNameSet.add(className);
          ????????}
          ????}
          ????return?classNameSet;
          }
          /**
          ?*?方法描述?判斷class對象是否帶有spring的注解
          ?*/

          public?static?boolean?isSpringBeanClass(Class?cla)?{
          ????if?(cla?==?null)?{
          ????????return?false;
          ????}
          ????//是否是接口
          ????if?(cla.isInterface())?{
          ????????return?false;
          ????}
          ????//是否是抽象類
          ????if?(Modifier.isAbstract(cla.getModifiers()))?{
          ????????return?false;
          ????}
          ????if?(cla.getAnnotation(Component.class)?!=?null)?{
          ????????return?true;
          ????}
          ????if?(cla.getAnnotation(Repository.class)?!=?null)?{
          ????????return?true;
          ????}
          ????if?(cla.getAnnotation(Service.class)?!=?null)?{
          ????????return?true;
          ????}
          ????return?false;
          }
          /**
          ?*?類名首字母小寫?作為spring容器beanMap的key
          ?*/

          public?static?String?transformName(String?className)?{
          ????String?tmpstr?=?className.substring(className.lastIndexOf(".")?+?1);
          ????return?tmpstr.substring(0,?1).toLowerCase()?+?tmpstr.substring(1);
          }

          刪除jar時(shí),需要同時(shí)刪除spring容器中注冊的bean

          在jar包切換或刪除時(shí),需要將之前注冊到spring容器的bean刪除。spring容器的bean的刪除操作和注冊操作是相逆的過程,這里要注意使用同一個(gè)spring上下文。

          代碼如下:

          /**
          ?*?刪除jar包時(shí)?需要在spring容器刪除注入
          ?*/

          public?static?void?delete()?throws?Exception?{
          ????Set?classNameSet?=?DeployUtils.readJarFile(jarAddress);
          ????URLClassLoader?urlClassLoader?=?new?URLClassLoader(new?URL[]{new?URL(jarPath)},?Thread.currentThread().getContextClassLoader());
          ????for?(String?className?:?classNameSet)?{
          ????????Class?clazz?=?urlClassLoader.loadClass(className);
          ????????if?(DeployUtils.isSpringBeanClass(clazz))?{
          ????????????defaultListableBeanFactory.removeBeanDefinition(DeployUtils.transformName(className));
          ????????}
          ????}
          }

          測試

          測試類手動(dòng)模擬用戶上傳jar的功能。測試函數(shù)寫了個(gè)死循環(huán),一開始沒有找到j(luò)ar會(huì)拋出異常,捕獲該異常并睡眠10秒。這時(shí)候可以把jar手動(dòng)放到指定的目錄下。

          代碼如下:

          ?ApplicationContext?applicationContext?=?new?ClassPathXmlApplicationContext("applicationContext.xml");
          ????DefaultListableBeanFactory?defaultListableBeanFactory?=?(DefaultListableBeanFactory)?applicationContext.getAutowireCapableBeanFactory();
          ????while?(true)?{
          ????????try?{
          ??????????????hotDeployWithReflect();
          //????????????hotDeployWithSpring();
          //????????????delete();
          ????????????}?catch?(Exception?e)?{
          ????????????????e.printStackTrace();
          ????????????????Thread.sleep(1000?*?10);
          ????????????}
          ????????}

          程序汪資料鏈接

          程序汪接的7個(gè)私活都在這里,經(jīng)驗(yàn)整理

          Java項(xiàng)目分享 ?最新整理全集,找項(xiàng)目不累啦 07版

          堪稱神級的Spring Boot手冊,從基礎(chǔ)入門到實(shí)戰(zhàn)進(jìn)階

          臥槽!字節(jié)跳動(dòng)《算法中文手冊》火了,完整版 PDF 開放下載!

          臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開放下載!

          字節(jié)跳動(dòng)總結(jié)的設(shè)計(jì)模式 PDF 火了,完整版開放下載!


          歡迎添加程序汪個(gè)人微信 itwang009? 進(jìn)粉絲群或圍觀朋友圈

          瀏覽 59
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  色综合久久88色综合天天看泰 | 国产av佳作老友重逢 相干恨晚 | 国产精品在线免费观看 | 一级日韩影院 | 2021国产免费自拍视频 |