有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

spring测试中的java请求范围bean

我想在我的应用程序中使用请求范围的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,这在Spring3.0中似乎被弃用了。 此时我使用Spring2.5,但我认为将此方法转换为使用AbstractJUnit4SpringContextTests应该不会太难 正如文档所建议的(好的,文档链接到3.8版本,但我使用的是4.4)。所以我改变了主意 测试以扩展AbstractJUnit4SpringContextTests。。。同样的信息。同样的问题。现在是我想要的prepareTestInstance()方法 未定义要覆盖的对象。好吧,也许我会把那些登记镜电话放在别的地方。。。所以我读了更多关于TestExecutionListeners的书,认为这样会更好,因为我不想继承spring包结构。所以 我将测试更改为:

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

我希望我必须创建一个自定义侦听器,但当我运行它时,我忘记了。它起作用了!很好,但是为什么呢?我看不出有哪位股票听众 正在注册请求范围或会话范围,为什么要注册?现在还没有什么要说的,这可能不是对SpringMVC代码的测试


共 (6) 个答案

  1. # 1 楼答案

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

    Spring从版本3.2provides 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));
        }
    }
    

    阅读更多:Request and Session Scoped Beans


    3.2之前的Spring解决方案,带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());
            }
        }
    }
    

    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>
    

    源代码

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

  2. # 2 楼答案

    我尝试过几种解决方案,包括@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>
    

    祝你好运

  3. # 3 楼答案

    Test Request-Scoped Beans with Spring很好地解释了如何使用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>
    

    与使用基于ThreadLocal的预定义SimpleThreadScope不同,实现自定义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;
        }
    }
    
  4. # 4 楼答案

    使用Spring4测试的解决方案,用于当您需要请求范围的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()));
        }
    
        // ...
    
  5. # 5 楼答案

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

    省略@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的一部分,您可以假设它可以工作