如何避免“循环视图路径”与 Spring MVC 测试异常

我的一个控制器中有以下代码:

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


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

我只是试图使用 弹簧 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 测试来测试我的应用程序呢?

263423 次浏览

这与 Spring MVC 测试无关。

当您没有声明 ViewResolver时,Spring 注册一个默认的 InternalResourceViewResolver,它创建 JstlView的实例以呈现 View

JstlView类扩展了 InternalResourceView,即

同一 Web 应用程序中的 JSP 或其他资源的包装器。 将模型对象公开为请求属性并转发请求 使用 javax.servlet.RequestDispatcher 访问指定的资源 URL。

这个视图的 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,相同的 servlet (因为前面处理的是同一个 servlet)将处理请求,您将进入一个无止境的循环。


当你声明一个 ThymeleafViewResolver和一个具有特定 prefixsuffixServletContextTemplateResolver时,它会以不同的方式构建 View,给它一个类似于

WEB-INF/web-templates/preference.html

ThymeleafView实例通过使用 ServletContextResourceResolver

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

最终

return servletContext.getResourceAsStream(resourceName);

这将获取一个相对于 ServletContext路径的资源。然后它可以使用 TemplateEngine生成 HTML。这里不可能发生无止境的循环。

我通过使用@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) {

这就是我解决这个问题的方法:

@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();
}

您还可以在.xml 文件中为此创建 bean

<bean id = "viewResolver" class = "org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"/>
<property name="suffix" value=".jsp"/>
</bean

如果您实际上并不关心呈现视图,这里有一个简单的修复方法。

创建 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();
}

我使用注释来配置 spring web 应用程序,这个问题通过在配置中添加一个 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;
}
}

另一个简单的方法是:

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);
}
}

对于 Thymeleaf:

我刚开始使用 spring 4和 thymeleaf,当我遇到这个错误时,它通过添加:

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

当使用 @Controller注释时,您需要 @RequestMapping@ResponseBody注释。 添加注释 @ResponseBody后再试一次

如果您正在使用 Spring Boot,那么将 thymeleaf 依赖项添加到 pom.xml 中:

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

我正在使用 SpringBoot 尝试加载一个网页,而不是测试,并有这个问题。考虑到情况稍有不同,我的解决方案与上述方案略有不同。(尽管这些答案帮助我理解。)

我只需要在 Maven 中更改我的 Spring Boot 启动器依赖项 来自:

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

只要把“网”改成“百里香叶”,问题就解决了。

之所以会发生这种情况,是因为 Spring 正在删除“选项”,并再次附加“选项”,从而产生与请求 Uri 相同的路径。

事情是这样的: 请求尤里: ”/喜好

删除“偏好”: "/"

附加路径: “/”+ “偏好”

结尾字符串: ”/喜好

这就进入了一个循环,Spring 通过抛出异常来通知您。

为了您的利益,最好给出一个不同的视图名称,如“偏好视图”或任何您喜欢的名称。

@Controller@RestController

我也遇到了同样的问题,我注意到我的控制器也用 @Controller进行了注释。用 @RestController代替它解决了这个问题。以下是来自 SpringWebMVC的解释:

@ RestController 是一个组合注释,它本身是元注释的 用@Controller 和@ResponseBody 指示控制器的每个 方法继承类型级别的@ResponseBody 注释,因此 直接写入响应主体与视图解析和呈现 使用 HTML 模板。

尝试将编译依赖项(“ org.springframework.boot: spring-boot-starter-Thymeleaf”)添加到你的 gradle 文件中,Thymeleaf 可以帮助映射视图。

我在 Thymeleaf 中使用 Spring Boot。我就是这么做的。对于 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;
}

希望这个能帮上忙。

/preference之后加入 /为我解决了这个问题:

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

在我的例子中,我正在尝试 Kotlin + Spring 引导,我遇到了循环视图路径的问题。我在网上得到的所有建议都无济于事,直到我尝试了以下方法:

最初,我使用 @Controller对控制器进行了注释

import org.springframework.stereotype.Controller

然后我用 @RestController代替了 @Controller

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

而且成功了。

将注释 @ResponseBody添加到方法返回中。

当运行 Spring Boot + Freemarker 时,如果页面出现:

Whitelabel Error Page 这个应用程序没有显式的/Error 映射,因此您将其视为一个回退。

在 spring-boot-starter-father 2.2.1中,释放版本自由标记不起作用:

  1. 将 Freemarker 文件从.ftl 重命名为.ftlh
  2. 添加到 application.properties: Expal-request-properties = true

后缀 = . ftl

在我的例子中,我在尝试使用 Spring 引导应用程序为 JSP 页面提供服务时遇到了这个问题。

以下是对我有效的方法:

应用性能

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>

如果您没有使用 @RequestBody,而只使用 @Controller,那么最简单的解决方法是使用 @RestController而不是 @Controller

在我的例子中,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");
}
};

在 xml 文件中添加一个视图转换程序

<bean id = "viewResolver" class = "org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"/>
<property name="suffix" value=".jsp"/>
</bean