1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        動態(tài)上傳jar包熱部署實戰(zhàn)

        共 5583字,需瀏覽 12分鐘

         ·

        2022-06-08 07:13



        來源:blog.csdn.net/zhangzhiqiang_0912/

        article/details/106980080

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

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

        定義簡單的接口

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

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

        該接口的一個簡單的實現(xiàn)

        考慮到用戶實現(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)反射方式。計算器接口實現(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的目的是為了驗證在注解模式下,系統(tǒng)可以完整的構(gòu)造出bean的依賴體系,并注冊到當前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包中接口實現(xiàn)類的完整類名。接下來系統(tǒng)要把上傳的jar包加載到當前線程的類加載器中,然后通過完整類名,加載得到該實現(xiàn)的Class對象。然后反射調(diào)用即可,完整代碼:

        /**
        ?*?熱加載Calculator接口的實現(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,注冊到當前系統(tǒng)的spring容器中。其實,這就是一個類的熱加載+動態(tài)注冊的過程。

        直接上代碼:

        /**
        ?*?加入jar包后?動態(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());
        ????????}
        ????}
        }

        在這個過程中,將jar加載到當前線程類加載器的過程和之前反射方式是一樣的。然后掃描jar包下所有的類文件,獲取到完整類名,并使用當前線程類加載器加載出該類名對應(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();//遍歷整個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時,需要同時刪除spring容器中注冊的bean

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

        代碼如下:

        /**
        ?*?刪除jar包時?需要在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));
        ????????}
        ????}
        }

        測試

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

        代碼如下:

        ?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個私活都在這里,經(jīng)驗整理

        Java項目分享 ?最新整理全集,找項目不累啦 07版

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

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

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

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


        歡迎添加程序汪個人微信 itwang009? 進粉絲群或圍觀朋友圈

        瀏覽 59
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美操逼视屏 | 熟女超碰 | 韩剧大尺度电视剧免费观看 | 被士兵粗暴进入蹂躏小说 | 影视一区 | 91女人18毛片水多的意思 | 老外的大雞巴黑長粗视频 | 亚洲成人免费电影 | 五月停停开心无码网站 | 午夜色电影 |