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

时间:2013-09-15 14:42:57

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

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

@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测试来测试我的应用程序?有人有任何线索吗?

21 个答案:

答案 0 :(得分:81)

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

答案 1 :(得分:54)

这与Spring MVC测试无关。

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

JstlView类扩展了InternalResourceView

  

同一Web应用程序中的JSP或其他资源的包装器。   将模型对象公开为请求属性并转发请求   使用javax.servlet.RequestDispatcher到指定的资源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(因为处理前一个)会处理请求,你会进入无限循环。


当您使用特定ThymeleafViewResolverServletContextTemplateResolver声明prefixsuffix时,它会以不同方式构建View,为其提供类似<的路径/ p>

WEB-INF/web-templates/preference.html

ThymeleafView个实例使用a定位相对于ServletContext路径的文件 ServletContextResourceResolver

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

最终

return servletContext.getResourceAsStream(resourceName);

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

答案 2 :(得分:31)

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

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

答案 3 :(得分:31)

@Controller@RestController

我有同样的问题,我注意到我的控制器也注明了@Controller。用@RestController替换它解决了这个问题。以下是Spring Web MVC的解释:

  

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

答案 4 :(得分:13)

如果您真的不关心渲染视图,可以轻松解决此问题。

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

答案 5 :(得分:11)

如果您使用的是Spring Boot,请将百万美元依赖项添加到您的pom.xml中:

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

答案 6 :(得分:9)

如果您未使用@RequestBody且仅使用@Controller,则解决此问题的最简单方法是使用@RestController而不是@Controller

答案 7 :(得分:9)

我正在使用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>

只需更改网络&#39;到了百里香的&#39;为我解决了这个问题。

答案 8 :(得分:3)

我正在将Spring Boot与Thymeleaf一起使用。这对我有用。 JSP也有类似的答案,但请注意,我使用的是HTML,而不是JSP,它们在文件夹src/main/resources/templates中,就像在标准here中所述的标准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;
}

希望这会有所帮助。

答案 9 :(得分:2)

对于Thymeleaf:

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

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

答案 10 :(得分:1)

使用@Controller注释时,您需要@RequestMapping@ResponseBody注释。 添加注释@ResponseBody

后再试一次

答案 11 :(得分:1)

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

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

答案 12 :(得分:1)

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

答案 13 :(得分:0)

在我的情况下,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");
            }
        };

答案 14 :(得分:0)

就我而言,在尝试使用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>

答案 15 :(得分:0)

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

答案 16 :(得分:0)

就我而言,我正在尝试Kotlin + Spring引导,但是遇到了“环形视图路径”问题。在尝试以下方法之前,我上网获得的所有建议都无济于事:

最初我使用@Controller注释了我的控制器

import org.springframework.stereotype.Controller

然后我将@Controller替换为@RestController

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

它奏效了。

答案 17 :(得分:0)

尝试在您的gradle文件中添加compile(“ org.springframework.boot:spring-boot-starter-thymeleaf”)依赖项。Thymeleaf帮助映射视图。

答案 18 :(得分:0)

这种情况正在发生,因为Spring正在删除&#34;偏好&#34;并附加&#34;偏好&#34;再次创建与请求Uri相同的路径。

发生这样的事: 请求Uri: &#34; /偏好&#34;

删除&#34;偏好&#34;: &#34; /&#34;

追加路径: &#34; /&#34; +&#34;优选&#34;

结束字符串: &#34; /偏好&#34;

这是一个循环,Spring通过抛出异常来通知你。

最适合您提供不同的视图名称,例如&#34; preferenceView&#34;或者你喜欢的任何东西。

答案 19 :(得分:0)

我使用注释来配置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;
    }
}

答案 20 :(得分:-2)

另一种简单方法:

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