如何在Quarkus的CDI Bean中获取请求HttpHeaders,以获取AcceptableLanguage头进行翻译语言选择?
你遇到的这个问题核心在于Bean作用域不匹配:@ApplicationScoped的Bean是单例,它的生命周期远长于单个HTTP请求,初始化的时候根本没有请求上下文,所以直接用@Context注入HttpHeaders自然会得到null。而JAX-RS控制器(资源类)默认是请求作用域的,所以注入能正常工作。
下面给你几个可行的解决方案,你可以根据自己的需求选择:
方案一:将TranslationService改为请求作用域(最简单)
如果你的TranslationService不需要保持单例状态,直接把@ApplicationScoped换成@RequestScoped,@Context就能正常注入请求级别的HttpHeaders了:
import javax.ws.rs.core.HttpHeaders; import javax.enterprise.context.RequestScoped; @RequestScoped public class TranslationService { @Context HttpHeaders httpHeaders; public String getPreferredLanguage() { return httpHeaders.getAcceptableLanguages().stream() .findFirst() .map(lang -> lang.getLanguage()) .orElse("en"); } }
方案二:用请求作用域Holder + Provider保留单例服务
如果必须让TranslationService保持@ApplicationScoped,可以通过一个请求作用域的Holder Bean来传递HttpHeaders,再用Provider延迟获取:
1. 创建请求作用域的HeadersHolder
import javax.ws.rs.core.HttpHeaders; import javax.enterprise.context.RequestScoped; @RequestScoped public class HeadersHolder { private HttpHeaders httpHeaders; public void setHttpHeaders(HttpHeaders httpHeaders) { this.httpHeaders = httpHeaders; } public HttpHeaders getHttpHeaders() { return httpHeaders; } }
2. 用过滤器设置Holder的值
创建一个JAX-RS过滤器,在请求到达时把当前的HttpHeaders存入Holder:
import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.inject.Inject; import javax.ws.rs.ext.Provider; @Provider public class RequestHeadersFilter implements ContainerRequestFilter { @Inject HeadersHolder headersHolder; @Context HttpHeaders httpHeaders; @Override public void filter(ContainerRequestContext requestContext) { // 两种方式都可以:用注入的HttpHeaders,或者从requestContext获取 headersHolder.setHttpHeaders(httpHeaders); } }
3. 在单例服务中通过Provider获取Holder
注意这里必须用Provider<HeadersHolder>而不是直接注入HeadersHolder——因为单例Bean初始化时请求上下文还不存在,Provider会在你调用get()时才从当前请求上下文获取实例:
import javax.ws.rs.core.HttpHeaders; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import javax.enterprise.inject.Provider; @ApplicationScoped public class TranslationService { @Inject Provider<HeadersHolder> headersHolderProvider; public String getPreferredLanguage() { HttpHeaders httpHeaders = headersHolderProvider.get().getHttpHeaders(); return httpHeaders.getAcceptableLanguages().stream() .findFirst() .map(lang -> lang.getLanguage()) .orElse("en"); } }
方案三:直接在方法参数中注入请求上下文
如果你的TranslationService只会在处理JAX-RS请求时被调用,还可以直接在服务方法中注入ContainerRequestContext(它包含了所有请求头信息):
import javax.ws.rs.core.ContainerRequestContext; import javax.enterprise.context.ApplicationScoped; import javax.ws.rs.core.Context; @ApplicationScoped public class TranslationService { public String getPreferredLanguage(@Context ContainerRequestContext requestContext) { return requestContext.getAcceptableLanguages().stream() .findFirst() .map(lang -> lang.getLanguage()) .orElse("en"); } }
之后在JAX-RS控制器中调用这个方法时,Quarkus会自动把当前请求的ContainerRequestContext注入进去。
至于你之前尝试用过滤器保存到@RequestScoped Bean却失败的原因,大概率是直接把@RequestScoped Bean注入到了@ApplicationScoped的TranslationService中——这种情况下注入的是一个代理,但如果在请求上下文外调用会出错,或者没有正确拿到当前请求的实例,用Provider就能解决这个问题。
内容的提问来源于stack exchange,提问作者Herr Derb

