【问题标题】:How to avoid the "Circular view path" exception with Spring MVC test如何使用 Spring MVC 测试避免“圆形视图路径”异常
【发布时间】: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

标签: spring spring-mvc circular-reference thymeleaf spring-mvc-test


【解决方案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的转发或包含 方法。

强调我的。换句话说,视图在渲染之前将尝试获取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,但我对 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不工作:

        1. 将 Freemarker 文件从 .ftl 重命名为 .ftlh
        2. 添加到 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);
                          }
                      }
                      

                      【讨论】:

                        猜你喜欢
                        • 2017-09-12
                        • 2016-06-28
                        • 1970-01-01
                        • 1970-01-01
                        • 2017-07-02
                        • 2020-12-25
                        • 1970-01-01
                        • 2012-05-20
                        • 2016-03-10
                        相关资源
                        最近更新 更多