首页 > 解决方案 > 为什么我可以在没有请求的情况下注入请求范围的 bean?

问题描述

如果我尝试将请求范围的 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.

正如它应该。

(代码示例见本文末尾)

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

  1. 改变范围UsingBean
  2. 方法注入
  3. 范围代理

([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"
    }
}

标签: javaspringspring-bootkotlindependency-injection

解决方案


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

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

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


推荐阅读