Как избежать исключения "Circular view path" с Spring MVC test

У меня есть следующий код в одном из моих контроллеров:

@Controller
@RequestMapping("/preference")
public class PreferenceController {

    @RequestMapping(method = RequestMethod.GET, produces = "text/html")
    public String preference() {
        return "preference";
    }
}

Я просто пытаюсь проверить его с помощью Spring MVC следующим образом:

@ContextConfiguration
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class PreferenceControllerTest {

    @Autowired
    private WebApplicationContext ctx;

    private MockMvc mockMvc;
    @Before
    public void setup() {
        mockMvc = webAppContextSetup(ctx).build();
    }

    @Test
    public void circularViewPathIssue() throws Exception {
        mockMvc.perform(get("/preference"))
               .andDo(print());
    }
}

Я получаю следующее исключение:

Путь к циклическому представлению [предпочтение]: снова отправит обратно к текущему URL обработчика [/предпочтение]. Проверьте настройки ViewResolver! (Подсказка: это может быть результатом неуказанного представления из-за генерации имени представления по умолчанию.)

То, что я нахожу странным, это то, что он работает нормально, когда я загружаю "полную" конфигурацию контекста, которая включает шаблон и средства разрешения представления, как показано ниже:

<bean class="org.thymeleaf.templateresolver.ServletContextTemplateResolver" id="webTemplateResolver">
    <property name="prefix" value="WEB-INF/web-templates/" />
    <property name="suffix" value=".html" />
    <property name="templateMode" value="HTML5" />
    <property name="characterEncoding" value="UTF-8" />
    <property name="order" value="2" />
    <property name="cacheable" value="false" />
</bean>

Мне хорошо известно, что префикс, добавленный распознавателем шаблонов, гарантирует отсутствие "кругового пути просмотра", когда приложение использует этот распознаватель шаблонов.

Но тогда как я должен проверить свое приложение, используя Spring MVC test? У кого-нибудь есть какие-либо подсказки?

Ответ 1

Это не имеет никакого отношения к тестированию MVC Spring.

Если вы не объявляете ViewResolver, Spring регистрирует по умолчанию InternalResourceViewResolver, который создает экземпляры JstlView для рендеринга View.

Класс JstlView расширяет InternalResourceView, который

Обертка для JSP или другого ресурса в одном и том же веб-приложении. Предоставляет объекты модели в качестве атрибутов запроса и пересылает запрос на указанный URL ресурса с помощью javax.servlet.RequestDispatcher.

URL-адрес этого представления должен указывать ресурс в Интернете приложение, подходящее для RequestDispatcher вперед или включает Метод.

Смелый - мой. В других словах представление перед рендерингом попытается получить RequestDispatcher, к которому относится forward(). Перед этим он проверяет следующее

if (path.startsWith("/") ? uri.equals(path) : uri.equals(StringUtils.applyRelativePath(uri, path))) {
    throw new ServletException("Circular view path [" + path + "]: would dispatch back " +
                        "to the current handler URL [" + uri + "] again. Check your ViewResolver setup! " +
                        "(Hint: This may be the result of an unspecified view, due to default view name generation.)");
}

где path - это имя вида, то, что вы вернули из @Controller. В этом примере это preference. Переменная uri содержит uri обрабатываемого запроса, который равен /context/preference.

В приведенном выше коде понимается, что если бы вы перешли на /context/preference, тот же сервлет (так же, как и предыдущий) обработал бы запрос, и вы отправились бы в бесконечный цикл.


Когда вы объявляете ThymeleafViewResolver и ServletContextTemplateResolver с конкретными prefix и suffix, он строит View по-другому, указывая ему путь, как

WEB-INF/web-templates/preference.html

ThymeleafView экземпляры располагают файл относительно пути ServletContext, используя ServletContextResourceResolver

templateInputStream = resourceResolver.getResourceAsStream(templateProcessingParameters, resourceName);`

который в конечном итоге

return servletContext.getResourceAsStream(resourceName);

Это получает ресурс, относящийся к пути ServletContext. Затем он может использовать TemplateEngine для генерации HTML. Здесь не может быть бесконечного цикла.

Ответ 2

Я решил эту проблему, используя @ResponseBody, как показано ниже:

@RequestMapping(value = "/resturl", method = RequestMethod.GET, produces = {"application/json"})
    @ResponseStatus(HttpStatus.OK)
    @Transactional(value = "jpaTransactionManager")
    public @ResponseBody List<DomainObject> findByResourceID(@PathParam("resourceID") String resourceID) {

Ответ 3

@Controller@RestController

У меня была та же проблема, и я заметил, что мой контроллер также @Controller. Замена на @RestController решила проблему. Вот объяснение от Spring Web MVC:

@RestController - это составная аннотация, которая сама мета-аннотирована с помощью @Controller и @ResponseBody, указывающих контроллер, каждый метод которого наследует аннотацию @ResponseBody уровня типа и, следовательно, записывает непосредственно в тело ответа по сравнению с разрешением представления и рендерингом с помощью шаблона HTML.

Ответ 4

Вот как я решил эту проблему:

@Before
    public void setup() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/jsp/view/");
        viewResolver.setSuffix(".jsp");

        mockMvc = MockMvcBuilders.standaloneSetup(new HelpController())
                                 .setViewResolvers(viewResolver)
                                 .build();
    }

Ответ 5

Здесь легко исправить, если вы действительно не заботитесь о рендеринге представления.

Создайте подкласс InternalResourceViewResolver, который не проверяет круговые пути просмотра:

public class StandaloneMvcTestViewResolver extends InternalResourceViewResolver {

    public StandaloneMvcTestViewResolver() {
        super();
    }

    @Override
    protected AbstractUrlBasedView buildView(final String viewName) throws Exception {
        final InternalResourceView view = (InternalResourceView) super.buildView(viewName);
        // prevent checking for circular view paths
        view.setPreventDispatchLoop(false);
        return view;
    }
}

Затем настройте свой тест:

MockMvc mockMvc;

@Before
public void setUp() {
    final MyController controller = new MyController();

    mockMvc =
            MockMvcBuilders.standaloneSetup(controller)
                    .setViewResolvers(new StandaloneMvcTestViewResolver())
                    .build();
}

Ответ 6

Если вы используете Spring Boot, добавьте зависимость thymeleaf в свой pom.xml:

    <dependency>
        <groupId>org.thymeleaf</groupId>
        <artifactId>thymeleaf-spring4</artifactId>
        <version>2.1.6.RELEASE</version>
    </dependency>

Ответ 7

Я использую Spring Boot, чтобы попытаться загрузить веб-страницу, а не тестировать, и возникла эта проблема. Мое решение немного отличалось от приведенного выше, учитывая немного другие обстоятельства. (хотя эти ответы помогли мне понять.)

Я просто должен был изменить свою зависимость от загрузчика Spring Boot в Maven с:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
</dependency>

чтобы:

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Простое изменение "паутины" на "тимелист" решило проблему для меня.

Ответ 8

Добавление / после /preference решило проблему для меня:

@Test
public void circularViewPathIssue() throws Exception {
    mockMvc.perform(get("/preference/"))
           .andDo(print());
}

Ответ 9

Я использую Spring Boot с Thymeleaf. Это то, что сработало для меня. Есть похожие ответы с JSP, но обратите внимание, что я использую HTML, а не JSP, и они находятся в папке src/main/resources/templates как в стандартном проекте Spring Boot, как описано здесь. Это также может быть вашим делом.

@InjectMocks
private MyController myController;

@Before
public void setup()
{
    MockitoAnnotations.initMocks(this);

    this.mockMvc = MockMvcBuilders.standaloneSetup(myController)
                    .setViewResolvers(viewResolver())
                    .build();
}

private ViewResolver viewResolver()
{
    InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();

    viewResolver.setPrefix("classpath:templates/");
    viewResolver.setSuffix(".html");

    return viewResolver;
}

Надеюсь это поможет.

Ответ 10

В моем случае я пробовал загрузку Kotlin + Spring и попал в проблему Circular View Path. Все предложения, которые я получил онлайн, не могли помочь, пока я не попробовал следующее:

Первоначально я аннотировал свой контроллер с помощью @Controller

import org.springframework.stereotype.Controller

Затем я заменил @Controller на @RestController

import org.springframework.web.bind.annotation.RestController

И это сработало.

Ответ 11

Для тимелеафа:

Я только начал использовать spring 4 и тимелеаф, когда я столкнулся с этой ошибкой, это было разрешено, добавив:

<bean class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
  <property name="templateEngine" ref="templateEngine" />
  <property name="order" value="0" />
</bean> 

Ответ 12

При использовании аннотации @Controller вам нужны аннотации @RequestMapping и @ResponseBody. Повторите попытку после добавления аннотации @ResponseBody

Ответ 13

Я использую аннотацию для настройки веб-приложения spring, проблема решена путем добавления в конфигурацию InternalResourceViewResolver bean. Надеюсь, это было бы полезно.

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.example.springmvc" })
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Bean
    public InternalResourceViewResolver internalResourceViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/jsp/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
}

Ответ 14

Это происходит потому, что Spring удаляет "preference" и снова добавляет "preference", делая тот же путь, что и Uri запроса.

Бывает так: request Uri: "/preference"

удалить "предпочтение": "/"

путь добавления: "/" + "preference"

конец строки: "/предпочтение"

Это входит в цикл, который Spring уведомляет вас, генерируя исключение.

Лучше всего в ваших интересах дать другое имя представления, например "preferenceView" или что угодно.

Ответ 15

попробуйте добавить в ваш файл gradle зависимость compile ("org.springframework.boot: spring-boot-starter-thymeleaf"). Tymeleaf помогает отображать представления.

Ответ 16

Добавьте аннотацию @ResponseBody к вашему методу return.

Ответ 17

Другой простой подход:

package org.yourpackagename;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

      @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
            return application.sources(PreferenceController.class);
        }


    public static void main(String[] args) {
        SpringApplication.run(PreferenceController.class, args);
    }
}