变态需求给定一个接口,用户自定义动态实现上传热部署,咋搞?
近期开发系统过程中遇到的一个需求,系统给定一个接口,用户可以自定义开发该接口的实现,并将实现打成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
统筹兼顾,谱写跨越山海的航天协奏曲2022年10月31日下午,轰鸣声打破了海岛的寂静,搭载梦天实验舱的长征五号运载火箭,在我国文昌航天发射场点火升空,卫星顺利进入预定轨道,发射任务取得圆满成功,中心技术部以实际行动
泰州白米镇致力交出亩均改革高分答卷今年以来,泰州市姜堰区白米镇把亩均论英雄改革作为转变经济发展方式优化经济结构激发增长动力有力抓手,推动工业经济实现高质量发展。截止目前,全镇清退僵尸企业6家零税企业10家,在盘活的
披荆斩棘蔡珩唱歌演戏综艺,不同挑战让我更有冲劲从学习跳舞到成为一名演员,我已经走了23年。你问我,我的梦想在哪里?我还年轻,我还年轻。披荆斩棘第二季初舞台播出后,演员蔡珩在社交媒体平台上发文呼应演唱的歌曲我还年轻我还年轻。为期
江苏首例网约车司机获赔误工费1。54万元央广网南京10月31日消息(记者王锡斐)近日,江苏泰州一网约车因交通事故导致车辆受损修理,从事网约车经营业务的个人获赔车辆营运损失15400元的案件,成为江苏省首个网约车司机误工费
她不过是指了个路2019年贾永婷去山东看望孙洪正夫妇作者杜佳冰编辑从玉华孙洪正把2018年9月19日这个日子记了5年,但贾永婷起初并没放在心上。那就是一个普通的星期三,25岁的贾永婷忙到晚上9点才
德邦净利飙升20倍,刘强东捡到宝了出品电商头条作者李松月净利大涨20倍,德邦重新看见希望德邦交出了一份让市场震惊的成绩单。10月27日,京东物流和德邦共同发布了德邦2022年第三季度财报。数据显示,该季度德邦营收8
中航重机2022年前三季度净利润9。16亿元同比增长50。46中证智能财讯中航重机(600765)10月31日披露2022年第三季度报告。2022年前三季度,公司实现营业总收入77。18亿元,同比增长18。34归母净利润9。16亿元,同比增长
如果只看联赛的表现!这六名球员更应该入选国家队,杜锋任人唯亲最新一期的中国男篮大名单出来了,一共是17人,辽宁队没有球员入选,广东队有四名球员入选赵睿胡明轩徐杰任骏飞,深圳队有三名球员入选顾全周鹏沈梓捷,浙江队有三名球员入选吴前王奕博陆文博
足坛绝杀夜!孙兴慜莱万助热刺巴萨绝处逢生,利物浦遭降级队羞辱北京时间10月29日晚间到30日凌晨,欧洲五大联赛继续展开争夺,这一夜可以说是绝对的绝杀夜,在英超和西甲当中,3场比赛都在比赛的最后时刻出现绝杀的情况。这其中热刺和巴萨都在客场绝处
古而不老,是绍兴绍兴文旅发布绍兴,古称越州,这是一座2500多年钟灵毓秀的历史名城。文物之邦,鱼米之乡,镜湖月下的吟诵,光怪陆离的梦境,从市井小吃,到黄酒文化,百川汇海,耐人寻味。古而不老,陈而不
Utair增加飞往撒马尔罕的航班原标题Utair增加莫斯科飞往撒马尔罕的航班据详实网报道,11月14日起,俄罗斯优梯航空公司(Utair)将推出每周一从莫斯科飞往撒马尔罕的航班,届时,该航线航班每周将有4班周一周