在春季测试中请求范围内的bean

发布于 2024-08-25 00:55:54 字数 2647 浏览 7 评论 0原文

我想在我的应用程序中使用请求范围的 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);
    }

使用以下 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>           

我得到:

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'

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

但我注意到他使用 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 {

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

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:

@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:

 <?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

But I noticed he uses AbstractDependencyInjectionSpringContextTests which 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 TestExecutionListeners and think that would be better since I don't want to have to inherit the spring package structure. So
I changed my Test to:

@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...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

澉约 2024-09-01 00:55:55

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

从版本 3.2 开始,Spring 提供对会话/请求作用域 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));
    }
}

了解更多:请求和会话作用域 Bean


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

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

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());
        }
    }
}

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

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

...

}

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

}

或使用 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>

源代码

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

Solution for Spring 3.2 or newer

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

@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


Solution for Spring before 3.2 with listener

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

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

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

...

}

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

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:

も星光 2024-09-01 00:55:55

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

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

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

<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>

祝你好运!

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.

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/

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!

九厘米的零° 2024-09-01 00:55:55

一个使用 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()));
    }

    // ...

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

@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()));
    }

    // ...
浮生面具三千个 2024-09-01 00:55:55

测试通过是因为它没有执行任何操作:)

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

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

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

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

因此,使用空的 @TestExecutionListeners 时,测试会通过,因为什么也没有发生

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

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

您可以尝试一下,然后查看你怎么做,但可能会有奇怪的副作用,因为你并不是真的打算在测试中这样做。

相反,我建议重新措辞您的测试,以便您不需要请求作用域 bean。这应该不难,如果您编写独立的测试,则 @Test 的生命周期不应比请求范围 bean 的生命周期长。请记住,无需测试作用域机制,它是 Spring 的一部分,您可以假设它有效。

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

When you omit the @TestExecutionListeners annotation, 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 @Resource annotations. This listener tried to inject tObj, and fails, because of the undefined scope.

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

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.

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:

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 need request scoped beans. This shouldn't be difficult, the lifecycle of the @Test shouldn'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.

温柔戏命师 2024-09-01 00:55:55

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

https://jira.springsource.org/browse/SPR-4588< /a>

我能够通过定义自定义上下文加载器(主要)来使其工作,如

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

This is still an open issue:

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

呆头 2024-09-01 00:55:55

使用 Spring 测试请求范围的 Bean 很好地解释了如何使用 Spring 注册并创建自定义范围。

简而言之,正如 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>

不用使用预定义的 SimpleThreadScope,基于 ThreadLocal,也可以轻松实现自定义线程,如文章中所述。

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;
    }
}

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

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

<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.

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;
    }
}
乖乖公主 2024-09-01 00:55:55

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

看来新发布的 3.2 终于使测试请求/会话作用域的 beans 成为一等公民。这里有几个博客以了解更多详细信息。

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

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

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

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.

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

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

断肠人 2024-09-01 00:55:55

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

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

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

Spring 的家伙绝对应该把这个提示放入他们的异常消息...

如果您不想更改默认值,还有一种注释方式:在@Component等之后放置“@Lazy(true)”,以使单例初始化延迟并避免过早实例化请求范围的bean 。

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!

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...

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文