Как избежать исключения пути кругового представления с помощью теста Spring MVC

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

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


person balteo    schedule 15.09.2013    source источник
comment
Можете ли вы опубликовать ViewResolver, который вы используете, когда он терпит неудачу?   -  person Sotirios Delimanolis    schedule 15.09.2013
comment
@SotiriosDelimanolis: я не уверен, используется ли какой-либо viewResolver в Spring MVC Test. документация   -  person balteo    schedule 15.09.2013
comment
Я столкнулся с той же проблемой, но проблема заключалась в том, что я не добавил ниже зависимость. ‹Dependency› ‹groupId› org.springframework.boot ‹/groupId› ‹artifactId› пружинный ботинок-стартер-тимелист ‹/artifactId› ‹/dependency›   -  person Aamir    schedule 05.12.2016
comment
используйте @RestController вместо @Controller   -  person MozenRath    schedule 21.03.2020


Ответы (21)


Это не имеет ничего общего с тестированием Spring MVC.

Если вы не объявляете 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. Здесь не может быть бесконечного цикла.

person Sotirios Delimanolis    schedule 15.09.2013
comment
@balteo Когда вы используете ThymleafViewResolver, View разрешается как файл относительно предоставленных вами prefix и suffix. Когда вы не используете это решение, Spring использует значение по умолчанию InternalResourceViewResolver, которое находит ресурсы с _ 6_. Этот ресурс может быть Servlet. В данном случае это потому, что путь /preference соответствует вашему DispatcherServlet. - person Sotirios Delimanolis; 15.09.2013
comment
@balteo Чтобы протестировать приложение, укажите правильный ViewResolver. Либо ThymeleafViewResolver, как в вашем вопросе, ваш собственный настроенный InternalResourceViewResolver, либо измените имя представления, которое вы возвращаете в своем контроллере. - person Sotirios Delimanolis; 15.09.2013
comment
@ShirgillFarhanAnsari Метод-обработчик с аннотациями @RequestMapping с типом возврата String (без @ResponseBody) имеет возвращаемое значение, обрабатываемое ViewNameMethodReturnValueHandler, которое интерпретирует String как имя представления и использует его для выполнения процесса, который я объясняю в своем ответе. С @ResponseBody Spring MVC вместо этого будет использовать RequestResponseBodyMethodProcessor, который вместо этого записывает String непосредственно в ответ HTTP, т.е. нет разрешения просмотра. - person Sotirios Delimanolis; 17.12.2017
comment
@valik Не / в примере OP, их путь будет /context/preference. Но да, DispatcherServlet обнаруживает, что метод обработчика переадресует его по тому же пути и что DispatcherServlet обработает его точно так же, как только что, и, следовательно, войдет в цикл. - person Sotirios Delimanolis; 26.03.2018

Я решил эту проблему, используя @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) {
person Deepti Kohli    schedule 01.12.2013
comment
Они хотят вернуть HTML, разрешив представление, а не вернуть сериализованную версию List<DomainObject>. - person Sotirios Delimanolis; 12.05.2015
comment
Это решило мою проблему при возврате ответа JSON для веб-службы Spring rest. - person Joe; 04.08.2017
comment
Хорошо, если я не укажу Produce = {application / json}, он все равно будет работать. Создает ли он по умолчанию json? - person Jay; 12.10.2017

@Controller@RestController

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

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

person Boris    schedule 16.04.2018
comment
@TodorTodorov Это помогло мне - person Igor Rodriguez; 20.05.2019
comment
@TodorTodorov и мне! - person Ran; 20.06.2019
comment
У меня тоже сработало. У меня был @ControllerAdvice с handleXyException методом, который возвращал мой собственный объект вместо ResponseEntity. Добавление @RestController поверх аннотации @ControllerAdvice сработало, и проблема исчезла. - person Igor; 20.07.2019
comment
У меня это сработало - у меня было @ControllerAdvice вместо @RestControllerAdvice, что вызывало ту же проблему. Спасибо! - person Brice Frisco; 27.12.2020
comment
Это та же проблема, что и ответ Дипти. OP пытается вернуть имя представления и отобразить шаблон HTML с помощью Thymeleaf. Они не пытаются вернуть String "preference" в качестве содержимого ответа. - person Sotirios Delimanolis; 14.02.2021

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

@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();
    }
person Piotr Sagalara    schedule 13.02.2014
comment
Это только для тестовых случаев. Не для контроллеров. - person cst1992; 20.07.2016
comment
Помогал кому-то устранять эту проблему в одном из их новых модульных тестов, это именно то, что мы искали. - person Bradford2000; 28.07.2016
comment
Я использовал это, но, несмотря на то, что в тесте я указал неправильный префикс и суффикс для моего распознавателя, это сработало. Можете ли вы обосновать это, почему это необходимо? - person dushyantashu; 11.09.2017

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

Мне просто пришлось изменить мою стартовую зависимость Spring Boot в Maven с:

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

to:

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

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

person Old Schooled    schedule 05.01.2018
comment
Для меня не было необходимости менять стартовую сеть, но у меня была зависимость тимелеафа с тестом ‹scope› ‹/scope›. Когда я снял прицел, все заработало. Спасибо за подсказку! - person Georgina Diaz; 31.03.2020
comment
У меня тоже была эта проблема, я попробовал это решение, и у меня были проблемы с отсутствующими файлами, такими как отсутствующий javax.validation.constraints. Мое решение заключалось в том, чтобы включить как тимелист, так и веб-банки, которые разрешили все. - person Dave; 26.11.2020

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

Создайте подкласс 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();
}
person Dave Bower    schedule 06.01.2015
comment
Это устранило мою проблему. Я просто добавил класс StandaloneMvcTestViewResolver в тот же каталог тестов и использовал его в MockMvcBuilders, как описано выше. Спасибо - person Matheus Araujo; 08.02.2017
comment
У меня была такая же проблема, и это тоже решило ее для меня. Большое спасибо! - person Johan; 23.02.2017
comment
Это отличное решение, которое (1) не требует изменения контроллеров и (2) может быть повторно использовано во всех тестовых классах с одним простым импортом для каждого класса. +1 - person Nander Speerstra; 18.04.2018
comment
Олди, но Голди! Спас мой день. Спасибо за обходной путь +1 - person Raistlin; 01.10.2018

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

    <dependency>
        <groupId>org.thymeleaf</groupId>
        <artifactId>thymeleaf-spring4</artifactId>
        <version>2.1.6.RELEASE</version>
    </dependency>
person Sarvar Nishonboyev    schedule 21.12.2017
comment
Голосовать за. Отсутствие зависимости Thymeleaf было причиной этой ошибки в моем проекте. Однако, если вы используете Spring Boot, зависимость будет выглядеть так: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> - person peterh; 25.08.2019

если вы не использовали @RequestBody и используете только @Controller, самый простой способ исправить это - использовать @RestController вместо @Controller

person MozenRath    schedule 21.03.2020
comment
это не исправить, теперь он будет отображать имя вашего файла, а не шаблон - person Ashish Kamble; 24.05.2020
comment
это зависит от реальной проблемы. эта ошибка может возникнуть по многим причинам - person MozenRath; 27.05.2020

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

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

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

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

import org.springframework.stereotype.Controller

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

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

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

person johnmilimo    schedule 17.07.2019

Я использую 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;
}

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

person Pedro Lopez    schedule 14.07.2018

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

person Ishaan Arora    schedule 09.10.2019
comment
Пожалуйста, включите объяснение того, как и почему это решает проблему, это действительно поможет улучшить качество вашего сообщения и, возможно, приведет к большему количеству голосов за. - person Android; 09.10.2019

При запуске Spring Boot + Freemarker, если страница появляется:

Страница ошибки Whitelabel В этом приложении нет явного сопоставления для / error, поэтому вы рассматриваете это как запасной вариант.

В версии spring-boot-starter-parent 2.2.1.RELEASE freemarker не работает:

  1. переименовать файлы Freemarker из .ftl в .ftlh
  2. Добавьте в application.properties: spring.freemarker.expose-request-attributes = true

spring.freemarker.suffix = .ftl

person Max    schedule 26.11.2019
comment
Простое переименование файлов Freemarker из .ftl в .ftlh решило проблему для меня. - person jannnik; 20.01.2020
comment
Блин ... Я должен тебе пива. Я потерял весь свой день из-за этого переименования. - person julianobrasil; 02.06.2020

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

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

<bean class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
  <property name="templateEngine" ref="templateEngine" />
  <property name="order" value="0" />
</bean> 
person Carlos H. Raymundo    schedule 04.05.2016

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

person Gowri Ayyanar    schedule 04.12.2017

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

@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;
    }
}
person alijandro    schedule 12.05.2015
comment
Спасибо, это отлично работает для меня. Мое приложение сломалось после обновления до весенней загрузки 1.3.1 с 1.2.7, и была только эта строка, в которой произошел сбой реестра .addViewController (/ login) .setViewName (login); При регистрации этого bean-компонента приложение снова заработало ... по крайней мере, вход в систему прошел. - person le0diaz; 24.12.2015

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

Происходит так: запрос Uri: "/ preference"

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

добавить путь: "/" + "предпочтение"

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

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

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

person xpioneer    schedule 21.01.2018

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

person aishwarya kore    schedule 09.07.2018

В моем случае у меня возникла эта проблема при попытке обслуживать страницы JSP с помощью загрузочного приложения Spring.

Вот что у меня сработало:

application.properties

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

pom.xml

Чтобы включить поддержку JSP, нам нужно добавить зависимость от tomcat-embed-jasper.

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>provided</scope>
</dependency>
person Faouzi    schedule 17.02.2020

В моем случае круговой путь просмотра в Spring boot 2 и jdk 11 был исправлен путем перенаправления на index.html:

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            }
            @Override
            public void addViewControllers(ViewControllerRegistry registry) {
                registry.addViewController("/").setViewName("redirect:/index.html");
            }
        };
person sermyro    schedule 08.04.2021

Еще один простой подход:

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);
    }
}
person Toothless Seer    schedule 09.04.2016