Spring Boot MockMVC - запрос bean-компонента с областью действия не был автоматически подключен должным образом

Я хочу иметь bean-компонент с областью запроса, заполнить его данными в фильтре, а затем получить доступ к данным в контроллере. Все это отлично работает, когда я запускаю свое приложение, но когда я пытаюсь протестировать его с помощью MockMVC, данные не устанавливаются в bean-компоненте с областью запроса в моем контроллере.

Вот код:

пом.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <groupId>org.lebo.test</groupId>
    <artifactId>spring-boot-rsb-test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <spring-boot.version>1.2.3.RELEASE</spring-boot.version>
        <lombok.version>1.16.2</lombok.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>${spring-boot.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${spring-boot.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>

        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>1.9.5</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-library</artifactId>
            <version>1.3</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
            <version>1.3</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

Применение

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application
{
    public static void main(String[] args) {
        ApplicationContext ctx = SpringApplication.run(Application.class, args);
    }
}

RequestScopedBean

import lombok.Data;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;  

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Data
public class RequestScopedBean {

    private String data;
}

Контроллер

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class SomeController {

    @Autowired
    private RequestScopedBean requestScopedBean;

    @RequestMapping("/test")
    public ResponseEntity test() {
        return new ResponseEntity(requestScopedBean.getData(), HttpStatus.OK);
    }
}

Фильтр

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class SomeFilter extends OncePerRequestFilter {

    @Autowired
    private RequestScopedBean requestScopedBean;

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        requestScopedBean.setData("Data");

        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }
}

И, наконец, тест:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@SpringApplicationConfiguration(classes = Application.class)
public class SomeTest {

    @Autowired
    private MockHttpSession session;

    @Autowired
    private WebApplicationContext wac;

    @Autowired
    private SomeFilter someFilter;

    @Test
    public void should_return_data() throws Exception {
        MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(wac)
                .addFilter(someFilter)
                .build();

        MvcResult result = mockMvc.perform(get("/test")
                .session(session))
                .andExpect(status().isOk())
                .andReturn();

        assertThat(result.getResponse().getContentAsString(), is("Data"));
    }
}

Если я изменю область действия на «сеанс», тест пройдет успешно.


person Lebo    schedule 26.04.2015    source источник
comment
Можете ли вы предоставить свой pom и импорт для ваших классов?   -  person PaulNUK    schedule 28.04.2015
comment
Конечно... pom.xml и импорт добавлены   -  person Lebo    schedule 28.04.2015
comment
У вас есть решение этого? У меня аналогичная настройка, и похоже, что компонент области запроса реконструируется, когда запрос попадает в DispatcherServlet (поэтому конструктор вызывает цепочку фильтров и обогащает компонент запроса, но затем после фильтра перед MVC он снова создается как новый) - поэтому состояние потеряно. Не знаю, как обойти проблему.   -  person rhinds    schedule 21.05.2015
comment
К сожалению нет. Мне нужно было найти быстрое решение, поэтому я установил свои данные в атрибуте запроса вручную, вызвав HttpServletRequest.setAttribute() в фильтре. Когда мне это нужно в контроллере, я просто получаю его из запроса с помощью HttpServletRequest.getAttribute(). В Spring удобно, что вы можете легко получить HttpServletRequest, просто определив его как параметр метода в любом методе контроллера.   -  person Lebo    schedule 25.05.2015