Как избежать исключения "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);
}
}