
本文介绍如何在 Spring Boot 应用不重启的前提下,通过编程方式动态注册与注销 REST 端点,实现如 /old → /new 的实时路由切换,适用于灰度发布、A/B 测试或配置驱动的接口演进场景。
本文介绍如何在 spring boot 应用不重启的前提下,通过编程方式动态注册与注销 rest 端点,实现如 `/old` → `/new` 的实时路由切换,适用于灰度发布、a/b 测试或配置驱动的接口演进场景。
Spring Boot 默认将 @RequestMapping、@GetMapping 等注解在启动时静态注册到 RequestMappingHandlerMapping 中,因此常规方式(如属性占位符 ${dynamic.endpoint} 或路径变量)无法真正“替换”已注册的端点路径。若需在运行时彻底移除旧路径并启用新路径(例如将 GET /api/testing/old 切换为 GET /api/testing/new),必须直接操作 Spring MVC 的底层映射注册器。
以下是一个完整、可立即运行的实现方案:
✅ 核心原理
利用 RequestMappingHandlerMapping 提供的 registerMapping() 和 unregisterMapping() 方法,配合反射获取目标处理方法(Method),实现端点的动态挂载与卸载。
✅ 示例代码(推荐用于开发/测试环境)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.lang.reflect.Method;
@RestController
@RequestMapping("/api/testing")
public class TestingController {
@Autowired
private RequestMappingHandlerMapping mappingHandler;
/**
* 触发端点切换:停用 /old,启用 /new
*/
@GetMapping("/switch")
public String switchEndpoint() {
try {
// 1. 注销旧端点:/api/testing/old
RequestMappingInfo oldInfo = RequestMappingInfo.paths("/api/testing/old")
.methods(RequestMethod.GET)
.build();
mappingHandler.unregisterMapping(oldInfo);
// 2. 注册新端点:/api/testing/new,复用 oldHandler 方法逻辑
RequestMappingInfo newInfo = RequestMappingInfo.paths("/api/testing/new")
.methods(RequestMethod.GET)
.build();
Method handlerMethod = this.getClass().getDeclaredMethod("oldHandler");
mappingHandler.registerMapping(newInfo, this, handlerMethod);
return "✅ Switched: /old → /new";
} catch (Exception e) {
return "❌ Switch failed: " + e.getMessage();
}
}
/**
* 原始业务逻辑(复用给新端点)
*/
@GetMapping("/old")
public String oldHandler() {
return "? Response from OLD endpoint";
}
/**
* 注意:/new 不需要显式声明 @GetMapping,
* 它由 registerMapping 动态绑定,因此不会在启动时暴露
*/
}
⚠️ 关键注意事项
-
线程安全:
RequestMappingHandlerMapping的注册/注销操作非原子性,高并发调用/switch可能引发竞态。生产环境建议加锁(如synchronized或ReentrantLock)或引入幂等控制。 -
方法可见性:被反射调用的处理方法(如
oldHandler)需为public或protected;若为private,需调用setAccessible(true)(不推荐,破坏封装)。 -
路径唯一性:确保
RequestMappingInfo构建参数(路径、HTTP 方法、consumes/produces 等)与原注册完全一致,否则unregisterMapping可能失败。 -
Bean 生命周期:
RequestMappingHandlerMapping是单例 Bean,但频繁注册/注销可能影响性能,不建议高频调用(如每秒多次)。 -
生产慎用:该方案绕过 Spring 的标准组件生命周期管理,调试困难且易引发内存泄漏(如未清理旧映射引用)。更稳健的替代方案包括:
- 使用网关层(如 Spring Cloud Gateway)做路由转发;
- 基于请求头/参数路由至同一方法内部分支(
if/else); - 结合
@ConditionalOnProperty+ 配置刷新(需@RefreshScope+ Actuator/actuator/refresh)。
✅ 验证流程
- 启动应用,访问
GET http://localhost:8080/api/testing/old→ 返回"? Response from OLD endpoint" - 调用
GET http://localhost:8080/api/testing/switch→ 返回"✅ Switched: /old → /new" - 再次访问
/old→404 Not Found - 访问
/new→ 同样返回"? Response from OLD endpoint"(逻辑复用)
通过此机制,你可在零停机前提下完成端点语义迁移,为灵活的 API 治理提供底层支撑。











