为啥我可以在没有请求的情况下注入请求范围的 bean?

Posted

技术标签:

【中文标题】为啥我可以在没有请求的情况下注入请求范围的 bean?【英文标题】:Why can I inject request-scoped beans in the absence of a request?为什么我可以在没有请求的情况下注入请求范围的 bean? 【发布时间】:2019-09-12 10:48:25 【问题描述】:

如果我尝试将请求范围的 bean 注入到单例范围的 bean 中,则会失败,因为

java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

应该如此。

(代码示例见文末)

我知道三种方法可以通过以下测试变绿:

    更改UsingBean的范围 方法注入 范围代理

([1] 与其说是一种解决方案,不如说是一种 hack,并且可能会导致进一步的问题,但它确实将测试变为绿色。:P)

虽然我确实理解这三个选项背后的想法,但我完全不明白为什么它们会起作用。

我的意思是,即使我在 [1] 中将范围更改为“会话”,当我实例化 UsingBean 时,我仍然既没有会话也没有请求。

至于 [2] 和 [3],它们避免在启动时获取实例,但是当它们实际获取实例时我仍然没有请求。

但测试并没有失败。为什么?

代码示例

假设我有一个请求范围的 bean

@Repository
@Scope("request")
class RequestScopedBean : ScopedBean
    override fun foo(): String 
        return "Hello World"
    

在单例范围内使用

@Service
class UsingBean
    private val scopedBean:ScopedBean

    @Inject
    constructor(scopedBean: ScopedBean) 
        this.scopedBean = scopedBean
    

    fun foo():String
        val foo = scopedBean.foo()
        println(foo)
        return foo
    

我们也为此创建一个小测试:

@RunWith(SpringJUnit4ClassRunner::class)
@SpringBootTest
@WebAppConfiguration
class RequestScopedBeansIT

    @Inject
    private lateinit var bean : UsingBean

    @Test
    fun canInject()
        assertThat(bean.foo()).isEqualTo("Hello World")
    

1) 改变UsingBean的范围

@Service
@Scope("session")
class UsingBean
    private val scopedBean:ScopedBean

    @Inject
    constructor(scopedBean: ScopedBean) 
        this.scopedBean = scopedBean
    

    fun foo():String
        val foo = scopedBean.foo()
        println(foo)
        return foo
    

2) 方法注入

@Service
class UsingBean
    private val scopedBean:ScopedBean
        get() = injectBean()

    fun foo():String
        val foo = scopedBean.foo()
        println(foo)
        return foo
    

    @Lookup
    fun injectBean():ScopedBean
        TODO("will be replaced by spring")
    

3) 范围代理

@Repository
@Scope("request",proxyMode = ScopedProxyMode.TARGET_CLASS)
class RequestScopedBean : ScopedBean
    override fun foo(): String 
        return "Hello World"
    

@Repository
@RequestScope
class RequestScopedBean : ScopedBean
    override fun foo(): String 
        return "Hello World"
    

【问题讨论】:

您确实有一个请求,这就是 @WebAppConfiguration 正在处理的问题。它将MockServletRequest 等绑定到当前正在执行的线程。所以是的,你确实有一个请求。 @M.Deinum 谢谢。想从中找出答案吗? 【参考方案1】:

尽管您可能认为您没有当前的请求和会话,但实际上您确实有。

@WebAppConfiguration 是触发它的原因。它将激活ServletTestExecutionListener,它将注册一个线程绑定的模拟请求和响应。

这解释了为什么测试成功,因为存在线程绑定请求。

【讨论】:

以上是关于为啥我可以在没有请求的情况下注入请求范围的 bean?的主要内容,如果未能解决你的问题,请参考以下文章

将较短范围的 Bean 实例注入 CDI 中较大范围的 bean 实例 - 它是如何工作的?

为啥禁止没有凭据的 CORS?

为啥在这种情况下无法访问全局范围? [复制]

Spring:如何将 HttpServletRequest 注入到请求范围的 bean 中?

在启用 CORS 的情况下,无法从我的请求标头中检索我的 x-api-key。为啥?

Google OAuth 在不请求的情况下返回其他范围