近期开发系统过程中遇到的一个需求,系统给定一个接口,用户可以自定义开发该接口的实现,并将实现打成jar包,上传到系统中。系统完成热部署,并切换该接口的实现。
定义简单的接口 这里以一个简单的计算器功能为例,接口定义比较简单,直接上代码。
public interface calculator { int calculate(int a, int b); int add(int a, int b);} 该接口的一个简单的实现 考虑到用户实现接口的两种方式,使用spring上下文管理的方式,或者不依赖spring管理的方式,这里称它们为注解方式和反射方式。calculate方法对应注解方式,add方法对应反射方式。计算器接口实现类的代码如下:
@servicepublic 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的代码如下:
@servicepublic 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 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); } } -end-
减持计划公布后,长电科技股价不断下跌
SIA联盟论剑浦江,中国传感器产业发展迎合IoT多元需求
展会回顾 | 成都国际工业博览会 虹科的精彩表现!
LED节能灯比白炽灯更省电的原因分析
用RT5350 OpenWrt Linux,制作物联网风幕机
系统完成热部署,并切换该接口的实现
俄罗斯批准了一项新法律将禁止军人使用智能手机和社交网络
波士顿动力官方推出了全新主角物流机器人Handle——一款专为物流设计的移动操纵机器人
怎样去设计一种基于SOA架构的热管理系统软件呢
AI与工业互联网相辅相成 促工业智能生产
阿里启动香港上市 人工智能是阿里布局之重
用PIC12C508制作太阳能热水自动控制器,PIC12C508 for water heaters
苹果在性能上的优势难超越,iPhone8仍完爆各家旗舰
pcb设计原理图的之跟踪整个电路的电压和电流解析
ANSYS结构振动试验仿真方法
丰田零排放氢燃料电池汽车全数召回!电池输出电压有问题
将AI嵌入到整个系统设计工作流的步骤及应用
iPhone 12更多规格揭示,或配备飞行时间传感器
光照培养箱是什么,它具有哪些优势功能
变频恒压供水控制器几类操纵方法的介绍