写点什么

热更新适配 ibatis 原理浅析

  • 2024-01-25
    北京
  • 本文字数:2553 字

    阅读完需:约 8 分钟

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

在研发过程中,每个研发同学在联调、自测阶段中总会频繁的去执行编译、构建、打包的动作,遇到比较大的项目,执行一套流程下来,往往需要 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 去重新加载代码


五、总结

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



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


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


作者:京东零售 张骞


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

用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
热更新适配ibatis原理浅析_京东科技开发者_InfoQ写作社区