0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

系统完成热部署,并切换该接口的实现

Android编程精选 来源:zhangzhiqiang_0912 作者:Android编程精选 2022-07-23 11:03 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

近期开发系统过程中遇到的一个需求,系统给定一个接口,用户可以自定义开发该接口的实现,并将实现打成jar包,上传到系统中。系统完成热部署,并切换该接口的实现。

定义简单的接口

这里以一个简单的计算器功能为例,接口定义比较简单,直接上代码。

publicinterfaceCalculator{
intcalculate(inta,intb);
intadd(inta,intb);
}

该接口的一个简单的实现

考虑到用户实现接口的两种方式,使用spring上下文管理的方式,或者不依赖spring管理的方式,这里称它们为注解方式和反射方式。calculate方法对应注解方式,add方法对应反射方式。计算器接口实现类的代码如下:

@Service
publicclassCalculatorImplimplementsCalculator{
@Autowired
CalculatorCorecalculatorCore;
/**
*注解方式
*/
@Override
publicintcalculate(inta,intb){
intc=calculatorCore.add(a,b);
returnc;
}
/**
*反射方式
*/
@Override
publicintadd(inta,intb){
returnnewCalculatorCore().add(a,b);
}
}

这里注入CalculatorCore的目的是为了验证在注解模式下,系统可以完整的构造出bean的依赖体系,并注册到当前spring容器中。CalculatorCore的代码如下:

@Service
publicclassCalculatorCore{
publicintadd(inta,intb){
returna+b;
}
}

反射方式热部署

用户把jar包上传到系统的指定目录下,这里定义上传jar文件路径为jarAddress,jar的Url路径为jarPath。

privatestaticStringjarAddress="E:/zzq/IDEA_WS/CalculatorTest/lib/Calculator.jar";
privatestaticStringjarPath="file:/"+jarAddress;

并且可以要求用户填写jar包中接口实现类的完整类名。接下来系统要把上传的jar包加载到当前线程的类加载器中,然后通过完整类名,加载得到该实现的Class对象。然后反射调用即可,完整代码:

/**
*热加载Calculator接口的实现反射方式
*/
publicstaticvoidhotDeployWithReflect()throwsException{
URLClassLoaderurlClassLoader=newURLClassLoader(newURL[]{newURL(jarPath)},Thread.currentThread().getContextClassLoader());
Classclazz=urlClassLoader.loadClass("com.nci.cetc15.calculator.impl.CalculatorImpl");
Calculatorcalculator=(Calculator)clazz.newInstance();
intresult=calculator.add(1,2);
System.out.println(result);
}

注解方式热部署

如果用户上传的jar包含了spring的上下文,那么就需要扫描jar包里的所有需要注入spring容器的bean,注册到当前系统的spring容器中。其实,这就是一个类的热加载+动态注册的过程。

直接上代码:

/**
*加入jar包后动态注册bean到spring容器,包括bean的依赖
*/
publicstaticvoidhotDeployWithSpring()throwsException{
SetclassNameSet=DeployUtils.readJarFile(jarAddress);
URLClassLoaderurlClassLoader=newURLClassLoader(newURL[]{newURL(jarPath)},Thread.currentThread().getContextClassLoader());
for(StringclassName:classNameSet){
Classclazz=urlClassLoader.loadClass(className);
if(DeployUtils.isSpringBeanClass(clazz)){
BeanDefinitionBuilderbeanDefinitionBuilder=BeanDefinitionBuilder.genericBeanDefinition(clazz);
defaultListableBeanFactory.registerBeanDefinition(DeployUtils.transformName(className),beanDefinitionBuilder.getBeanDefinition());
}
}
}

在这个过程中,将jar加载到当前线程类加载器的过程和之前反射方式是一样的。然后扫描jar包下所有的类文件,获取到完整类名,并使用当前线程类加载器加载出该类名对应的class对象。判断该class对象是否带有spring的注解,如果包含,则将该对象注册到系统的spring容器中。

DeployUtils包含读取jar包所有类文件的方法、判断class对象是否包含sping注解的方法、获取注册对象对象名的方法。代码如下:

/**
*读取jar包中所有类文件
*/
publicstaticSetreadJarFile(StringjarAddress)throwsIOException{
SetclassNameSet=newHashSet<>();
JarFilejarFile=newJarFile(jarAddress);
Enumerationentries=jarFile.entries();//遍历整个jar文件
while(entries.hasMoreElements()){
JarEntryjarEntry=entries.nextElement();
Stringname=jarEntry.getName();
if(name.endsWith(".class")){
StringclassName=name.replace(".class","").replaceAll("/",".");
classNameSet.add(className);
}
}
returnclassNameSet;
}
/**
*方法描述判断class对象是否带有spring的注解
*/
publicstaticbooleanisSpringBeanClass(Classcla){
if(cla==null){
returnfalse;
}
//是否是接口
if(cla.isInterface()){
returnfalse;
}
//是否是抽象类
if(Modifier.isAbstract(cla.getModifiers())){
returnfalse;
}
if(cla.getAnnotation(Component.class)!=null){
returntrue;
}
if(cla.getAnnotation(Repository.class)!=null){
returntrue;
}
if(cla.getAnnotation(Service.class)!=null){
returntrue;
}
returnfalse;
}
/**
*类名首字母小写作为spring容器beanMap的key
*/
publicstaticStringtransformName(StringclassName){
Stringtmpstr=className.substring(className.lastIndexOf(".")+1);
returntmpstr.substring(0,1).toLowerCase()+tmpstr.substring(1);
}

删除jar时,需要同时删除spring容器中注册的bean

在jar包切换或删除时,需要将之前注册到spring容器的bean删除。spring容器的bean的删除操作和注册操作是相逆的过程,这里要注意使用同一个spring上下文。

代码如下:

/**
*删除jar包时需要在spring容器删除注入
*/
publicstaticvoiddelete()throwsException{
SetclassNameSet=DeployUtils.readJarFile(jarAddress);
URLClassLoaderurlClassLoader=newURLClassLoader(newURL[]{newURL(jarPath)},Thread.currentThread().getContextClassLoader());
for(StringclassName:classNameSet){
Classclazz=urlClassLoader.loadClass(className);
if(DeployUtils.isSpringBeanClass(clazz)){
defaultListableBeanFactory.removeBeanDefinition(DeployUtils.transformName(className));
}
}
}

测试

测试类手动模拟用户上传jar的功能。测试函数写了个死循环,一开始没有找到jar会抛出异常,捕获该异常并睡眠10秒。这时候可以把jar手动放到指定的目录下。

代码如下:

ApplicationContextapplicationContext=newClassPathXmlApplicationContext("applicationContext.xml");
DefaultListableBeanFactorydefaultListableBeanFactory=(DefaultListableBeanFactory)applicationContext.getAutowireCapableBeanFactory();
while(true){
try{
hotDeployWithReflect();
//hotDeployWithSpring();
//delete();
}catch(Exceptione){
e.printStackTrace();
Thread.sleep(1000*10);
}
}

-End-

审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 接口
    +关注

    关注

    33

    文章

    9450

    浏览量

    156165
  • 代码
    +关注

    关注

    30

    文章

    4942

    浏览量

    73160

原文标题:动态上传jar包热部署实战

文章出处:【微信号:AndroidPush,微信公众号:Android编程精选】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    尼日利亚MTN携手华为完成RuralCow全球首商用部署

    近日,尼日利亚MTN携手华为完成普惠联接RuralCow解决方案商用部署方案实现了“一盒子一站点”的极简创新突破,可助力偏远农村地区低成本、高效率地
    的头像 发表于 12-08 13:41 192次阅读

    电能质量在线监测装置自诊断功能的硬件层实时监测的冗余切换是如何实现的?

    硬件层冗余切换的核心是 通过专用切换电路 + 硬件触发信号 + 同步机制 ,实现主备模块(如电源、ADC、通信)的毫秒级无缝切换,全程不依赖复杂软件,仅通过硬件逻辑
    的头像 发表于 11-06 10:54 587次阅读

    功率器件设计基础(十四)----成像仪测温度概述

    摘要功率半导体热设计是实现IGBT、SiC高功率密度设计的基础,只有掌握功率半导体的设计基础知识和测试的基本技能,才能完成精确设计,提高功率器件的利用率,降低
    的头像 发表于 09-12 17:05 669次阅读
    功率器件<b class='flag-5'>热</b>设计基础(十四)----<b class='flag-5'>热</b>成像仪测温度概述

    HT 流畅过渡动画 × 场景切换实现方案

    在图扑 HT 项目中,尤其是复杂应用里,单一场景或图纸通常难以承载所有需求,因此在多个图纸或场景之间进行切换是一种常见的实现方式。本文将深入解析图扑 HT 项目中场景/图纸切换的核心实现
    的头像 发表于 09-03 14:49 604次阅读
    HT 流畅过渡动画 × 场景<b class='flag-5'>切换</b><b class='flag-5'>实现</b>方案

    深度解析:双卡切换的SIM卡电路设计原则与实现

    实现稳定的双卡切换功能,电路设计必须遵循严格的技术规范。本文系统梳理SIM卡接口的电气特性、时序要求及ESD防护策略,揭秘双卡切换背后的电
    的头像 发表于 07-17 16:14 603次阅读
    深度解析:双卡<b class='flag-5'>切换</b>的SIM卡电路设计原则与<b class='flag-5'>实现</b>!

    Context接口切换介绍

    ; isUpdatingConfigurations():Promise;Stage模型无对应接口系统环境变化时,应用不会重启,调用onConfigurationUpdated接口通知应用,
    发表于 06-06 06:12

    无人职守自动安装部署操作系统指南

    当组织有服务器需要部署linux系统时,需要通过网络方式安装结合自动应答文件,实现无人职守自动安装部署操作
    的头像 发表于 05-22 13:38 742次阅读
    无人职守自动安装<b class='flag-5'>部署</b>操作<b class='flag-5'>系统</b>指南

    POE交换机接口详解

    实现远程开关控制与亮度调节,节能高效。 PoE交换机接口与传统接口的对比 相较于传统接口,PoE交换机接口在布线、
    发表于 03-27 17:17

    Telkomsel与华为合作完成EasyAAU千站规模部署

    近日,印尼领先的电信运营商Telkomsel与华为合作,在2.3GHz频段完成千站规模的EasyAAU部署,成功打造巴厘岛5G City样板,逐步向雅加达扩展,为印尼5G网络建设树立了新的标杆,也为全球5G发展提供了宝贵经验。
    的头像 发表于 03-03 11:46 937次阅读

    行芯完成DeepSeek-R1大模型本地化部署

    近日,行芯正式宣布完成 DeepSeek-R1 大模型本地化部署实现在多场景、多产品中应用。解锁“芯”玩法,开启“芯”未来!
    的头像 发表于 02-24 15:17 1145次阅读

    添越智创基于 RK3588 开发板部署测试 DeepSeek 模型全攻略

    RKLLM-Toolkit 是一款专为大语言模型量化与转换设计的开发套件,提供简洁易用的 Python 接口,让开发者能轻松实现以下两大关键功能: 1.模型转换: 工具支持将部分特定格式的大语言模型,比如
    发表于 02-14 17:42

    ADS1243做一个多通道采集系统,AD前置滤波RC怎么设置?

    我现在要做一个多通道采集系统,需要采集3路K值偶,3路要轮流采集,使用ADS1243,输出频率为15hz,使用内部PGA,现在的问题是AD前置滤波RC我怎么设置?设置的依据是什么? 再我对通道
    发表于 02-07 06:56

    功率器件设计基础(十三)——使用系数Ψth(j-top)获取结温信息

    /前言/功率半导体热设计是实现IGBT、碳化硅SiC高功率密度的基础,只有掌握功率半导体的设计基础知识,才能完成精确设计,提高功率器件的利用率,降低
    的头像 发表于 01-20 17:33 1923次阅读
    功率器件<b class='flag-5'>热</b>设计基础(十三)——使用<b class='flag-5'>热</b>系数Ψth(j-top)获取结温信息

    功率器件设计基础(十一)——功率半导体器件的功率端子

    /前言/功率半导体热设计是实现IGBT、碳化硅SiC高功率密度的基础,只有掌握功率半导体的设计基础知识,才能完成精确设计,提高功率器件的利用率,降低
    的头像 发表于 01-06 17:05 1251次阅读
    功率器件<b class='flag-5'>热</b>设计基础(十一)——功率半导体器件的功率端子

    功率器件设计基础(八)——利用瞬态阻计算二极管浪涌电流

    /前言/功率半导体热设计是实现IGBT、碳化硅SiC高功率密度的基础,只有掌握功率半导体的设计基础知识,才能完成精确设计,提高功率器件的利用率,降低
    的头像 发表于 12-11 01:03 1086次阅读
    功率器件<b class='flag-5'>热</b>设计基础(八)——利用瞬态<b class='flag-5'>热</b>阻计算二极管浪涌电流