137

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

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

4

22 回答 22

113

@Controller@RestController

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

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

于 2018-04-16T09:52:42.357 回答
108

我通过使用@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) {
于 2013-12-01T12:58:34.463 回答
72

这与 Spring MVC 测试无关。

当您不声明 aViewResolver时,Spring 会注册一个默认值,该默认值InternalResourceViewResolver会创建 的实例JstlView以呈现View.

JstlView扩展InternalResourceView

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

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

强调我的。换句话说,视图在渲染之前会尝试获取RequestDispatcherto which to 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(因为同一个处理前一个)将处理请求,你将进入一个无限循环。


当你用一个特定的and声明 aThymeleafViewResolver和 a时,它会构建不同的,给它一个类似的路径ServletContextTemplateResolverprefixsuffixView

WEB-INF/web-templates/preference.html

ThymeleafView实例ServletContext通过使用 ServletContextResourceResolver

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

最终

return servletContext.getResourceAsStream(resourceName);

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

于 2013-09-15T17:03:51.187 回答
41

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

@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
于 2014-02-13T13:24:28.773 回答
25

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

只需将“网络”更改为“百里香叶”就可以解决我的问题。

于 2018-01-05T13:40:26.893 回答
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();
}
于 2015-01-06T11:45:03.223 回答
13

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

于 2020-03-21T14:15:44.737 回答
13

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

    <dependency>
        <groupId>org.thymeleaf</groupId>
        <artifactId>thymeleaf-spring4</artifactId>
        <version>2.1.6.RELEASE</version>
    </dependency>
于 2017-12-21T08:45:25.807 回答
10

在为我解决问题/后添加:/preference

@Test
public void circularViewPathIssue() throws Exception {
    mockMvc.perform(get("/preference/"))
           .andDo(print());
}
于 2019-03-19T18:31:53.540 回答
8

就我而言,我正在尝试 Kotlin + Spring boot,但遇到了 Circular View Path 问题。在我尝试以下方法之前,我在网上获得的所有建议都无济于事:

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

import org.springframework.stereotype.Controller

然后我替换@Controller@RestController

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

它奏效了。

于 2019-07-17T09:51:28.403 回答
4

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

于 2019-10-09T07:20:48.670 回答
4

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

希望这可以帮助。

于 2018-07-14T14:00:33.133 回答
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

于 2019-11-26T19:41:43.037 回答
2

对于百里香叶:

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

<bean class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
  <property name="templateEngine" ref="templateEngine" />
  <property name="order" value="0" />
</bean> 
于 2016-05-04T08:02:29.677 回答
1

使用@Controller注解时,需要@RequestMapping@ResponseBody注解。添加注释后重试@ResponseBody

于 2017-12-04T17:33:32.190 回答
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;
    }
}
于 2015-05-12T12:58:07.700 回答
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");
            }
        };
于 2021-04-08T13:42:37.813 回答
0

发生这种情况是因为 Spring 正在删除“首选项”并再次附加“首选项”,从而形成与请求 Uri 相同的路径。

像这样发生:请求 Uri:"/preference"

删除“首选项”:“/”

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

结束字符串:“/首选项”

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

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

于 2018-01-21T14:15:42.020 回答
0

就我而言,我在尝试使用 Spring Boot 应用程序提供 JSP 页面时遇到了这个问题。

这对我有用:

应用程序属性

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>
于 2020-02-17T11:43:11.643 回答
0

在 xml 文件中添加视图解析器

<bean id = "viewResolver" class = "org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/view/"/>
    <property name="suffix" value=".jsp"/>
</bean
于 2021-09-16T06:31:07.830 回答
0

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

于 2018-07-09T06:18:38.150 回答
-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);
    }
}
于 2016-04-09T20:20:32.620 回答