还在手动部署jar包?太low了,动态上传jar包热部署真的爽!
大家好,我是寶哥!
近期開發系統過程中遇到的一個需求,系統給定一個接口,用戶可以自定義開發該接口的實現,并將實現打成jar包,上傳到系統中。系統完成熱部署,并切換該接口的實現。
定義簡單的接口
這里以一個簡單的計算器功能為例,接口定義比較簡單,直接上代碼。
public?interface?Calculator?{int?calculate(int?a,?int?b);int?add(int?a,?int?b); }該接口的一個簡單的實現
考慮到用戶實現接口的兩種方式,使用spring上下文管理的方式,或者不依賴spring管理的方式,這里稱它們為注解方式和反射方式。calculate方法對應注解方式,add方法對應反射方式。計算器接口實現類的代碼如下:
@Service public?class?CalculatorImpl?implements?Calculator?{@AutowiredCalculatorCore?calculatorCore;/***?注解方式*/@Overridepublic?int?calculate(int?a,?int?b)?{int?c?=?calculatorCore.add(a,?b);return?c;}/***?反射方式*/@Overridepublic?int?add(int?a,?int?b)?{return?new?CalculatorCore().add(a,?b);} }這里注入CalculatorCore的目的是為了驗證在注解模式下,系統可以完整的構造出bean的依賴體系,并注冊到當前spring容器中。CalculatorCore的代碼如下:
@Service public?class?CalculatorCore?{public?int?add(int?a,?int?b)?{return?a+b;} }反射方式熱部署
用戶把jar包上傳到系統的指定目錄下,這里定義上傳jar文件路徑為jarAddress,jar的Url路徑為jarPath。
private?static?String?jarAddress?=?"E:/zzq/IDEA_WS/CalculatorTest/lib/Calculator.jar"; private?static?String?jarPath?=?"file:/"?+?jarAddress;并且可以要求用戶填寫jar包中接口實現類的完整類名。接下來系統要把上傳的jar包加載到當前線程的類加載器中,然后通過完整類名,加載得到該實現的Class對象。然后反射調用即可,完整代碼:
/***?熱加載Calculator接口的實現?反射方式*/ 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,注冊到當前系統的spring容器中。其實,這就是一個類的熱加載+動態注冊的過程。
直接上代碼:
/***?加入jar包后?動態注冊bean到spring容器,包括bean的依賴*/ public?static?void?hotDeployWithSpring()?throws?Exception?{Set<String>?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包下所有的類文件,獲取到完整類名,并使用當前線程類加載器加載出該類名對應的class對象。判斷該class對象是否帶有spring的注解,如果包含,則將該對象注冊到系統的spring容器中。
DeployUtils包含讀取jar包所有類文件的方法、判斷class對象是否包含sping注解的方法、獲取注冊對象對象名的方法。代碼如下:
/***?讀取jar包中所有類文件*/ public?static?Set<String>?readJarFile(String?jarAddress)?throws?IOException?{Set<String>?classNameSet?=?new?HashSet<>();JarFile?jarFile?=?new?JarFile(jarAddress);Enumeration<JarEntry>?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<String>?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的功能。測試函數寫了個死循環,一開始沒有找到jar會拋出異常,捕獲該異常并睡眠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);}}來源:https://blog.csdn.net/zhangzhiqiang_0912
精彩推薦: MyBatis的三種分頁方式,你用過幾種?幾行代碼,搞定 SpringBoot 接口惡意刷新和暴力請求!Java實現人臉識別登錄、注冊等功能【附源碼】 SpringCloud+Gateway+Security 搭建微服務統一認證授權(附源碼)發現個工具,一鍵生成Spring Boot +Vue項目!接私活縮短一半工期...總結
以上是生活随笔為你收集整理的还在手动部署jar包?太low了,动态上传jar包热部署真的爽!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jdk动态代理invoke方法自动运行原
- 下一篇: Flash 安装、修复和诊断工具分享(F