Spring MVC 테스트에서“Circular view path”예외를 피하는 방법


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 2013 년

@SotiriosDelimanolis : Spring MVC 테스트에서 viewResolver를 사용하는지 확실하지 않습니다. 문서
balteo 2013-09-15

8
나는 같은 문제에 직면했지만 문제는 종속성 아래에 추가하지 않았다는 것입니다. <의존성> <의 groupId> org.springframework.boot </의 groupId> <artifactId를> 스프링 부팅 스타터 thymeleaf </ artifactId를> </ 의존성>
아미르

@RestController대신 사용@Controller
MozenRath

답변:


65

이것은 Spring MVC 테스트와 관련이 없습니다.

당신이를 선언하지 않는 경우 ViewResolver, Spring은 기본 등록 InternalResourceViewResolver의 인스턴스를 생성 JstlView을 렌더링을 View.

JstlView클래스는 확장 InternalResourceView되는

동일한 웹 응용 프로그램 내의 JSP 또는 기타 리소스에 대한 래퍼입니다. 모델 객체를 요청 속성으로 노출하고 javax.servlet.RequestDispatcher를 사용하여 지정된 리소스 URL로 요청을 전달합니다.

이보기의 URL은 RequestDispatcher의 전달 또는 포함 메소드에 적합한 웹 애플리케이션 내의 자원을 지정해야합니다.

Bold는 내 것입니다. 즉, 렌더링하기 전에 뷰는를 가져 오려고 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경우 동일한 서블릿 (동일한 서블릿이 이전에 처리되었으므로)이 요청을 처리하고 무한 루프에 빠진다는 것을 알고 있습니다.


ThymeleafViewResolvera ServletContextTemplateResolver와 a를 특정 prefixand로 선언 suffix하면 View다르게 빌드되어 다음 과 같은 경로를 제공합니다.

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 받는 파일 상대적으로 해결 하고 당신은 제공합니다. 해결 되는 것을 사용하지 않으면 Spring은 . 이 리소스는 . 경로가 있기 때문에이 경우는 당신의 매핑 . ThymleafViewResolverViewprefixsuffixInternalResourceViewResolverRequestDispatcherServlet/preferenceDispatcherServlet
Sotirios Delimanolis 2013 년

2
@balteo 앱을 테스트하려면 올바른 ViewResolver. 중 하나를 ThymeleafViewResolver귀하의 질문처럼, 자신의 구성 InternalResourceViewResolver또는 컨트롤러에 반환하는 뷰의 이름을 변경합니다.
Sotirios Delimanolis 2013 년

감사합니다, 감사합니다, 감사합니다! 내부 리소스 뷰 리졸버가 "포함"보다는 포워드를 선호하는 이유를 알 수 없었지만 이제는 설명을 통해 이름에 "리소스"를 사용하는 것이 약간 모호한 것처럼 보입니다. 이 설명은 훌륭합니다.
Chris Thompson

2
@ShirgillFarhanAnsari 반환 유형 (및 no )이 있는 @RequestMapping주석 처리기 메서드 는 String을 뷰 이름으로 해석하고이를 사용하여 내 대답에서 설명하는 프로세스를 수행 하는 a에 의해 처리되는 반환 값을 가지고 있습니다. 를 사용 하면 Spring MVC는 대신 HTTP 응답에 직접 문자열을 쓰는 대신 사용 합니다. 보기 해상도가 없습니다. String@ResponseBodyViewNameMethodReturnValueHandler@ResponseBodyRequestResponseBodyMethodProcessor
소티 리오스 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
.NET Framework의 직렬화 된 버전을 반환하는 것이 아니라 뷰를 확인하여 HTML을 반환하려고합니다 List<DomainObject>.
Sotirios Delimanolis 2015 년

2
이것은 Spring rest 웹 서비스에 대한 JSON 응답을 반환하는 동안 내 문제를 해결했습니다.
Joe

좋습니다. 생성 = { "application / json"}을 지정하지 않으면 여전히 작동합니다. 기본적으로 json을 생성합니까?
제이

74

@Controller@RestController

나는 같은 문제가 있었고 내 컨트롤러에도 @Controller. 교체 @RestController하면 문제 가 해결되었습니다. 다음은 Spring Web MVC 의 설명입니다 .

@RestController는 자체적으로 @Controller 및 @ResponseBody로 메타 주석이 추가 된 구성된 주석으로, 모든 메서드가 유형 수준 @ResponseBody 주석을 상속하므로 응답 본문에 직접 쓰기와 HTML 템플릿을 사용한 렌더링 비교를 나타냅니다.


1
@TodorTodorov 그것은 나를 위해 한
이고르 로드리게스에게

@TodorTodorov 그리고 나를 위해!

3
나를 위해 일했습니다. 나는 ResponseEntity 대신 내 자신의 개체를 반환 @ControllerAdvice하는 handleXyException메서드가 있습니다. 주석 @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

2
누군가가 새로운 단위 테스트 중 하나에서이 문제를 해결하도록 돕고 있었는데, 이것이 바로 우리가 찾고 있던 것입니다.
Bradford2000

나는 이것을 사용했지만 테스트에서 내 해석기에 잘못된 접두사와 접미사를 제공했지만 작동했습니다. 이에 대한 추론을 제공 할 수 있습니까? 이것이 필요한 이유는 무엇입니까?
dushyantashu

이 답변은 가장 정확하고 구체적으로 투표해야합니다
Caffeine Coder

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>

'웹'을 'thymeleaf'로 변경하면 문제가 해결되었습니다.


1
저에게는 시작 웹을 변경할 필요가 없었지만 <scope> test </ scope>에 thymeleaf 종속성이있었습니다. "테스트"범위를 제거하면 작동했습니다. 단서 감사합니다!
Georgina Diaz

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 2017

나는 같은 문제가 있었고 이것은 나에게도 해결되었습니다. 감사합니다!
Johan

이것은 (1) 컨트롤러를 변경할 필요가없고 (2) 클래스 당 하나의 간단한 가져 오기로 모든 테스트 클래스에서 재사용 할 수있는 훌륭한 솔루션입니다. +1
Nander Speerstra

명품이지만 골디! 내 하루를 구했습니다. 이 해결 방법에 감사드립니다 +1
Raistlin

13

Spring Boot를 사용하는 경우 pom.xml에 thymeleaf 종속성을 추가하십시오.

    <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

나를 위해 문제를 해결 한 /후 추가 /preference:

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

8

제 경우에는 Kotlin + Spring 부팅을 시도하고 있었고 Circular View Path 문제에 직면했습니다. 내가 온라인에서 얻은 모든 제안은 아래를 시도 할 때까지 도움이 될 수 없습니다.

원래 컨트롤러에 주석을 달았습니다. @Controller

import org.springframework.stereotype.Controller

그때 교체 @Controller와 함께@RestController

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

그리고 그것은 효과가있었습니다.


6

@RequestBody를 사용하지 않았고 만 사용하는 @Controller경우이 문제를 해결하는 가장 간단한 방법 @RestController@Controller


이것은 수정되지 않았습니다. 이제 파일 이름이 대신 템플릿을 표시합니다
Ashish Kamble

1
그것은 실제 문제에 달려 있습니다. 이 오류는 여러 가지 이유로 발생할 수 있습니다
MozenRath

4

@ResponseBody메소드 리턴에 주석 을 추가하십시오 .


이것이 문제를 해결하는 방법과 이유에 대한 설명을 포함하여 게시물의 품질을 개선하는 데 실제로 도움이되며 더 많은 찬성 투표를받을 수 있습니다.
Android '

3

Thymeleaf와 함께 Spring Boot를 사용하고 있습니다. 이것이 나를 위해 일한 것입니다. JSP와 비슷한 답변이 있지만 JSP가 아닌 HTML을 사용하고 있으며 여기에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;
}

도움이 되었기를 바랍니다.


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의 경우 :

방금 스프링 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

주석을 사용하여 스프링 웹 앱을 구성했는데 InternalResourceViewResolver, 구성에 빈을 추가하여 문제를 해결했습니다 . 도움이 되길 바랍니다.

@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에서 스프링 부트 1.3.1로 업그레이드 한 후 중단되었으며이 줄만 실패했습니다. registry.addViewController ( "/ login"). setViewName ( "login"); 해당 빈을 등록 할 때 앱이 다시 작동했습니다 ... 적어도 로그인은갔습니다.
le0diaz

0

이는 Spring이 "preference"를 제거하고 "preference"를 다시 추가하여 요청 Uri와 동일한 경로를 만들기 때문에 발생합니다.

다음과 같이 발생합니다. 요청 Uri : "/ preference"

"preference"제거 : "/"

추가 경로 : "/"+ "preference"

끝 문자열 : "/ preference"

이것은 Spring이 예외를 발생시켜 통지하는 루프에 들어갑니다.

"preferenceView"또는 원하는대로 다른보기 이름을 지정하는 것이 가장 좋습니다.


0

gradle 파일에 compile ( "org.springframework.boot : spring-boot-starter-thymeleaf") 종속성을 추가해보십시오 .Thymeleaf는 뷰 매핑을 도와줍니다.


0

제 경우에는 Spring 부트 응용 프로그램을 사용하여 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);
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.