变态需求给定一个接口,用户自定义动态实现上传热部署,咋搞?
近期开发系统过程中遇到的一个需求,系统给定一个接口,用户可以自定义开发该接口的实现,并将实现打成jar包,上传到系统中。系统完成热部署,并切换该接口的实现
定义简单的接口
这里以一个简单的计算器功能为例,接口定义比较简单,直接上代码。 public interface Calculator { int calculate(int a, int b); int add(int a, int b); } 该接口的一个简单的实现
考虑到用户实现接口的两种方式,使用spring上下文管理的方式,或者不依赖spring管理的方式,这里称它们为注解方式和反射方式。 calculate 方法对应注解方式,add方法对应反射方式。
这里推荐一个 Spring Boot 基础教程:
https://github.com/javastacks/spring-boot-best-practice
计算器接口实现类的代码如下: @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 的目的是为了验证在注解模式下,系统可以完整的构造出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容器中。其实,这就是一个类的热加载+动态注册的过程。另外,最新 Spring 面试题整理好了,大家可以在Java面试库小程序在线刷题。
直接上代码: /** * 加入jar包后 动态注册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包下所有的类文件,获取到完整类名,并使用当前线程类加载器加载出该类名对应的class对象。判断该class对象是否带有spring的注解,如果包含,则将该对象注册到系统的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的功能。测试函数写了个死循环,一开始没有找到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/article/details/106980080
汉武帝刘彻一场幻觉引起的巫蛊皇宫血案公元前92年,汉武帝坐在建章宫内养神,迷神之际,感觉有一名带剑的男子走进中龙华门,于是他猛地站起身,大声叫喊来人,抓刺客,他一喊,那人仿佛消失一般不见了,侍卫们一拥而入,整个建章宫
良缘(全集)在清朝年间,江苏按察使有一个小儿子叫黄有为。这个黄有为啊,从小就聪明伶俐。饱读诗书。在17岁的时候就参加了乡试,并且考取了借员。由于他家庭也不错,人也帅气,又颇有前途,上门来说亲的
AI绘画算不算艺术据人民网报道,在2022世界人工智能大会上,Tiamat人工智能艺术团队的展位吸引着好奇的观众,他们中有不少人是首次尝试AI绘画,普通人竟能像名家一样画画,呈现毕加索蒙克莫奈的画风
徕卡旗舰手机LeitzPhone2发布一英寸大底主摄,售价过万在去年6月,徕卡与日本软银公司联合推出了首款智能手机LeitzPhone1,得益于徕卡,该机在当时就有着相当不错的性能和摄影表现。而在近日,软银发布了全新一代的徕卡旗舰手机Leit
iPhone只能排第二,今年双十一销量冠军大爆冷双十一已进入尾声,大家关心的手机销量情况也基本已确定,就和预测的一样,苹果依然是最大赢家,根据京东双11手机竞速排行榜,销量前十中,iPhone霸占四席,分别是iPhone14iP
目前出色的3款国产手机,性能强悍价格良心,还买什么iPhone14?现在国产手机各方面做的真的非常不错,真的不比苹果差,目前这3款手机性能十分出色,性价比也很高,还买什么iPhone14?vivoX70Pro性能方面,搭载了骁龙888Plus芯片增
Web3。0是什么?Web3。0一夜之间传遍了整个大街小巷,那什么是Web3。0呢?这就得从Web1。0开始说起。1991年第一个网址的出现,就标志着我们迈进了Web1。0时代,特点是只读,Reado
薇娅被罚13亿后,投资的公司要来A股了雷军雷布斯是国内互联网创业投资界典型人物,无论是雷军这个名字,还是小米这个品牌,到哪都是自带光环自带流量。以直播带货封神的薇娅,本名黄薇,因为巨额偷税漏税让她跌落神坛,目前也是一个
智能制造时代,服装企业如何实现智能制造系统转型升级?什么是智能制造工厂好比一台复杂的汽车,需要各个部门高效协同,统一行动,才能使管理效率和生产效率达到平衡智能化就是透过网络让企业内部各关联部门,生产各关联环节及要素互连,实现高效协同
华为麒麟芯片被拒代工之后,台积电5nm利用率能撑多久?众所周知,目前全球5纳米芯片制程,只有台积电和三星能完成生产,在科技无国界之时,三星在供应自己三星手机使用的同时,也帮助苹果代工生产芯片,台积电就纯粹的多了,就是一家代加工企业,客
花钱不手软!沙特王储将造访首尔,订了乐天酒店400间房沙特阿拉伯王储穆罕默德本萨勒曼将在17至18日造访韩国首尔,下榻首尔繁华地段的乐天酒店,讨论从智慧城市到核能基建等议题。沙特王储此行也是花钱不手软,比如在有1058个客房的乐天酒店