Spring Boot 3.3中替代RestControllerEndpoint实现代理Actuator端点
在Spring Boot 3.3.0中替代@RestControllerEndpoint实现Actuator代理端点
之前在Spring Boot 3.2.x环境下,我用WebFlux实现了一个基于@RestControllerEndpoint的自定义Actuator端点,用于将hal-browser的请求转发到实际API,通过这个代理实现从Actuator入口访问API,该方案运行多年正常。但Spring Boot 3.3.0中@RestControllerEndpoint被弃用,改用@WebEndpoint后出现「Missing parameters: exchange」错误。
原(Spring Boot 3.2.x可用)代码
@Component @RestControllerEndpoint(id = "api", enableByDefault = true) class ApiProxyEndpoint { @Value("\${management.endpoints.web.base-path}") lateinit var managementPath: String @Autowired lateinit var webHandler: WebHandler @RequestMapping("**") // 匹配<management-path>/api/下的所有路径片段 fun apiProxyEndpoint(exchange: ServerWebExchange): Mono<Void> { val originalPath = exchange.request.path.toString() val apiPathWithoutManagementPath = originalPath.substringAfter(managementPath) val updatedRequest = exchange.request.mutate().contextPath("/").path(apiPathWithoutManagementPath).header("X-Api-RootPath", managementPath).build() val updatedExchange = exchange.mutate().request(updatedRequest).build() return webHandler.handle(updatedExchange) } }
尝试的Spring Boot 3.3.0代码
@Component @WebEndpoint(id = "api", enableByDefault = true) class ApiProxyEndpoint { @Value("\${management.endpoints.web.base-path}") lateinit var managementPath: String @Autowired lateinit var webHandler: WebHandler @WriteOperation @ReadOperation @DeleteOperation fun apiProxyEndpoint(exchange: ServerWebExchange): Mono<Void> { val originalPath = exchange.request.path.toString() val apiPathWithoutManagementPath = originalPath.substringAfter(managementPath) val updatedRequest = exchange.request.mutate().contextPath("/").path(apiPathWithoutManagementPath).header("X-Api-RootPath", managementPath).build() val updatedExchange = exchange.mutate().request(updatedRequest).build() return webHandler.handle(updatedExchange) } }
报错信息
{ "timestamp": "2024-06-12T10:23:17.366+00:00", "status": 400, "error": "Bad Request", "requestId": "efc7c7b9-4", "message": "Missing parameters: exchange" }
解决方案
问题根源
@WebEndpoint是用于构建标准化的Actuator端点(支持JMX/HTTP),它的操作方法(@ReadOperation等)依赖请求参数、路径变量的绑定注入,不能直接接收ServerWebExchange对象,这就是报错的原因。而原@RestControllerEndpoint本质是特殊的@RestController,天然支持Web请求上下文的处理。
替代方案1:使用@RestController配合Actuator路径
直接用普通的@RestController映射到Actuator基础路径下的/api/**,保留原有的代理逻辑:
@Component @RestController @RequestMapping("\${management.endpoints.web.base-path}/api/**") class ApiProxyEndpoint { @Value("\${management.endpoints.web.base-path}") lateinit var managementPath: String @Autowired lateinit var webHandler: WebHandler @RequestMapping fun apiProxyEndpoint(exchange: ServerWebExchange): Mono<Void> { val originalPath = exchange.request.path.toString() val apiPathWithoutManagementPath = originalPath.substringAfter(managementPath) val updatedRequest = exchange.request.mutate() .contextPath("/") .path(apiPathWithoutManagementPath) .header("X-Api-RootPath", managementPath) .build() val updatedExchange = exchange.mutate().request(updatedRequest).build() return webHandler.handle(updatedExchange) } }
若项目启用了Spring Security,需确保Actuator的安全配置允许访问/api/**路径。
替代方案2:使用RouterFunction实现路由转发
在WebFlux环境下,也可以用函数式路由实现代理逻辑:
@Component class ApiProxyRouter( @Value("\${management.endpoints.web.base-path}") private val managementPath: String, private val webHandler: WebHandler ) { @Bean fun apiProxyRoute(): RouterFunction<ServerResponse> { return RouterFunctions.route(RequestPredicates.path("\${management.endpoints.web.base-path}/api/**")) { exchange -> val originalPath = exchange.request.path.toString() val apiPathWithoutManagementPath = originalPath.substringAfter(managementPath) val updatedRequest = exchange.request.mutate() .contextPath("/") .path(apiPathWithoutManagementPath) .header("X-Api-RootPath", managementPath) .build() val updatedExchange = exchange.mutate().request(updatedRequest).build() webHandler.handle(updatedExchange).then(Mono.empty()) } } }
内容的提问来源于stack exchange,提问作者Johan
相关产品推荐
相关产品推荐

