Java 系列 | 远程热部署在美团的落地实践
Java 系列 | 远程热部署在美团的落地实践
作者:凯哥 占峰 李晗 龚炎 程晓 玉龙
Sonic 是美团内部研发设计的一款用于热部署的IDEA 插件,本文其实现原理及落
地的一些技术细节。在阅读本文之前,建议大家先熟悉一下Spring 源码、Spring
MVC 源码、Spring Boot 源码、Agent 字节码增强、Javassist、Classloader 等相
关知识。
1. 前言
1.1 什么是热部署
所谓热部署,就是在应用正在运行时升级软件,却不需要重新启动应用。对于Java
应用程序来说,热部署就是在运行时更新Java 类文件,同时触发Spring 以及其他
常用第三方框架的一系列重新加载的过程。在这个过程中不需要重新启动,并且修改
的代码实时生效,好比是战斗机在空中完成加油,不需要战斗机熄火降落,一系列操
作都在“运行”状态来完成。
1.2 为什么我们需要热部署
据了解,美团内部很多工程师每天本地重启服务高达5~12 次,单次大概3~8 分钟,
每天向Cargo(美团内部测试环境管理工具)部署3~5 次,单次时长20~45 分钟,
部署频繁频次高、耗时长,严重影响了系统上线的效率。而插件提供的本地和远程热
部署功能,可让将代码变更“秒级”生效。一般而言,开发者日常工作主要分为开发
自测和联调两个场景,下面将分别介绍热部署在每个场景中发挥的作用。

1.2.1 开发自测场景
一般来讲,在用插件之前,开发者修改完代码还需等待3~8 分钟启动时间,然后手
动构造请求或协调上游发请求,耗时且费力。在使用完热部署插件后,修改完代码可
以一键增量部署,让变更“秒级”生效,能够做到快速自测。而对于那些无法本地启
动项目,也可以通过远程热部署功能使代码变更“秒级”生效。

图 2
1.2.2 联调场景
通常情况下,在使用插件之前,开发者修改代码经过20~35 分钟的漫长部署,需要
联系上游联调开发者发起请求,一直要等到远程服务器查看日志,才能确认代码生
效。在使用热部署插件之后,开发者修改代码远程热部署能够秒级(2~10s)生效,
开发者直接发起服务调用,可以节省大量的碎片化时间(热部署插件还具备流量回放、
远程调用、远程反编译等功能,可配合进行使用)。

所以,热部署插件希望解决的痛点是:在可控的条件内,帮助开发者减少频繁编译部
署的次数,节省碎片化的时间。最终为开发者每天节约出一定量的编码时间。
1.3 热部署难在哪
为什么业界目前没有好用的开源工具?因为热部署不等同于热重启,像Tomcat 或
者Spring Boot DevTools 此类热重启模式需要重新加载项目,性能较差。增量热
部署难度较大,需要兼容常用的中间件版本,需要深入启动销毁加载流程。以美团
为例,我们需要对JPDA(Java Platform Debugger Architecture)、Java Agent、
ASM 字节码增强、Classloader、Spring 框架、Spring Boot 框架、MyBatis 框
架、Mtthrift(美团RPC 框架)、Zebra(美团持久层框架)、Pigeon(美团RPC 框
架),MDP(美团快速开发框架)、XFrame(美团快速开发脚手架)、Crane(美团分
布式任务调度框架)等众多框架和技术原理深入了解才能做到全面的兼容和支持。另
外,还需要IDEA 插件开发能力,形成整体的产品解决方案闭环,美团的热部署插件
Sonic 正是在这种背景下应运而生。

图 4
1.4 Sonic 可以做什么
Sonic 是美团内部研发设计的一款IDEA 插件,旨在通过低代码开发辅助远程/ 本地
热部署,解决Coding、单测编写执行、自测联调等阶段的效率问题,提高开发者的
编码产出效率。数据统计表明,开发者日常大概有35% 时间用于编码的产出。如果
想提高研发效率,要么扩大编码产出的时间占比,要么提高编码阶段的产出效率,而
Sonic 则聚焦提高编码阶段的产出效率。
目前,使用Sonic 热部署可以解决大部分代码重复构建的问题。Sonic 可以使用户
在本地编写代码一键部署到远程环境,修改代码、部署、联调请求、查看日志,循环
反复。如果不考虑代码修改时间,通常一个循环需要20~35 分钟,而使用Sonic 可
以把整个时长缩短至5~10 秒,而且能够给开发者带来高效沉浸式的开发体验。在实
际编码工作中,多文件修改是家常便饭,Sonic 对多文件的热部署能力尤为突出,它
可以通过依赖分析等手段来对多文件批量进行远程热部署,并且支持Spring Bean
Class、普通Class、Spring XML、MyBatis XML 等多类型文件混合热部署。
那么跟业界现有的产品相比,Sonic 有哪些优劣势呢?下面我们尝试给出几种产品的
对比,仅供大家参考:

上表未把Sofa-Ark、Osgi、Arthas 列举,此类属于插件化、模块化应用框架,以
及Java 在线诊断工具,核心能力非热部署。值得注意的是,Spring Boot DevTools
只能应用在Spring Boot 项目中,并且它不是增量热部署,而是通过Classloader
迭代的方式重启项目,对大项目而言,性能上是无法接受的。虽然,JRebel 支持三
方插件较多,生态庞大,但是对于国产的插件不支持,例如FastJson 等,同时它还
存在远程热部署配置局限,对于公司内部的中间件需要个性化开发,并且是商业软
件,整体的使用成本较高。
1.5 Sonic 远程热部署落地推广的实践经验
相信大家都知道,对于技术产品的推广,尤其是开发、测试阶段使用的产品,由于远
离线上环境,推动力、执行力、产品功能闭环能否做好,是决定着该产品是否能在企
业内部落地并得到大多数人认可的重要的一环。此外,因为很多开发者在开发、测试
阶段已逐渐形成了“固化动作”,如何改变这些用户的行为,让他们拥抱新产品,也
是Sonic 面临的艰巨挑战之一。我们从主动沟通、零成本(或极低成本)快速接入、
自动化脚本,以及产品自动诊断、收集反馈等方向出发,践行出了四条原则。

2. 整体设计方案
2.1 Sonic 结构
Sonic 插件由4 大部分组成,包括脚本端、插件端、Agent 端,以及Sonic 服务端。
脚本端负责自动化构建Sonic 启动参数、服务启动等集成工作;IDEA 插件端集成环境为开发者提供更便捷的热部署服务;Agent 端随项目启动负责热部署的功能实现;
服务端则负责收集热部署信息、失败上报等统计工作。如下图所示:

图 7
2.2 走进Agent
2.2.1 Instrumentation 类常用API
public interface Instrumentation {
// 增加一个Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数
canRetransform 设置是否允许重新转换。
void addTransformer(ClassFileTransformer transformer, boolean
canRetransform);
// 在类加载之前,重新定义 Class 文件,ClassDefinition 表示对一个类新的定义,
// 如果在类加载之后,需要使用 retransformClasses 方法重新定义。
addTransformer 方法配置之后,后续的类加载都会被Transformer 拦截。
// 对于已经加载过的类,可以执行retransformClasses 来重新触发这个
Transformer 的拦截。类加载的字节码被修改后,除非再次被retransform,否则不会恢复。
void addTransformer(ClassFileTransformer transformer);
// 删除一个类转换器
boolean removeTransformer(ClassFileTransformer transformer);
// 是否允许对class retransform
boolean isRetransformClassesSupported();
// 在类加载之后,重新定义 Class。这个很重要,该方法是1.6 之后加入的,事实上,
该方法是 update 了一个类。
void retransformClasses(Class<?>... classes) throws
UnmodifiableClassException;
// 是否允许对class 重新定义
boolean isRedefineClassesSupported();
// 此方法用于替换类的定义,而不引用现有的类文件字节,就像从源代码重新编译以进行
修复和继续调试时所做的那样。
// 在要转换现有类文件字节的地方(例如在字节码插装中),应该使用
retransformClasses。
// 该方法可以修改方法体、常量池和属性值,但不能新增、删除、重命名属性或方法,也
不能修改方法的签名
void redefineClasses(ClassDefinition... definitions) throws
ClassNotFoundException, UnmodifiableClassException;
// 获取已经被JVM 加载的class,有className 可能重复(可能存在多个
classloader)
@SuppressWarnings(“rawtypes”)
Class[] getAllLoadedClasses();
}
2.2.2 Instrument 简介
Instrument 的底层实现依赖于JVMTI(JVM Tool Interface),它是JVM 暴露出来
的一些供用户扩展的接口集合,JVMTI 是基于事件驱动的,JVM 每执行到一定的
逻辑就会调用一些事件的回调接口(如果存在),这些接口可以供开发者去扩展自己
的逻辑。
JVMTIAgent 是一个利用JVMTI 暴露出来的接口提供了代理启动时加载(Agent
On Load)、代理通过Attach 形式加载(Agent On Attach)和代理卸载(Agent
On Unload)功能的动态库。而Instrument Agent 可以理解为一类JVMTIAgent
动态库,别名是JPLISAgent(Java Programming Language Instrumentation
Services Agent),也就是专门为Java 语言编写的插桩服务提供支持的代理。
2.2.3 启动时和运行时加载Instrument Agent 过程

图 8
2.3 那些年JVM 和HotSwap 之间的“相爱相杀”
围绕着Method Body 的HotSwap JVM 一直在进行改进。从1.4 版本开始,JPDA
引入HotSwap 机制(JPDA Enhancements),实现Debug 时的Method Body 的
动态性。大家可参考文档:enhancements1.4 。
1.5 版本开始通过JVMTI 实现的java.lang.instrument(Java Platform SE 8)的
Premain 方式,实现Agent 方式的动态性(JVM 启动时指定Agent)。大家可参考
文档:package-summary。
1.6 版本又增加Agentmain 方式,实现运行时动态性(通过The Attach API 绑定
到具体VM)。大家可参考文档:package-summary 。基本实现是通过JVMTI 的
retransformClass/redefineClass 进行method、body 级的字节码更新,ASM、
CGLib 基本都是围绕这些在做动态性。但是针对Class 的HotSwap 一直没有动作
(比如Class 添加method、添加field、修改继承关系等等),为什么会这样呢?因为
复杂度过高,且没有很高的回报。
2.4 Sonic 如何解决Instrumentation 的局限性
由于JVM 限制,JDK 7 和JDK 8 都不允许改类结构,比如新增字段,新增方法和修
改类的父类等,这对于Spring 项目来说是致命的。比如开发同学想修改一个Spring
Bean,新增一个@Autowired 字段,此类场景在实际应用时很多,所以Sonic 对此
类场景的支持必不可少。
那么,具体是如何做到的呢?这里要提一下“大名鼎鼎” 的Dcevm。Dcevm
(DynamicCode Evolution Virtual Machine)是Java Hostspot 的补丁(严格上来
说是修改),允许(并非无限制)在运行环境下修改加载的类文件。当前虚拟机只允许
修改方法体(Method,Body),而Decvm 可以增加、删除类属性、方法,甚至改变
一个类的父类,Dcevm 是一个开源项目,遵从GPL 2.0 协议。更多关于Dcevm 的
介绍,大家可以参考:Wuerthinger10a 以及GitHub Decvm。 https://ssw.jku.at/Research/Papers/Wuerthinger10a/Wuerthinger10a.pdf
值得一提的是,在美团内部,针对Dcevm 的安装,Sonic 已经打通HULK,集成发
布镜像即可完成(本地热部署可结合插件功能实现一键安装热部署环境)。
3. Sonic 热部署技术解析
3.1 Sonic 整体架构模型
上一章节我们主要介绍了Sonic 的组成。下图详细介绍了Sonic 在运行期间各个组
成部分的工作职责,由它们形成一整套完备的技术产品落地闭环方案:

3.2 Sonic 功能流转
Sonic 通过NIO 监听本地文件变更,触发文件变更事件,例如Class 新增、Class
修改、Spring Bean 重载等事件流程。下图展示了一次热部署单个文件的生命周期:

3.3 文件监听
Sonic 首先会在本地和远程预定义两个目录,/var/tmp/sonic/extraClass
path 和/var/tmp/sonic/classes。extraClasspath 为Sonic 自定义的拓展
Classpath URL,classes 为Sonic 监听的目录,当有文件变更时,通过IDEA 插
件来部署到远程/ 本地,触发Agent 的监听目录,来继续下面的热加载逻辑:
图 11

为什么Sonic 不直接替换用户ClassPath 下面的资源文件呢?因为考虑到业务方
WAR 包的API 项目、Spring Boot、Tomcat 项目、Jetty 项目等,都是以JAR 包
来启动的,这样是无法直接修改用户的Class 文件的。即使是用户项目可以修改,直
接操作用户的Class,也会带来一系列的安全问题。
所以,Sonic 采用拓展ClassPath URL 路径来实现文件的修改和新增。并且存在这
么一种场景,多个业务侧的项目引入相同的JAR 包,在JAR 里面配置MyBatis 的
XML 和注解。在此类情况下,Sonic 没有办法直接来修改JAR 包中源文件,通过拓
展路径的方式可以不需要关注JAR 包,来修改JAR 包中某一文件和XML。同理,
采用此类方法可以进行整个JAR 包的热替换。下面我们简单介绍一下Sonic 的核心
监听器,如下图所示:

图 12
3.4 JVM Class Reload
JVM 的字节码批量重载逻辑,通过新的字节码二进制流和旧的Class 对象生成
ClassDefinition 定义,instrumentation.redefineClasses(definitions),来触发JVM 重载,重载过后将触发初始化时Spring 插件注册的Transfrom。接下来,我
们简单讲解一下Spring 是怎么重载的。
新增class Sonic 如何保证可以加载到Classloader 上下文中?由于项目在远程执
行,所以运行环境复杂,有可能是JAR 包方式启动(Spring Boot),也有可能是普
通项目,也有可能是War Web 项目,针对此类情况Sonic 做了一层Classloader
URL 拓展。

图 13
User ClassLoader 是框架自定义的ClassLoader 统称,例如Jetty 项目是We
bAppclassLoader。其中Urlclasspath 为当前项目的lib 文件件下,例如Spring
Boot 项目也是从当前项目BOOT-INF/lib/ 路径中加载CLass 等等,不同框架的
自定义位置稍有不同。所以针对此类情况,Agent 必须拿到用户的自定义Class
loader,如果是常规方式启动的,比如普通Spring XML 项目,借助Plus(美团内
部服务发布平台)发布,此类没有自定义Classloader,是默认AppClassLoader,
所以Agent 在用户项目启动过程中,借助字节码增强的方式来获取到真正的用户
Classloader。
https://mp.weixin.qq.com/s/dE-lneZE85SAfhZLyUeLZw
1 Sonic结构

2 java agent
见3.7
3 Sonic如何解决Instrumentation的局限性
由于JVM限制,JDK 7和JDK 8都不允许改类结构,比如新增字段,新增方法和修改类的父类等,这对于Spring项目来说是致命的。比如开发同学想修改一个Spring Bean,新增一个@Autowired字段,此类场景在实际应用时很多,所以Sonic对此类场景的支持必不可少。
这里要提一下“大名鼎鼎”的Dcevm。Dcevm(DynamicCode Evolution Virtual Machine)是Java Hostspot的补丁(严格上来说是修改),允许(并非无限制)在运行环境下修改加载的类文件。当前虚拟机只允许修改方法体(Method,Body),而Decvm可以增加、删除类属性、方法,甚至改变一个类的父类,Dcevm是一个开源项目,遵从GPL 2.0协议。更多关于Dcevm的介绍,大家可以参考:Wuerthinger10a以及GitHub Decvm。
4 Sonic整体架构模型
下图详细介绍了Sonic在运行期间各个组成部分的工作职责,由它们形成一整套完备的技术产品落地闭环方案:
图 9
4.1 Sonic功能流转
Sonic通过NIO监听本地文件变更,触发文件变更事件,例如Class新增、Class修改、Spring Bean重载等事件流程。下图展示了一次热部署单个文件的生命周期:
图 10
4.2 文件监听
Sonic首先会在本地和远程预定义两个目录,/var/tmp/sonic/extraClasspath和/var/tmp/sonic/classes。extraClasspath为Sonic自定义的拓展Classpath URL,classes为Sonic监听的目录,当有文件变更时,通过IDEA插件来部署到远程/本地,触发Agent的监听目录,来继续下面的热加载逻辑:
图 11
为什么Sonic不直接替换用户ClassPath下面的资源文件呢?因为考虑到业务方WAR包的API项目、Spring Boot、Tomcat项目、Jetty项目等,都是以JAR包来启动的,这样是无法直接修改用户的Class文件的。即使是用户项目可以修改,直接操作用户的Class,也会带来一系列的安全问题。
所以,Sonic采用拓展ClassPath URL路径来实现文件的修改和新增。并且存在这么一种场景,多个业务侧的项目引入相同的JAR包,在JAR里面配置MyBatis的XML和注解。在此类情况下,Sonic没有办法直接来修改JAR包中源文件,通过拓展路径的方式可以不需要关注JAR包,来修改JAR包中某一文件和XML。同理,采用此类方法可以进行整个JAR包的热替换。下面我们简单介绍一下Sonic的核心监听器,如下图所示:
图 12
4.3 JVM Class Reload
JVM的字节码批量重载逻辑,通过新的字节码二进制流和旧的Class对象生成ClassDefinition定义,instrumentation.redefineClasses(definitions),来触发JVM重载,重载过后将触发初始化时Spring插件注册的Transfrom。接下来,我们简单讲解一下Spring是怎么重载的。
新增class Sonic如何保证可以加载到Classloader上下文中?由于项目在远程执行,所以运行环境复杂,有可能是JAR包方式启动(Spring Boot),也有可能是普通项目,也有可能是War Web项目,针对此类情况Sonic做了一层Classloader URL拓展。
图 13
User ClassLoader是框架自定义的ClassLoader统称,例如Jetty项目是WebAppclassLoader。其中Urlclasspath为当前项目的lib文件件下,例如Spring Boot项目也是从当前项目BOOT-INF/lib/路径中加载CLass等等,不同框架的自定义位置稍有不同。所以针对此类情况,Agent必须拿到用户的自定义Classloader,如果是常规方式启动的,比如普通Spring XML项目,借助Plus(美团内部服务发布平台)发布,此类没有自定义Classloader,是默认AppClassLoader,所以Agent在用户项目启动过程中,借助字节码增强的方式来获取到真正的用户Classloader。

图 14
找到用户使用的子Classloader之后,通过反射的方式来获取Classloader中的元素Classpath,其中ClassPath中的URL就是当前项目加载Class时需要的所有运行时Class环境,并且包括三方的JAR包依赖等。
Sonic获取到URL数组,把Sonic自定义的拓展Classpath目录加入到URL数组首位,这样当有新增Class时,Sonic只需要将Class文件复制到拓展Classpath对应的包目录下面即可,当有其他Bean依赖新增的Class时,会从当前目录下面查找类文件。
为什么不直接对Appclassloader进行加强?而是对框架的自定义Classloader进行加强?

图 15
考虑这样一个场景,框架自定义类加载器中有ClassA,此时用户新增ClassB需要热加载,B Class里面有A的引用关系,如果增强AppClassLoader,初始化B实例时ClassLoader。loadclass首先从UserClassLoader开始加载ClassB的字节码,依靠双亲委派原则,B被Appclassloader加载,因为B依赖类A,所以当前AppClassLoader加载B一定是加载不到的,此时会抛出ClassNotFoundException异常。所以对类加载器拓展,一定要拓展最上层的类加载器,这样才会达到使用者想要的效果。
4.2 Spring Bean重载
Spring Bean Reload过程中,Bean的销毁和重启流程,主要内容如下图展示:
图 16
首先当修改Java Class D时,通过Spring ClasspathScan扫描校验当前修改的Bean是否Sprin Bean(注解校验),然后触发销毁流程(BeanDefinitionRegistry.removeBeanDefinition),此方法会将当前Spring上下文中的Bean D和依赖Spring Bean D的Bean C一并销毁,但是作用范围仅仅在当前Spring上下文。如果C被子上下文中的Bean B依赖,就无法更新子上下文中的依赖关系,当有系统请求时,Bean B中关联的Bean C还是热部署之前的对象,所以热部署失败。
因此,在Spring初始化过程中,需要维护父子上下文的对应关系,当子上下文变时若变更范围涉及到Bean B时,需要重新更新子上下文中的依赖关系,当有多上下文关联时需要维护多上下文环境,且当前上下文环境入口需要Reload。这里的入口是指:Spring MVC Controller、Mthrift和Pigeon,对不同的流量入口,采用不同的Reload策略。RPC框架入口主要操作为解绑注册中心、重新注册、重新加载启动流程等等,对Spring MVC Controller,主要是解绑和注册URL Mappping来实现流量入口类的变化切换。
4.3 Spring XML重载
当用户修改/新增Spring XML时,需要对XML中所有Bean进行重载。
图 17
重新Reload之后,将Spring销毁后重启。需要注意的是:XML修改方式改动较大,可能涉及到全局的AOP的配置以及前置和后置处理器相关的内容,影响范围为全局,所以目前只放开普通的XML Bean标签的新增/修改,其他能力酌情逐步放开。
4.3 MyBatis 热部署
Spring MyBatis热部署的主要处理流程是在启动期间获取所有Configuration路径,并维护它和Spring Context的对应关系,在热部署Class、XML时去匹配Configuration,从而重新加载Configuration以达到热部署的目的。
图 18
5 总结
4.1 热部署功能一览
上一章节主要讲述了Spring Bean、Spring MVC、MyBatis的重载流程,Sonic还支持其它常用的开发框架,丰富的框架支持和兼容能力是Sonic的基石,下面列举一些Sonic支持的常用的第三方框架:
图19 美团内部框架以及常用开源框架
截止目前,Sonic已经支持绝大部分常用第三方框架的热加载,常规业务开发几乎无需重启服务。并且在美团内部的成功率已经高达99.9%以上,真正地让热部署来代替常规部署构建成为一种可能。
4.2 IDE插件集成
Sonic也提供了功能强大的IDEA插件,让用户进行沉浸式开发,远程热部署也变得更加便利。

图 14
找到用户使用的子Classloader 之后,通过反射的方式来获取Classloader 中的元
素Classpath,其中ClassPath 中的URL 就是当前项目加载Class 时需要的所有
运行时Class 环境,并且包括三方的JAR 包依赖等。
Sonic 获取到URL 数组,把Sonic 自定义的拓展Classpath 目录加入到URL 数组
首位,这样当有新增Class 时,Sonic 只需要将Class 文件复制到拓展Classpath
对应的包目录下面即可,当有其他Bean 依赖新增的Class 时,会从当前目录下面查
找类文件。
为什么不直接对Appclassloader 进行加强?而是对框架的自定义Classloader 进行
加强?

图 15
考虑这样一个场景,框架自定义类加载器中有ClassA,此时用户新增ClassB 需要
热加载,B Class 里面有A 的引用关系,如果增强AppClassLoader,初始化B 实
例时ClassLoader。loadclass 首先从UserClassLoader 开始加载ClassB 的字
节码,依靠双亲委派原则,B 被Appclassloader 加载,因为B 依赖类A,所以当前
AppClassLoader 加载B 一定是加载不到的,此时会抛出ClassNotFoundExcep
tion 异常。所以对类加载器拓展,一定要拓展最上层的类加载器,这样才会达到使用
者想要的效果。
3.5 Spring Bean 重载
Spring Bean Reload 过程中,Bean 的销毁和重启流程,主要内容如下图展示:

图 16
首先当修改Java Class D 时,通过Spring ClasspathScan 扫描校验当前修改的
Bean 是否Sprin Bean(注解校验),然后触发销毁流程(BeanDefinitionRegistry.
removeBeanDefinition),此方法会将当前Spring 上下文中的Bean D 和依赖
Spring Bean D 的Bean C 一并销毁,但是作用范围仅仅在当前Spring 上下文。如
果C 被子上下文中的Bean B 依赖,就无法更新子上下文中的依赖关系,当有系统
请求时,Bean B 中关联的Bean C 还是热部署之前的对象,所以热部署失败。
因此,在Spring 初始化过程中,需要维护父子上下文的对应关系,当子上下文变时
后端 < 709
若变更范围涉及到Bean B 时,需要重新更新子上下文中的依赖关系,当有多上下文
关联时需要维护多上下文环境,且当前上下文环境入口需要Reload。这里的入口是
指:Spring MVC Controller、Mthrift 和Pigeon,对不同的流量入口,采用不同的
Reload 策略。RPC 框架入口主要操作为解绑注册中心、重新注册、重新加载启动流
程等等,对Spring MVC Controller,主要是解绑和注册URL Mappping 来实现流
量入口类的变化切换。
3.6 Spring XML 重载
当用户修改/ 新增Spring XML 时,需要对XML 中所有Bean 进行重载。

图 17
重新Reload 之后,将Spring 销毁后重启。需要注意的是:XML 修改方式改动较
大,可能涉及到全局的AOP 的配置以及前置和后置处理器相关的内容,影响范围
为全局,所以目前只放开普通的XML Bean 标签的新增/ 修改,其他能力酌情逐步
放开。
3.7 MyBatis 热部署
Spring MyBatis 热部署的主要处理流程是在启动期间获取所有Configuration 路
710 > 2022年美团技术年货
径,并维护它和Spring Context 的对应关系,在热部署Class、XML 时去匹配
Configuration,从而重新加载Configuration 以达到热部署的目的。

图 18
4. 总结
4.1 热部署功能一览
上一章节主要讲述了Spring Bean、Spring MVC、MyBatis 的重载流程,Sonic 还
支持其它常用的开发框架,丰富的框架支持和兼容能力是Sonic 的基石,下面列举一
些Sonic 支持的常用的第三方框架:

图19 美团内部框架以及常用开源框架
截止目前,Sonic 已经支持绝大部分常用第三方框架的热加载,常规业务开发几乎无
需重启服务。并且在美团内部的成功率已经高达99.9% 以上,真正地让热部署来代
替常规部署构建成为一种可能。
4.2 IDE 插件集成
Sonic 也提供了功能强大的IDEA 插件,让用户进行沉浸式开发,远程热部署也变得
更加便利。

4.3 推广使用情况
截止到发稿时,Sonic 在美团使用人数3000+,应用项目数量2000+。该项目还获
得了美团内部2020 年下半年到家研发平台“最佳效率团队”奖。
5. 作者简介
凯哥、占峰、李晗、龚炎、程骁、玉龙等,均来自美团/ 到家研发平台。
6. 参考文章
[1] 基于Javassist 和Javaagent 实现动态切面
[2] Spring MVC 源码解析
[3] Spring IOC 源码解析
[4] MyBatis 源码解析
[5] Spring Boot 源码解析
[6] Spring AOP 源码解析
[7] Spring 事务源码解析
[8] Cglib 源码解析
[9] JDK Proxy 源码解析
[10] Dcevm 简介
[11] 字节码增强技术探索
[12] Javassist API

浙公网安备 33010602011771号