Spring MVC测试中如何避免“圆形视图路径”异常


117

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

@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测试来测试我的应用程序呢?


1
ViewResolver发生故障时,您可以发布 您使用的内容吗?
Sotirios Delimanolis

@SotiriosDelimanolis:我不确定Spring MVC测试是否使用了任何viewResolver。文档
balteo

8
我面临着同样的问题,但问题是我还没有添加下面的依赖项。<dependency> <groupId> org.springframework.boot </ groupId> <artifactId> spring-boot-starter-thymeleaf </ artifactId> </ dependency>
aamir

使用@RestController代替@Controller
MozenRath

Answers:


65

这与Spring MVC测试无关。

当您不声明a时ViewResolver,Spring将注册一个默认值InternalResourceViewResolver,该默认值创建JstlView用于渲染的实例View

JstlView类扩展InternalResourceView其是

同一Web应用程序中的JSP或其他资源的包装。将模型对象公开为请求属性,然后使用javax.servlet.RequestDispatcher将请求转发到指定的资源URL。

该视图的URL应该在Web应用程序中指定一个资源,适用于RequestDispatcher的forward或include方法。

大胆是我的。。换句话说,视图渲染之前,将尝试获得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(因为相同的servlet处理前一个)将处理请求,并且您将陷入无限循环。


当您使用特定的and 声明a ThymeleafViewResolver和a ServletContextTemplateResolver时,它会以不同的方式构建,并为它提供如下路径prefixsuffixView

WEB-INF/web-templates/preference.html

ThymeleafView实例使用以下命令相对于ServletContext路径 定位文件ServletContextResourceResolver

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

最终

return servletContext.getResourceAsStream(resourceName);

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


1
感谢您的详细答复。我确实了解为什么在使用Thymeleaf时不会发生循环,以及为什么在不使用Thymeleaf视图解析器时会发生循环。但是,我仍然不确定如何更改我的配置,以便可以测试我的应用...
balteo

1
@balteo在使用时ThymleafViewResolver,会将View解析为相对于的文件,prefix然后suffix提供。当您不使用该解析时,Spring使用默认值InternalResourceViewResolver查找带有以下内容的资源:RequestDispatcher。此资源可以是Servlet。在这种情况下是因为路径/preference映射到您的DispatcherServlet
Sotirios Delimanolis

2
@balteo要测试您的应用,请提供正确的ViewResolver。要么ThymeleafViewResolver您可以使用问题中 as,您自己的配置InternalResourceViewResolver或更改要在控制器中返回的视图名称。
Sotirios Delimanolis

谢谢你,谢谢你,谢谢你!我不知道为什么内部资源视图解析器更喜欢转发而不是“包含”,但是现在有了您的解释,似乎在名称中使用“资源”似乎有点模棱两可。这个解释很出色。
克里斯·汤普森

2
@ShirgillFarhanAnsari @RequestMapping具有String返回类型(和no @ResponseBody)的带注释的处理程序方法的返回值ViewNameMethodReturnValueHandler由来处理,后者将String解释为视图名称,并使用它来完成我在答案中解释的过程。使用@ResponseBody,Spring MVC将改为使用RequestResponseBodyMethodProcessor,而直接将String写入HTTP响应,即。没有视图分辨率。
Sotirios Delimanolis

97

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

10
他们想通过解析视图来返回HTML,而不是返回的序列化版本List<DomainObject>
Sotirios Delimanolis,2015年

2
这解决了我的问题,而返回春季REST Web服务的JSON响应..

很好,如果我不指定produces = {“ application / json”},它仍然可以工作。默认情况下会产生json吗?
杰伊(Jay)

74

@Controller@RestController

我遇到了同样的问题,并且我注意到我的控制器也带有注释@Controller。替换为已@RestController解决问题。这是Spring Web MVC的解释:

@RestController是一个组合的批注,其本身带有@Controller和@ResponseBody的元注释,表示一个控制器,其每个方法都继承了类型级别的@ResponseBody批注,因此直接将其写入响应主体与视图分辨率并使用HTML模板进行呈现。


1
@TodorTodorov它为我做了
Igor Rodriguez

@TodorTodorov和对我来说!

3
也为我工作。我有一个@ControllerAdvice带有handleXyException方法的方法,它返回我自己的对象而不是ResponseEntity。@RestController@ControllerAdvice注释的顶部进行添加可以解决问题。
伊戈尔(Igor)

36

这是我解决此问题的方法:

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

1
这仅适用于测试用例。不适用于控制器。
cst1992 '16

2
在帮助某人在其新的单元测试之一中解决此问题时,这正是我们想要的。
Bradford2000

我使用了它,但是尽管在测试中为解析器提供了错误的前缀和后缀,但它仍然有效。您可以在此后面提供推理吗,为什么要这样做?
dushyantashu

该答案应被投票为最正确和最具体
咖啡因编码器

20

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

只是将“网络”更改为“百里香”对我来说就解决了这个问题。


1
对我来说,不必更改启动程序网络,但是我对<scope> test </ scope>有百里香依赖。当我删除“测试”范围时,它起作用了。感谢您的提示!
乔治娜·迪亚兹

16

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

创建一个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中使用了它。谢谢
Matheus Araujo

我遇到了同样的问题,这也为我解决了。非常感谢!
约翰·约翰(Johan)

这是一个很棒的解决方案,(1)不需要更改控制器,并且(2)可以在所有测试类中重用,每个类只需一个简单的导入即可。+1
Nander Speerstra

老歌,但戈尔迪!拯救了我的一天。感谢您提供此解决方法+1
Raistlin '18年

13

如果您使用的是Spring Boot,则将thymeleaf依赖项添加到pom.xml中:

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

1
赞成。缺少Thymeleaf依赖性是导致我的项目出现此错误的原因。但是,如果您使用的是Spring Boot,则依赖项应改为:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
peterh


8

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

最初,我使用 @Controller

import org.springframework.stereotype.Controller

我再换@Controller@RestController

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

而且有效。


6

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


此问题尚未解决,现在将显示您的文件名,而是显示模板
Ashish Kamble

1
这取决于实际问题。由于许多原因,可能会发生此错误
MozenRath


3

我在Thymeleaf中使用Spring Boot。这对我有用。没有与JSP类似的答案,但请注意,我用HTML,JSP不,这些都是在文件夹中src/main/resources/templates就像在一个标准的Spring引导项目作为解释在这里。这也可能是您的情况。

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

希望这可以帮助。


3

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


1
只需将Freemarker文件从.ftl重命名为.ftlh即可为我解决问题。
jannnik

老兄...我欠你一杯啤酒。由于这一重命名的事情,我失去了一整天。
julianobrasil

2

对于Thymeleaf:

我刚开始使用spring 4和thymeleaf,遇到此错误时,通过添加以下内容解决了:

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


0

我使用批注来配置Spring Web应用程序,该问题通过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后,我的应用程序中断了,只有这行失败了。在注册该bean时,该应用程序又可以工作了……至少登录名消失了。
le0diaz 2015年

0

发生这种情况是因为Spring删除了“首选项”,并再次附加了“首选项”,使其与请求Uri的路径相同。

发生这样的情况:request Uri:“ / preference”

删除“首选项”:“ /”

追加路径:“ /” +“首选项”

结束字符串:“ / preference”

这进入了一个循环,Spring通过抛出异常来通知您。

为了您的利益,最好提供一个不同的视图名称,例如“ preferenceView”或您喜欢的任何名称。


0

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


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>

-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);
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.