如何使用 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 我遇到了同样的问题,但问题是我没有在依赖项下添加。 org.springframework.bootspring-boot-starter-thymeleaf 使用@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&lt;DomainObject&gt; 的序列化版本。 这解决了我在返回 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的转发或包含 方法。

强调我的。换句话说,视图在渲染之前将尝试获取RequestDispatcherforward()。在执行此操作之前,它会检查以下内容

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(因为前一个处理相同)将处理请求,你会进入一个无限循环。


当您使用特定的 prefixsuffix 声明 ThymeleafViewResolverServletContextTemplateResolver 时,它会以不同的方式构建 View,给它一个类似的路径

WEB-INF/web-templates/preference.html

ThymeleafView 实例通过使用 ServletContextResourceResolver

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

最终

return servletContext.getResourceAsStream(resourceName);

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

【讨论】:

@balteo 当您使用ThymleafViewResolver 时,View 被解析为相对于您提供的prefixsuffix 的文件。当您不使用该解析时,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,但我对 test 有 thymeleaf 依赖项。当我删除“测试”范围时,它起作用了。谢谢你的线索! 我也遇到了这个问题,尝试了这个解决方案,但遇到了缺少文件的问题,例如缺少 javax.validation.constraints。我的解决方案是包括解决所有问题的百里香和网络罐子【参考方案6】:

如果您实际上并不关心渲染视图,这里有一个简单的解决方法。

创建一个不检查圆形视图路径的 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,则依赖项将如下所示:&lt;dependency&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-starter-thymeleaf&lt;/artifactId&gt; &lt;/dependency&gt;【参考方案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 测试避免“圆形视图路径”异常的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 spring 3.2 新 mvc 测试登录用户

Spring框架的圆形视图路径异常

Spring框架和AngularJs的圆形视图路径异常

高手如何给 Spring MVC 做单元测试?

如何使用 Spring MVC Test 对多部分 POST 请求进行单元测试?

如何使用 Spring MVC 和 Spring Security 为资源处理程序启用 HTTP 缓存