Quarkus中/q/health端点Web过滤器未触发,如何实现租户专属健康检查?
在Quarkus中为/q前缀系统端点添加租户上下文过滤器的解决方案
Quarkus的/q前缀端点(如健康检查)属于系统扩展提供的独立路由,默认不会触发应用内的JAX-RS ContainerRequestFilter。以下是几种可行的解决方案,帮你在这类端点中获取租户上下文:
方法1:使用Vert.x RouteFilter拦截所有请求
Vert.x层面的过滤器可以覆盖Quarkus处理的所有HTTP请求,包括/q前缀的系统端点,是最通用的方案。
- 编写RouteFilter Bean,提前解析租户上下文:
import io.quarkus.vertx.web.RouteFilter; import io.vertx.ext.web.RoutingContext; import jakarta.enterprise.context.ApplicationScoped; @ApplicationScoped public class TenantRouteFilter { @RouteFilter(100) // 数值越小,执行优先级越高 public void injectTenantContext(RoutingContext ctx) { // 示例:从请求头获取租户ID,可根据实际逻辑调整(如参数、Cookie) String tenantId = ctx.request().getHeader("X-Tenant-ID"); if (tenantId != null) { // 将租户ID存入Vertx上下文,供后续健康检查使用 ctx.put("tenantId", tenantId); // 或存入ThreadLocal,供其他组件共享 TenantContextHolder.setTenantId(tenantId); } // 继续执行后续请求逻辑 ctx.next(); } }
- 在自定义健康检查类中获取租户上下文:
import io.quarkus.smallrye.health.HealthCheck; import io.smallrye.health.api.HealthCheckResponse; import io.vertx.ext.web.RoutingContext; import jakarta.inject.Inject; public class TenantHealthCheck implements HealthCheck { @Inject RoutingContext routingContext; @Override public HealthCheckResponse call() { String tenantId = routingContext.get("tenantId"); if (tenantId == null) { return HealthCheckResponse.named("tenant-health") .down() .withData("reason", "租户ID未提供") .build(); } // 执行租户专属健康检查(如验证租户数据库连接、资源状态) boolean isHealthy = checkTenantResources(tenantId); return HealthCheckResponse.named("tenant-health") .status(isHealthy) .withData("tenant-id", tenantId) .build(); } private boolean checkTenantResources(String tenantId) { // 自定义检查逻辑 return true; } }
方法2:使用Servlet Filter覆盖全路径
Servlet过滤器能拦截所有HTTP请求,适合熟悉Servlet生态的场景。
- 实现Servlet Filter并指定拦截所有路径:
import jakarta.servlet.*; import jakarta.servlet.annotation.WebFilter; import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; @WebFilter(urlPatterns = "/*") public class TenantServletFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpReq = (HttpServletRequest) request; String tenantId = httpReq.getHeader("X-Tenant-ID"); if (tenantId != null) { // 将租户ID存入请求属性 request.setAttribute("tenantId", tenantId); // 或存入ThreadLocal,注意后续清理避免内存泄漏 TenantContextHolder.setTenantId(tenantId); } try { chain.doFilter(request, response); } finally { // 清理ThreadLocal(异步场景必须做) TenantContextHolder.clear(); } } }
- 在健康检查类中通过HttpServletRequest获取租户ID:
import io.quarkus.smallrye.health.HealthCheck; import io.smallrye.health.api.HealthCheckResponse; import jakarta.inject.Inject; import jakarta.servlet.http.HttpServletRequest; public class TenantServletHealthCheck implements HealthCheck { @Inject HttpServletRequest request; @Override public HealthCheckResponse call() { String tenantId = (String) request.getAttribute("tenantId"); if (tenantId == null) { return HealthCheckResponse.named("servlet-tenant-health") .down() .withData("reason", "租户上下文缺失") .build(); } // 执行租户特定检查 boolean healthy = verifyTenantServices(tenantId); return HealthCheckResponse.named("servlet-tenant-health") .status(healthy) .withData("tenant", tenantId) .build(); } private boolean verifyTenantServices(String tenantId) { // 自定义检查逻辑 return true; } }
方法3:直接在健康检查类中解析租户信息
如果不需要全局过滤器,可直接在健康检查逻辑中获取请求参数/头,省去过滤器步骤:
import io.quarkus.smallrye.health.HealthCheck; import io.smallrye.health.api.HealthCheckResponse; import io.vertx.ext.web.RoutingContext; import jakarta.inject.Inject; public class DirectTenantHealthCheck implements HealthCheck { @Inject RoutingContext routingContext; @Override public HealthCheckResponse call() { // 直接从请求头提取租户ID String tenantId = routingContext.request().getHeader("X-Tenant-ID"); if (tenantId == null) { return HealthCheckResponse.named("direct-tenant-health").down().build(); } // 租户专属健康检查逻辑 return HealthCheckResponse.named("direct-tenant-health") .up() .withData("tenant-id", tenantId) .build(); } }
注意事项
- 使用ThreadLocal存储租户上下文时,务必在请求结束后清理,避免内存泄漏(异步场景尤其重要)。
- RouteFilter的执行顺序可通过注解参数调整,确保租户上下文解析在其他业务逻辑之前完成。
- JAX-RS过滤器默认不会覆盖
/q路径,因此Vert.x或Servlet过滤器是更可靠的选择。
内容的提问来源于stack exchange,提问作者Herr Derb
相关产品推荐
相关产品推荐

