Java 在 Spring 测试中请求作用域 bean

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2411343/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 07:15:36  来源:igfitidea点击:

request scoped beans in spring testing

javaspringjunitspring-mvcspring-test

提问by harschware

I would like to make use of request scoped beans in my app. I use JUnit4 for testing. If I try to create one in a test like this:

我想在我的应用程序中使用请求范围的 bean。我使用 JUnit4 进行测试。如果我尝试在这样的测试中创建一个:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring/TestScopedBeans-context.xml" })
public class TestScopedBeans {
    protected final static Logger logger = Logger
            .getLogger(TestScopedBeans.class);

    @Resource
    private Object tObj;

    @Test
    public void testBean() {
        logger.debug(tObj);
    }

    @Test
    public void testBean2() {
        logger.debug(tObj);
    }

With the following bean definition:

使用以下 bean 定义:

 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  <bean class="java.lang.Object" id="tObj" scope="request" />
 </beans>           

And I get:

我得到:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'gov.nasa.arc.cx.sor.query.TestScopedBeans': Injection of resource fields failed; nested exception is java.lang.IllegalStateException: No Scope registered for scope 'request'
<...SNIP...>
Caused by: java.lang.IllegalStateException: No Scope registered for scope 'request'

So I found this blog that seemed helpful: http://www.javathinking.com/2009/06/no-scope-registered-for-scope-request_5.html

所以我发现这个博客似乎很有帮助:http: //www.javathinking.com/2009/06/no-scope-registered-for-scope-request_5.html

But I noticed he uses AbstractDependencyInjectionSpringContextTestswhich seems to be deprecated in Spring 3.0. I use Spring 2.5 at this time, but thought it shouldn't be too hard to switch this method to use AbstractJUnit4SpringContextTests as the docs suggest (ok the docs link to the 3.8 version but I'm using 4.4). So I change the test to extend AbstractJUnit4SpringContextTests... same message. Same problem. And now the prepareTestInstance() method I want to override is not defined. OK, maybe I'll put those registerScope calls somewhere else... So I read more about TestExecutionListenersand think that would be better since I don't want to have to inherit the spring package structure. So I changed my Test to:

但我注意到他使用AbstractDependencyInjectionSpringContextTests似乎在 Spring 3.0 中已弃用。我此时使用 Spring 2.5,但认为按照文档建议将这种方法切换为使用 AbstractJUnit4SpringContextTests 应该不会太难(好吧,文档链接到 3.8 版本,但我使用的是 4.4)。所以我更改测试以扩展 AbstractJUnit4SpringContextTests... 相同的消息。同样的问题。现在我想覆盖的 prepareTestInstance() 方法没有定义。好吧,也许我会把那些 registerScope 调用放在其他地方......所以我阅读了更多关于TestExecutionListeners并认为这会更好,因为我不想继承 spring 包结构。所以我将测试更改为:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring/TestScopedBeans-context.xml" })
@TestExecutionListeners({})
public class TestScopedBeans {

expecting I would have to create a custom listener but I when I ran it. It works! Great, but why? I don't see where any of the stock listeners are registering request scope or session scope, and why would they? there's nothing to say I want that yet, this might not be a Test for Spring MVC code...

期望我必须创建一个自定义侦听器,但是当我运行它时。有用!很好,但为什么呢?我没有看到任何股票侦听器在哪里注册请求范围或会话范围,为什么会这样?没什么好说的,我想要那个,这可能不是 Spring MVC 代码的测试......

采纳答案by skaffman

The test passes because it isn't doing anything :)

测试通过,因为它没有做任何事情:)

When you omit the @TestExecutionListenersannotation, Spring registers 3 default listeners, including one called DependencyInjectionTestExecutionListener. This is the listener responsible for scanning your test class looking for things to inject, including @Resourceannotations. This listener tried to inject tObj, and fails, because of the undefined scope.

当您省略@TestExecutionListeners注解时,Spring 会注册 3 个默认侦听器,包括一个名为DependencyInjectionTestExecutionListener. 这是负责扫描测试类以查找要注入的内容(包括@Resource注释)的侦听器。tObj由于未定义的范围,此侦听器尝试注入,但失败了。

When you declare @TestExecutionListeners({}), you suppress the registration of the DependencyInjectionTestExecutionListener, and so the test never gets tObjinjected at all, and because your test is not checking for the existence of tObj, it passes.

当您声明 时@TestExecutionListeners({}),您会抑制 的注册DependencyInjectionTestExecutionListener,因此测试根本不会被tObj注入,并且因为您的测试没有检查 的存在tObj,所以它通过了。

Modify your test so that it does this, and it will fail:

修改您的测试以使其执行此操作,但它将失败:

@Test
public void testBean() {
    assertNotNull("tObj is null", tObj);
}

So with your empty @TestExecutionListeners, the test passes because nothing happens.

所以在你的空的情况下@TestExecutionListeners,测试通过,因为什么都没有发生

Now, on to your original problem. If you want to try registering the request scope with your test context, then have a look at the source code for WebApplicationContextUtils.registerWebApplicationScopes(), you'll find the line:

现在,回到你原来的问题。如果您想尝试在您的测试上下文中注册请求范围,请查看 的源代码WebApplicationContextUtils.registerWebApplicationScopes(),您会找到以下行:

beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());

You could try that, and see how you go, but there might be odd side-effects, because you're not really meant to do this in a test.

您可以尝试一下,看看效果如何,但可能会有一些奇怪的副作用,因为您并不是真的打算在测试中这样做。

Instead, I would recommend rephrasing your test so that you don't needrequest scoped beans. This shouldn't be difficult, the lifecycle of the @Testshouldn't be any longer than the lifecycle of a request-scoped bean, if you write self-contained tests. Remember, there's no need to test the scoping mechanism, it's part of Spring and you can assume it works.

相反,我建议重新表述您的测试,以便您不需要请求范围的 bean。这应该不难,@Test如果您编写自包含测试,则 的生命周期不应超过请求范围 bean 的生命周期。请记住,没有必要测试作用域机制,它是 Spring 的一部分,您可以假设它有效。

回答by MariuszS

Solution for Spring 3.2 or newer

Spring 3.2 或更新版本的解决方案

Spring starting with version 3.2 provides support for session/request scoped beans for integration testing.

Spring 从 3.2 版开始为集成测试提供对会话/请求范围的 bean 的支持

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class)
@WebAppConfiguration
public class SampleTest {

    @Autowired WebApplicationContext wac;

    @Autowired MockHttpServletRequest request;

    @Autowired MockHttpSession session;    

    @Autowired MySessionBean mySessionBean;

    @Autowired MyRequestBean myRequestBean;

    @Test
    public void requestScope() throws Exception {
        assertThat(myRequestBean)
           .isSameAs(request.getAttribute("myRequestBean"));
        assertThat(myRequestBean)
           .isSameAs(wac.getBean("myRequestBean", MyRequestBean.class));
    }

    @Test
    public void sessionScope() throws Exception {
        assertThat(mySessionBean)
           .isSameAs(session.getAttribute("mySessionBean"));
        assertThat(mySessionBean)
           .isSameAs(wac.getBean("mySessionBean", MySessionBean.class));
    }
}

Read more: Request and Session Scoped Beans

阅读更多:请求和会话范围的 Bean



Solution for Spring before 3.2 with listener

Spring 3.2 之前的带有监听器的解决方案

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class)
@TestExecutionListeners({WebContextTestExecutionListener.class,
        DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class})
public class SampleTest {
    ...
}

WebContextTestExecutionListener.java

WebContextTestExecutionListener.java

public  class WebContextTestExecutionListener extends AbstractTestExecutionListener {
    @Override
    public void prepareTestInstance(TestContext testContext) {
        if (testContext.getApplicationContext() instanceof GenericApplicationContext) {
            GenericApplicationContext context = (GenericApplicationContext) testContext.getApplicationContext();
            ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
            beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST,
                    new SimpleThreadScope());
            beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION,
                    new SimpleThreadScope());
        }
    }
}


Solution for Spring before 3.2 with custom scopes

具有自定义范围的 Spring 3.2 之前的解决方案

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class, locations = "test-config.xml")
public class SampleTest {

...

}

TestConfig.java

TestConfig.java

@Configuration
@ComponentScan(...)
public class TestConfig {

    @Bean
    public CustomScopeConfigurer customScopeConfigurer(){
        CustomScopeConfigurer scopeConfigurer = new CustomScopeConfigurer();

        HashMap<String, Object> scopes = new HashMap<String, Object>();
        scopes.put(WebApplicationContext.SCOPE_REQUEST,
                new SimpleThreadScope());
        scopes.put(WebApplicationContext.SCOPE_SESSION,
                new SimpleThreadScope());
        scopeConfigurer.setScopes(scopes);

        return scopeConfigurer

}

or with xml configuration

或使用 xml 配置

test-config.xml

test-config.xml

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="request">
                <bean class="org.springframework.context.support.SimpleThreadScope"/>
            </entry>
        </map>
        <map>
            <entry key="session">
                <bean class="org.springframework.context.support.SimpleThreadScope"/>
            </entry>
        </map>
    </property>
</bean>

Source code

源代码

Source code for all presented solutions:

所有提供的解决方案的源代码:

回答by Jim Cox

This is still an open issue:

这仍然是一个悬而未决的问题:

https://jira.springsource.org/browse/SPR-4588

https://jira.springsource.org/browse/SPR-4588

I was able to get this to work (mostly) by defining a custom context loader as outlined in

我能够通过定义一个自定义上下文加载器来让它工作(主要是)

http://forum.springsource.org/showthread.php?p=286280

http://forum.springsource.org/showthread.php?p=286280

回答by user1050755

NOT reading the docs sometimes drives one crazy. Almost.

不阅读文档有时会让人发疯。几乎。

If you are using shorter-lived beans (request scope for example), you most likely also need to change your lazy init default! Otherwise the WebAppContext will fail to load and tell you something about missing request scope, which is of course missing, because the context is still loading!

如果您使用的是寿命较短的 bean(例如请求范围),您很可能还需要更改您的惰性初始化默认值!否则 WebAppContext 将无法加载并告诉您有关缺少请求范围的信息,这当然是缺少的,因为上下文仍在加载!

http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/beans.html#beans-factory-lazy-init

http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/beans.html#beans-factory-lazy-init

The Spring guys should definitely put that hint into their exception message...

Spring 人员绝对应该将该提示放入他们的异常消息中......

If you don't want to change the default, there is also the annotation way: put "@Lazy(true)" after @Component etc. to make singletons initialize lazy and avoid instantiating request-scoped beans too early.

如果不想更改默认值,还有一个注解方式:在@Component 等后面加上"@Lazy(true)",使单例初始化懒惰,避免过早实例化请求范围的bean。

回答by Christopher Yang

MariuszS' solution works, except I couldn't get the transaction committed properly.

MariuszS 的解决方案有效,但我无法正确提交事务。

It seems the newly released 3.2 has finally made testing request/session scoped beans first class citizens. Here's a couple of blogs for more details.

似乎新发布的 3.2 终于使测试请求/会话范围的 bean 成为一等公民。这里有几个博客了解更多细节。

Rossen Stoyanchev's Spring Framework 3.2 RC1: Spring MVC Test Framework

Rossen Stoyanchev 的Spring Framework 3.2 RC1:Spring MVC 测试框架

Sam Brannen's Spring Framework 3.2 RC1: New Testing Features

Sam Brannen 的Spring Framework 3.2 RC1:新的测试功能

回答by Ido Cohn

I've tried several solutions, including @Marius's solution with the "WebContextTestExecutionListener", but it didn't work for me, as this code loaded the application context before creating the request scope.

我尝试了几种解决方案,包括带有“WebContextTestExecutionListener”的@Marius 解决方案,但它对我不起作用,因为此代码在创建请求范围之前加载了应用程序上下文。

The answer that helped me in the end is not a new one, but it's good: http://tarunsapra.wordpress.com/2011/06/28/junit-spring-session-and-request-scope-beans/

最后帮助我的答案不是新的,但很好:http: //tarunsapra.wordpress.com/2011/06/28/junit-spring-session-and-request-scope-beans/

I simply added the following snippet to my (test) application context:

我只是将以下代码段添加到我的(测试)应用程序上下文中:

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="request">
                <bean class="org.springframework.context.support.SimpleThreadScope"/>
            </entry>
        </map>
    </property>
</bean>

Good luck!

祝你好运!

回答by stivlo

Test Request-Scoped Beans with Springexplains very well how to register and create a custom scope with Spring.

Test Request-Scoped Beans with Spring很好地解释了如何使用 Spring注册和创建自定义范围。

In a nutshell, as Ido Cohn explained, it's enough to add the following to the text context configuration:

简而言之,正如 Ido Cohn 所解释的,将以下内容添加到文本上下文配置中就足够了:

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="request">
                <bean class="org.springframework.context.support.SimpleThreadScope"/>
            </entry>
        </map>
    </property>
</bean>

Instead of using the predefined SimpleThreadScope, based on ThreadLocal, it's also easy to implement a Custom one, as explained in the article.

与使用基于 ThreadLocal 的预定义 SimpleThreadScope 不同,实现自定义的也很容易,如文章中所述。

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;

public class CustomScope implements Scope {

    private final Map<String , Object> beanMap = new HashMap<String , Object>();

    public Object get(String name, ObjectFactory<?> factory) {
        Object bean = beanMap.get(name);
        if (null == bean) {
            bean = factory.getObject();
            beanMap.put(name, bean);
        }
        return bean;
    }

    public String getConversationId() {
        // not needed
        return null;
    }

    public void registerDestructionCallback(String arg0, Runnable arg1) {
        // not needed
    }

    public Object remove(String obj) {
        return beanMap.remove(obj);
    }

    public Object resolveContextualObject(String arg0) {
        // not needed
        return null;
    }
}

回答by OrangeDog

A solution, tested with Spring 4, for when you require request-scoped beans but aren't making any requests via MockMVC, etc.

使用 Spring 4 测试的解决方案,用于当您需要请求范围的 bean 但不通过MockMVC等发出任何请求时。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(/* ... */)
public class Tests {

    @Autowired
    private GenericApplicationContext context;

    @Before
    public void defineRequestScope() {
        context.getBeanFactory().registerScope(
            WebApplicationContext.SCOPE_REQUEST, new RequestScope());
        RequestContextHolder.setRequestAttributes(
            new ServletRequestAttributes(new MockHttpServletRequest()));
    }

    // ...