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.