核心思路是绕过默认类加载路径,用自定义加载器优先加载补丁类并隔离旧Jar同名类:重写loadClass方法跳过双亲委派,直接findClass查找独立补丁路径(如./patch/),按包名白名单控制热加载范围,确保问题类在AppClassLoader加载前被补丁加载器捕获,结合defineClass注入字节码、运行时替换Bean及静态引用、版本校验与异常回滚机制保障生效。

核心思路是绕过默认类加载路径,用自定义加载器优先加载补丁类,同时隔离旧 Jar 中的同名类。
明确补丁类的加载优先级
默认的应用程序类加载器(AppClassLoader)按 classpath 顺序扫描 jar,先出现的类胜出。热修复要打破这个顺序,必须让补丁类在类加载请求到达旧 jar 前就被捕获。
- 重写 loadClass(String name, boolean resolve) 方法,不走双亲委派,直接在 findClass 阶段查找补丁目录
- 补丁类路径需独立于 classpath,例如放在
./patch/或classpath:/hotfix/ - 只对特定包名(如
com.example.fix.*)启用热加载,其余委托父加载器,避免干扰核心类
确保旧 Jar 类不被重复加载或干扰
即使补丁已加载,若旧 jar 中的同名类已被 AppClassLoader 加载过,JVM 不会重新加载——因为类由加载器+全限定名唯一标识。所以关键不是“覆盖”,而是“不让它先加载”。
- 启动时禁用自动扫描含问题类的 jar(如通过
-Xbootclasspath/p:或java -cp排除) - 在应用初始化早期(如 Spring ContextRefreshedEvent 前)完成补丁类加载,并缓存 Class 实例
- 使用 defineClass 直接注入字节码,跳过文件系统查找,防止被外部 jar 干扰
补丁生效后替换运行时引用
仅加载新类还不够,原业务逻辑中持有的仍是旧类实例。需主动更新持有者。
- 对单例 Bean,通过 Spring 的
ConfigurableApplicationContext替换 BeanDefinition 并刷新指定 Bean - 对非 Spring 管理对象(如静态工具类),用反射修改其内部静态字段引用
- 补丁类中避免使用
static final常量,改用可变配置或方法调用,便于运行时更新
验证与兜底机制
生产环境不能依赖“加载成功即生效”,必须确认实际行为已变更。
- 在补丁类中加入版本标识字段(如
public static final String PATCH_VERSION = "20260519-1";),启动后打印校验 - 监控类加载日志:开启
-verbose:class,确认目标类由你的加载器而非 AppClassLoader 加载 - 设置超时回滚:若补丁加载后 30 秒内出现指定异常(如 NPE、NoSuchMethodError),自动卸载并触发 JVM 重启流程

















