Quarkus中使用@RequestScoped自定义请求上下文时字段始终为null的问题求助
问题分析
从你的代码和描述来看,核心问题是JAX-RS过滤器与Quarkus CDI请求上下文的交互导致UserContext实例不共享:
- 你用
@Provider标注的AuthenticationContextProvider是传统JAX-RS过滤器,默认生命周期为@Singleton。在单例过滤器中注入@RequestScoped的UserContext时,CDI会注入代理对象,但过滤器执行filter方法时,可能还未完全激活CDI请求上下文,导致代理无法正确绑定到当前请求的UserContext实例,最终设置的id无法同步到后续资源类使用的实例中。 - 你使用的
quarkus-rest是RESTEasy Reactive实现,它和传统JAX-RS过滤器模型存在适配差异,传统的ContainerRequestFilter可能无法完美兼容Quarkus的请求上下文传播逻辑。
下面是几个按优先级排序的可行解决方案:
方案一:直接在UserContext中注入上下文(最简洁)
不需要额外过滤器,直接在UserContext中通过Quarkus的CDI能力注入安全上下文或JWT主体,利用框架内置的上下文传播机制:
@RequestScoped public class UserContext { @Inject Optional<JWTCallerPrincipal> jwtPrincipal; @Inject Optional<SecurityContext> securityContext; public String getId() { // 优先取JWT主体的name, fallback到普通Principal的name return jwtPrincipal.map(JWTCallerPrincipal::getName) .orElseGet(() -> securityContext .flatMap(sc -> Optional.ofNullable(sc.getUserPrincipal())) .map(Principal::getName) .orElse(null)); } }
这个方案可以直接删掉AuthenticationContextProvider过滤器,在GreetingResource中注入UserContext后调用getId()即可,Quarkus会自动处理请求上下文绑定。
方案二:改用Quarkus REST原生过滤器(适配Reactive生态)
如果想保留过滤器逻辑,建议使用Quarkus REST(RESTEasy Reactive)原生的@ServerRequestFilter注解替代传统JAX-RS过滤器,同时将过滤器设为@RequestScoped,确保每个请求的过滤器实例绑定当前请求的UserContext:
@RequestScoped @ServerRequestFilter public class AuthenticationContextProvider { @Inject UserContext userContext; public void filter(ContainerRequestContext requestContext) { var securityContext = requestContext.getSecurityContext(); if (securityContext != null) { var principal = securityContext.getUserPrincipal(); if (principal != null && principal instanceof JWTCallerPrincipal jwt) { userContext.setId(jwt.getName()); } else if (principal != null) { userContext.setId(principal.getName()); } } } }
该方案完全适配Quarkus的Reactive生态,请求上下文传播更可靠。
方案三:修复传统JAX-RS过滤器的作用域
如果一定要用传统ContainerRequestFilter,可以将过滤器的作用域改为@RequestScoped,让每个请求对应一个过滤器实例,确保注入的UserContext是当前请求的真实实例:
@RequestScoped @Provider public class AuthenticationContextProvider implements ContainerRequestFilter { @Inject UserContext userContext; @Override public void filter(ContainerRequestContext requestContext) { // 原有逻辑保持不变 } }
修改后,过滤器的生命周期变为请求级,设置的id就能在后续资源类中正常获取。
验证建议
你可以在UserContext的setId和getId方法中添加日志,验证实例是否一致:
@RequestScoped public class UserContext { private String id; private final Logger log = Logger.getLogger(UserContext.class.getName()); public String getId() { log.info("获取UserContext实例[" + this + "]的id: " + id); return id; } public void setId(String id) { this.id = id; log.info("设置UserContext实例[" + this + "]的id: " + id); } }
同时在GreetingResource的sayHello方法中也添加日志,查看过滤器和资源类中使用的UserContext实例哈希值是否相同——如果相同,说明上下文已正确传播。
备注:内容来源于stack exchange,提问作者Bad_Pop

