【问题标题】: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的转发或包含 方法。

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

        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
                        相关资源
                        最近更新 更多