热更新适配ibatis原理浅析

一、热更新解决了什么问题?

在研发过程中,每个研发同学在联调、自测阶段中总会频繁的去执行编译、构建、打包的动作,遇到比较大的项目,执行一套流程下来,往往需要3-10分钟左右,极大的降低了研发的速度,基于以上痛点,我们基于JAVA Agent技术开发出一套插件【藏经阁热更新插件 】,通过热更新方式,实现了**修改代码即时生效,**极大的降低研发的打包、发布时间,提升研发效率。目前这套插件已经兼容多个场景

二、ibatis如何进行热更新的?

热更新是什么?就是在目标JVM不停服的情况下,动态的更新一个class文件、xml文件,使程序的运行逻辑随之改变。比如加一行日志,执行热更新后就可以查看日志,修改sql语句就可以直接获取对应结果。

如果要实现修改ibatis框架中的配置文件怎么实现呢?

ibatis配置文件包含两个,一个是SqlMapConfig.xml,这个配置文件为我们提供了持久化所需的数据源配置,一个是sqlMapper.xml,这个配置文件定义了iBATIS- SQL映射语句,我们的目的是修改sqlMapper.xml中的sql语句,可以即时生效。在spring中,spring为我们提供了一个iBatis的工厂类,SqlMapClientFactoryBean,

ini 复制代码
<bean id="sqlMapClientFactoryBean" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">  
  <property name="dataSource" ref="masterDataSourcePool"/>  
  <property name="configLocation" value="classpath:sqlConfig.xml"/>  
  <property name="mappingLocations" value="classpath:sqlMapper.xml"/>
</bean>

一个jdos应用,一般是通过这个工厂类将ibatis与spring整合起来,所以在修改sqlMapper.xml文件后,同时需要重新加载这个bean。 热更新我们是通过dcevm来实现。dcevm可以热更新类,但也有一定的局限性,其中面临的一个问题是,被spring管理的bean和以及配置文件在初始化时候就被缓存好了,单纯的修改配置文件无法触发重新扫描。

所以我们需要在相关bean重新加载后清空springmvc缓存,重新触发扫描接口方法,进而实现相关bean的热加载。于是更新一个文件流程可以简化成如下流程:

在这个流程中,我们根据不同的场景去选择不同的插件plugin,通过plugin实现不同的监听方法。那么在具体的实现过程中,如果服务端收到很多变更配置文件,又如何来判断变更的文件哪些是ibatis配置信息呢?

通过了解ibatis的原理,结合agent的插桩技术,我们可以在JDos应用启动过程中,监听这个类(SqlMapClientFactoryBean)的加载事件,在这个类被加载的时候,写入一些处理方法,把ibatis的配置文件信息先保存一份,这样在更新的时候,我们可以通过缓存的路径来判断,是否是同一类型的配置。

我们发现SqlMapClient 接口主要定义了客户端的操作行为包括 select、insert、update、delete,"SqlMapExecutorDelegate" 这个类是执行代理类。这个类他耦合了用户端的执行操作行为和执行的环境,他持有执行操作的所需要的数据,同时提供着执行操作依赖的环境。其中有个状态mappedStatements,如果在每次更新文件后,不对他进行清空操作,修改的sql是不会生效的。而这个清空缓存方法,需要我们自己实现。这样在jdos应用启动的时候,我们可以增加如下流程:

三、ibatisPlugin 代码流程简介

接下来从代码视角,简单阐述整体流程

2.1 应用启动后,加载agent的jar包,首先会初始化插件pluginManager.getInstance().init(),扫描这个包路径

复制代码
 com.jd.plus.hot.deploy.core.plugin

下面的@Plugin注解信息,然后注册插件信息,其中就包含springPlugin、ibatisPlugin等插件,插件的方法上会通过@OnResourceFileEvent注解方式,在资源变更后反射调用该方法。进而更新不同的文件信息缓存,同时会根据不同的类触发事件,写入不同的缓存信息,根据事件@OnClassLoadEvent(classNameRegexp = "com.ibatis.sqlmap.engine.builder.xml.SqlMapConfigParser") 写入sqlMapConfig配置信息,根据事件SqlMapClientFactoryBean注册配置文件内容,根据事件"SqlMapExecutorDelegate"添加清空缓存方法clearMapperState

这里需要着重说一下@plugin插件。不同的框架去实现热加载,主要是通过插件体系实现。@plugin是用来声明插件的,一个插件具体包含如下几个部分

less 复制代码
@Init用来注解字段,类似Spring里的@Autowired,目前支持初始化的参数有:
1.PluginManager: 插件全局的相关配置在这个里面
2.Watcher: 监听器,可以通过监听器监听别的目录
3.ExecuteScheduler: 调度器,用来调度任务
4.HotswapTransformer: 用来转换类
5.PluginConfiguration:插件配置相关的逻辑在这个类里

@OnClassLoadEvent、@OnResourceLoadEvent用来注解方法,用来插桩,init方法获取配置文件,获取完成后,通过WatcherUtils.registerExtraClassPathListener来监听log4j配置文件的变动。配置文件变动后,再调用reload方法热加载。

2.2 agent监听文件,agent启动初始化一个监听器,使用的是NIO的fileSystems.getDefault().newWatchService 监听所有的文件资源,watcher启动一个线程,循环的从系统事件取出WatchEvent,放到dispatcher的队列中。

2.3 dispatcher 从队列中取出event,通过callListener通知监听者

2.4 IBatisPlugin.class中的监听方法,获取到 filter = ".*.xml" 的请求,通过路径比对,确定是否属于Ibatis的相关配置。

2.5 调用方法clearMapperState清空注册的文件缓存信息,重新写入新的变更文件。

2.6 通过XmlBeanRefreshCommand命令重新加载xml中的bean,刷新缓存,reloadBeanFromxml,完成热加载

四、相关技术

JAVA-Agent:简单来说,是就是通过Instrumentation API与虚拟机交互,在启动时配置相关的参数(-javaagent),其premain方法会在程序main方法执行之前被调用,此时大部分Java类都没有被加载,可以对类加载埋点(addTransformer)。同时实现 监听到类的变动-->然后调用Instrumentation#redefineClasses去重新加载代码

五、总结

本文主要是通过适配ibatis的热更新场景,抛砖引玉,分享一些热更新的思路。整体开发过程中,还遇到了各种复杂的场景,以下是【藏经阁热更新插件】的整体结构图

还没有使用插件的小伙伴,欢迎大家积极使用,并在评论区发表对插件的建议和想法。

技术达人们,还对哪些插件的原理更感兴趣呢?欢迎在评论区留言,我们将根据大家的反馈,积极推出更多的分享。

作者:京东零售 张骞

来源:京东云开发者社区 转载请注明来源

相关推荐
徐同保37 分钟前
tailwindcss暗色主题切换
开发语言·前端·javascript
生莫甲鲁浪戴1 小时前
Android Studio新手开发第二十七天
前端·javascript·android studio
细节控菜鸡3 小时前
【2025最新】ArcGIS for JS 实现随着时间变化而变化的热力图
开发语言·javascript·arcgis
拉不动的猪4 小时前
h5后台切换检测利用visibilitychange的缺点分析
前端·javascript·面试
桃子不吃李子4 小时前
nextTick的使用
前端·javascript·vue.js
Devil枫6 小时前
HarmonyOS鸿蒙应用:仓颉语言与JavaScript核心差异深度解析
开发语言·javascript·ecmascript
惺忪97986 小时前
回调函数的概念
开发语言·前端·javascript
前端 贾公子6 小时前
Element Plus组件v-loading在el-dialog组件上使用无效
前端·javascript·vue.js
天外飞雨道沧桑6 小时前
JS/CSS实现元素样式隔离
前端·javascript·css·人工智能·ai
qq_419854057 小时前
自定义组件(移动端下拉多选)中使用 v-model
前端·javascript·vue.js