数据测试 热更新适配 ibatis 原理浅析

京东云开发者 · 2024年01月25日 · 2382 次阅读

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

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

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

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

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

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

<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是用来声明插件的,一个插件具体包含如下几个部分


@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 去重新加载代码

image

五、总结

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

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

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

作者:京东零售 张骞

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

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册