如何使用 Spring MVC 测试避免“圆形视图路径”异常
Posted
技术标签:
【中文标题】如何使用 Spring MVC 测试避免“圆形视图路径”异常【英文标题】:How to avoid the "Circular view path" exception with Spring MVC test 【发布时间】:2013-09-19 18:04:04 【问题描述】:我的一个控制器中有以下代码:
@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());
我收到以下异常:
圆形视图路径 [preference]:将调度回当前 处理程序 URL [/preference] 再次。检查您的 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 测试来测试我的应用程序呢?
【问题讨论】:
你能发布你在失败时使用的ViewResolver
吗?
@SotiriosDelimanolis:我不确定 Spring MVC 测试是否使用了任何 viewResolver。 documentation
我遇到了同样的问题,但问题是我没有在依赖项下添加。 @RestController
而不是@Controller
【参考方案1】:
@Controller
→ @RestController
我遇到了同样的问题,我注意到我的控制器也用@Controller
注释。用@RestController
替换它解决了这个问题。以下是Spring Web MVC的解释:
@RestController 是一个组合注解,它本身是元注解的 @Controller 和 @ResponseBody 表示一个控制器,其每个 方法继承了类型级别的 @ResponseBody 注释,因此 直接写入响应正文与视图分辨率和渲染 使用 HTML 模板。
【讨论】:
@TodorTodorov 这对我有用 @TodorTodorov 和我! 也为我工作。我有一个@ControllerAdvice
,其中有一个handleXyException
方法,它返回我自己的对象而不是ResponseEntity。在@ControllerAdvice
注释之上添加@RestController
有效,问题消失了。
这对我有用 - 我有 @ControllerAdvice
而不是 @RestControllerAdvice
这导致了同样的问题。谢谢!
这和 Deepti 的回答有同样的问题。 OP 正在尝试返回视图名称并使用 Thymeleaf 呈现 HTML 模板。他们不会尝试将 String
"preference"
作为响应内容返回。【参考方案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)
【讨论】:
他们希望通过解析视图返回 HTML,而不是返回List<DomainObject>
的序列化版本。
这解决了我在返回 Spring REST Web 服务的 JSON 响应时遇到的问题。
很好,如果我不指定produces = "application/json",它仍然有效。默认会生成json吗?【参考方案3】:
这与 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(因为前一个处理相同)将处理请求,你会进入一个无限循环。
当您使用特定的 prefix
和 suffix
声明 ThymeleafViewResolver
和 ServletContextTemplateResolver
时,它会以不同的方式构建 View
,给它一个类似的路径
WEB-INF/web-templates/preference.html
ThymeleafView
实例通过使用
ServletContextResourceResolver
templateInputStream = resourceResolver.getResourceAsStream(templateProcessingParameters, resourceName);`
最终
return servletContext.getResourceAsStream(resourceName);
这将获得一个相对于ServletContext
路径的资源。然后它可以使用TemplateEngine
生成HTML。这里不可能发生无限循环。
【讨论】:
@balteo 当您使用ThymleafViewResolver
时,View
被解析为相对于您提供的prefix
和suffix
的文件。当您不使用该解析时,Spring 使用默认的InternalResourceViewResolver
来查找具有RequestDispatcher
的资源。此资源可以是Servlet
。在这种情况下,这是因为路径 /preference
映射到您的 DispatcherServlet
。
@balteo 要测试您的应用,请提供正确的ViewResolver
。您的问题中的ThymeleafViewResolver
、您自己配置的InternalResourceViewResolver
或更改您在控制器中返回的视图名称。
@ShirgillFarhanAnsari 带有String
返回类型(并且没有@ResponseBody
)的@RequestMapping
注释处理程序方法的返回值由ViewNameMethodReturnValueHandler
处理,该ViewNameMethodReturnValueHandler
将字符串解释为视图名称,并用它来完成我在回答中解释的过程。使用@ResponseBody
,Spring MVC 将改为使用RequestResponseBodyMethodProcessor
,而是将字符串直接写入 HTTP 响应,即。没有视图分辨率。
@valik 在 OP 的示例中不是 /
,它们的路径将是 /context/preference
。但是是的,DispatcherServlet
检测到处理程序方法将转发到相同的路径,并且DispatcherServlet
会以完全相同的方式处理它,因此进入循环。【参考方案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();
您也可以在 .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
【讨论】:
这仅适用于测试用例。不适用于控制器。 正在帮助某人在他们的一个新单元测试中解决此问题,这正是我们所寻找的。span> 我使用了这个,但是尽管在测试中为我的解析器提供了错误的前缀和后缀,它仍然有效。您能否提供这背后的原因,为什么需要这样做? 这是测试的完美答案,正是我想要的!【参考方案5】:我正在使用 Spring Boot 尝试加载网页,而不是测试,并且遇到了这个问题。考虑到略有不同的情况,我的解决方案与上述解决方案略有不同。 (尽管这些答案帮助我理解了。)
我只需要在 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>
只需将 'web' 更改为 'thymeleaf' 就为我解决了这个问题。
【讨论】:
对我来说,没有必要更改 starter-web,但我对如果您实际上并不关心渲染视图,这里有一个简单的解决方法。
创建一个不检查圆形视图路径的 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();
【讨论】:
这解决了我的问题。我刚刚在测试的同一目录中添加了一个 StandaloneMvcTestViewResolver 类,并如上所述在 MockMvcBuilders 中使用它。谢谢 我遇到了同样的问题,这也为我解决了这个问题。非常感谢! 这是一个很好的解决方案,(1) 不需要更改控制器,(2) 可以在所有测试类中重用,每个类只需一个简单的导入。 +1 老歌但老歌!拯救了我的一天。感谢您提供此解决方法 +1【参考方案7】:如果您使用的是 Spring Boot,则将 thymeleaf 依赖项添加到您的 pom.xml 中:
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring4</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
【讨论】:
点赞。缺少 Thymeleaf 依赖项是导致我的项目出现此错误的原因。但是,如果您使用的是 Spring Boot,则依赖项将如下所示:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
【参考方案8】:
如果您没有使用@RequestBody
并且只使用@Controller
,解决此问题的最简单方法是使用@RestController
而不是@Controller
【讨论】:
这不是修复,现在它会显示你的文件名,而不是显示模板 这取决于实际问题。发生此错误的原因有很多【参考方案9】:在/preference
之后添加/
为我解决了这个问题:
@Test
public void circularViewPathIssue() throws Exception
mockMvc.perform(get("/preference/"))
.andDo(print());
【讨论】:
【参考方案10】:就我而言,我正在尝试 Kotlin + Spring boot,但遇到了 Circular View Path 问题。在我尝试以下方法之前,我在网上获得的所有建议都无济于事:
最初我使用@Controller
注释我的控制器
import org.springframework.stereotype.Controller
然后我将@Controller
替换为@RestController
import org.springframework.web.bind.annotation.RestController
它奏效了。
【讨论】:
【参考方案11】:我正在使用带有 Thymeleaf 的 Spring Boot。这对我有用。 JSP 有类似的答案,但请注意,我使用的是 HTML,而不是 JSP,它们位于文件夹 src/main/resources/templates
中,就像在标准 Spring Boot 项目中一样,如 here 所述。这也可能是您的情况。
@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;
希望这会有所帮助。
【讨论】:
【参考方案12】:将注解@ResponseBody
添加到您的方法返回中。
【讨论】:
请说明如何以及为什么解决问题将真正有助于提高您的帖子质量,并可能导致更多的赞成票。【参考方案13】:运行Spring Boot + Freemarker时如果出现页面:
Whitelabel 错误页面此应用程序没有显式映射 / 错误,因此您将其视为后备。
在spring-boot-starter-parent 2.2.1.RELEASE版本freemarker不工作:
-
将 Freemarker 文件从 .ftl 重命名为 .ftlh
添加到 application.properties:
spring.freemarker.expose-request-attributes = true
spring.freemarker.suffix = .ftl
【讨论】:
简单地将 Freemarker 文件从 .ftl 重命名为 .ftlh 为我解决了这个问题。 伙计...我欠你一杯啤酒。因为这个重命名的事情,我失去了一整天。【参考方案14】:对于百里香:
我刚开始使用spring 4和thymeleaf,遇到这个错误后通过添加解决:
<bean class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
<property name="templateEngine" ref="templateEngine" />
<property name="order" value="0" />
</bean>
【讨论】:
【参考方案15】:使用@Controller
注解时,需要@RequestMapping
和@ResponseBody
注解。
加注后重试@ResponseBody
【讨论】:
【参考方案16】:我使用注解来配置spring web app,通过在配置中添加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;
【讨论】:
谢谢这对我来说很好。我的应用程序在从 1.2.7 升级到 spring boot 1.3.1 后坏了,只有这一行失败了 registry.addViewController("/login").setViewName("login");注册该 bean 时,应用程序再次运行……至少登录成功了。【参考方案17】:发生这种情况是因为 Spring 正在删除“首选项”并再次附加“首选项”,使路径与请求 Uri 相同。
像这样发生: 请求URI: “/偏好”
删除“偏好”: "/"
附加路径: "/"+"偏好"
结束字符串: “/偏好”
这进入了一个循环,Spring 通过抛出异常来通知你。
为您提供不同的视图名称(如“preferenceView”或您喜欢的任何名称)最符合您的利益。
【讨论】:
【参考方案18】:尝试将 compile("org.springframework.boot:spring-boot-starter-thymeleaf") 依赖添加到您的 gradle 文件中。Thymeleaf 有助于映射视图。
【讨论】:
【参考方案19】:就我而言,我在尝试使用 Spring Boot 应用程序提供 JSP 页面时遇到了这个问题。
这对我有用:
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>
【讨论】:
【参考方案20】:在我的例子中,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");
;
【讨论】:
【参考方案21】:在 xml 文件中添加视图解析器
<bean id = "viewResolver" class = "org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"/>
<property name="suffix" value=".jsp"/>
</bean
【讨论】:
【参考方案22】:另一种简单的方法:
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);
【讨论】:
以上是关于如何使用 Spring MVC 测试避免“圆形视图路径”异常的主要内容,如果未能解决你的问题,请参考以下文章