Request-scoped Bean在@RestController可用,HttpFilter中抛ScopeNotActiveException
问题根源
你的自定义JwtPreAuthorizationConfigurer是单例Bean,而TenantContext是请求作用域的Bean。虽然请求已经到达处理线程,但Spring的请求上下文(RequestContextHolder)此时还没绑定到当前线程——原因是你的Filter执行顺序在Spring的RequestContextFilter之前,或者项目中未配置RequestContextFilter/RequestContextListener。
而@RestController中的方法能正常工作,是因为Controller的方法是被DispatcherServlet调用的:DispatcherServlet会在处理请求前先初始化请求上下文并绑定到RequestContextHolder,此时请求作用域的Bean可以正常获取实例。
解决方案
方案1:配置RequestContextFilter并调整Filter执行顺序
首先注册RequestContextFilter,它负责将请求上下文绑定到线程:
@Configuration class PreAuthorizationHttpHeaderFilterConfig { @Bean public RequestContextFilter requestContextFilter() { return new RequestContextFilter(); } @Bean @Order(9991) // 确保在RequestContextFilter之后执行(默认order为9990) JwtPreAuthorizationConfigurer jwtPreAuthorizationConfigurer() { return new JwtPreAuthorizationConfigurer(); } @Bean @RequestScope TenantContext tenantContext() { return new TenantContext(); } }
注:
RequestContextFilter的默认执行顺序是Ordered.REQUEST_WRAPPER_FILTER_MAX_ORDER - 10(即9990),所以给自定义Filter设置更大的order值,让它后执行。
方案2:用ObjectFactory延迟获取请求作用域Bean
在Filter中注入ObjectFactory<TenantContext>,延迟到doFilter方法中获取Bean实例——此时请求上下文已经绑定:
public class JwtPreAuthorizationConfigurer extends HttpFilter { @Resource private ObjectFactory<TenantContext> tenantContextFactory; @Override protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { TenantContext tenantContext = tenantContextFactory.getObject(); tenantContext.getIssuerUri(); // 正常调用 chain.doFilter(request, response); } }
方案3:显式配置请求作用域Bean的代理模式(可选)
确保TenantContext的Bean生成CGLIB代理(异常中已显示有代理,但显式配置更稳妥):
@Bean @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS) TenantContext tenantContext() { return new TenantContext(); }
该方案需配合方案1或2使用,核心还是要保证请求上下文已初始化。
内容的提问来源于stack exchange,提问作者Sergey Tsypanov

