Spring Boot에서 아래처럼 생긴 에러 메시지 한 번쯤은 볼 수 있다.
Circular view path [index]: would dispatch back to the current handler URL [/index] again
겉으로 보면 템플릿이나 설정 문제 같지만, 대부분은 요청 URL이랑 뷰 이름을 똑같이 써서 생기는 경우다
스프링 MVC에서 @Controller 메서드가 문자열을 리턴하면, 그 값은 템플릿 파일 경로가 아니라 “뷰 이름(view name)”으로 처리된다.
@GetMapping("/welcome")
public String welcome(Model model) {
model.addAttribute("message", "Hello");
return "welcome"; // => templates/welcome.html
}
Spring Boot 기본 설정에서는 대략 이런 식으로 prefix / suffix가 잡혀 있다.
- prefix: classpath:/templates/
- suffix: .html
그래서 return "welcome"; 이면 src/main/resources/templates/welcome.html 을 찾게 된다.
문제는 URL이랑 뷰 이름을 이렇게 맞춰버렸을 때다.
@GetMapping("/index")
public String index() {
return "index";
}
- 요청 URL: /index
- 뷰 이름: index
요청 경로와 뷰 이름이 그대로 겹치면, 내부에서 디스패치 과정이 꼬인다. 요청을 처리하고 다시 뷰를 찾아 넘기는 과정에서 같은 URL로 계속 포워딩하려고 하다 보니, 이걸 순환 경로로 판단해서 Circular view path 에러를 던진다.
에러 페이지를 커스터마이징하면서 /error 경로와 뷰 이름을 둘 다 error 로 맞춰버려도 비슷한 문제가 생긴다.
에러 핸들러와 에러 뷰가 서로를 계속 호출하는 느낌이 된다.
1) URL을 바꾸는 방법
가장 손이 덜 가는 방법은 URL을 바꾸는 거였다. 나는 홈 화면을 아예 루트(/)로 노출하도록 정리했다.
@Controller
public class HomeController {
@GetMapping("/")
public String home(Model model) {
model.addAttribute("username", "jin");
return "index"; // templates/index.html
}
}
- URL: /
- 뷰 이름: index
- 템플릿: src/main/resources/templates/index.html
이 조합이면 요청 경로와 뷰 이름이 직접 부딪치지 않기 때문에, Circular view path 에러가 나지 않는다.
2) 뷰 이름을 바꾸는 방법
반대로 /index 라는 URL을 유지하고 싶다면, 뷰 이름 쪽을 바꾸면 된다.
@Controller
public class HomeController {
@GetMapping("/index")
public String index(Model model) {
model.addAttribute("username", "jin");
return "home/index";
}
}
템플릿 구조는 이렇게 맞췄다.
실제 프로젝트에서는 이런 식으로 디렉터리까지 포함해서 home/index, user/list 같이
뷰 이름을 조금 길게 가져가는 패턴이 더 많이 쓰인다.
URL 계층과 템플릿 폴더 구조를 어느 정도 맞추면서도, 경로와 뷰 이름을 완전히 1:1로 겹치게 만들지는 않는 편이다.
내가 삽질하면서 한 번쯤 같이 보게 됐던 포인트들도 정리해 보면.....
- 컨트롤러 매핑 중복
같은 URL(/index, /error 등)을 여러 컨트롤러나 메서드에서 매핑해두면, 실제로 어느 쪽이 호출되는지 헷갈리면서 디스패치가 꼬일 수 있다. - 뷰 리졸버/템플릿 경로 커스터마이징
spring.thymeleaf.prefix, spring.thymeleaf.suffix 를 수정해뒀다면, 템플릿 경로가 그 설정에 맞게 옮겨졌는지 같이 봐야 한다. 템플릿을 못 찾고 에러 경로로 떨어졌다가 다시 순환에 빠지는 식으로 이어질 수 있다. - 에러 페이지 커스터마이징
기본 Whitelabel 에러 페이지를 끄고 직접 /error 를 처리할 때, 컨트롤러 매핑과 뷰 이름을 둘 다 error 로 맞춰두면 아까 본 것처럼 순환 에러가 나기 쉽다. 에러 뷰를 error/default, error/404 이런 식으로 조금 더 구체적인 이름으로 나눠두는 게 안전했다. - 정적 리소스 경로와의 충돌
정적 리소스 경로를 커스터마이징하면서 컨트롤러가 CSS/JS 요청을 가로채는 경우도 있다. 이때 그 요청을 처리하는 메서드가 또 템플릿 이름과 겹치는 값을 리턴하면 비슷한 종류의 문제가 생길 수 있다.
결국 이 에러를 보면 템플릿 파일부터 뒤지기보다는...
요청 URL이랑 뷰 이름이 그대로 겹치는 곳이 있는지 먼저 확인하는 게 훨씬 빠르게 해결되는 것 같다.
