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

问题描述 投票:85回答:15

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

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

spring spring-mvc circular-reference thymeleaf spring-mvc-test
15个回答
56
投票

这与Spring MVC测试无关。

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

JstlView类扩展了InternalResourceView

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

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

大胆是我的。换句话说,在渲染之前,视图将尝试获取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声明一个prefix和一个suffix时,它会以不同的方式构建View,给它一条路径

WEB-INF/web-templates/preference.html

ThymeleafView实例使用ServletContext找到相对于ServletContextResourceResolver路径的文件

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

最终

return servletContext.getResourceAsStream(resourceName);

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


2
投票

Thimeleaf堡:

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

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

1
投票

使用@Controller注释时,需要@RequestMapping@ResponseBody注释。添加注释@ResponseBody后再试一次


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

0
投票

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

发生这样的事情:请求Uri:“/ preference”

删除“首选项”:“/”

追加路径:“/”+“偏好”

结束字符串:“/ preference”

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

为您提供不同的视图名称,例如“preferenceView”或您喜欢的任何内容,最符合您的利益。


0
投票

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


0
投票

就我而言,我正在尝试Kotlin + Spring启动,我进入了循环视图路径问题。我上网的所有建议都无济于事,直到我尝试了以下内容:

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

import org.springframework.stereotype.Controller

然后我用@Controller取代了@RestController

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

它奏效了。


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

82
投票

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

36
投票

@Controller @RestController

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

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


32
投票

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

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

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

11
投票

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

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

11
投票

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

只是将'网络'改为'百里香',为我解决了问题。


3
投票

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

希望这可以帮助。


3
投票

/之后添加/preference解决了我的问题:

@Test
public void circularViewPathIssue() throws Exception {
    mockMvc.perform(get("/preference/"))
           .andDo(print());
}
© www.soinside.com 2019 - 2024. All rights reserved.