5-3. View Resolver

shin·2025년 7월 20일

Spring MVC

목록 보기
21/25

Spring MVC에서 View Resolver와 템플릿 엔진 동작 방식 학습

  • 컨트롤러의 반환값이 어떻게 실제 사용자에게 보여지는 화면(뷰)으로 연결되는지 전체 흐름 이해

View Resolver와 템플릿 엔진 학습 이유

  • MVC 흐름의 핵심 구성요소
  • 다양한 뷰 기술을 정확히 구분하고 사용하기 위함
  • 자동 설정의 구조를 이해하면 오류 해결이 쉬움
  • 실무에서 템플릿 전환이나 커스터마이징 이슈 해결
  • 국제화, 다국어 처리, 테마 처리와 연관


OldController


  • 예전 방식의 Spring MVC 컨트롤러
  • 지금은 쓰이지 않지만, 스프링 내부 작동 원리나 MVC 흐름을 이해하기 위해 구현
package hello.servlet.web.springmvc.old;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component("/springmvc/old-controller")
public class OldController implements Controller {

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
	
    	System.out.println("OldController.handleRequest");
        
		return new ModelAndView("new-form");
	}
}
  • 요청이 /springmvc/old-controller로 오면 이 컨트롤러가 실행되고, 뷰 이름으로 "new-form"을 반환함
    • 이 뷰 이름은 뷰 리졸버가 new-form.jsp 같은 실제 뷰 파일로 매핑해줌
    • 뷰 이름을 반환하면, 뷰 리졸버가 JSP 파일 위치를 찾아서 렌더링해줌


스프링 MVC에서 뷰(view)를 찾는 방법 -> 뷰 리졸버의 역할


문제 상황 : WhitelabelErrorPage

실행 URL

http://localhost:8080/springmvc/old-controller
  • 해당 주소로 접속하면 콘솔에는 로그가 출력됨
OldController.handleRequest
  • 즉 컨트롤러는 정상적으로 호출이 되었음
  • 그런데 브라우저에는 Whitelabel Error Page라는 에러 화면이 출력됨

문제 분석

  • 컨트롤러는 new-form이라는 뷰 이름을 리턴함
return new ModelAndView("new-form");
  • 그런데 스프링은 이 "new-form"을 보고 어떤 JSP 파일을 보여줘야 하는지 알 수가 없음
  • 즉, 뷰 이름을 실제 파일 경로로 변환해주는 설정이 필요함

해결 방법 : application.properties에 뷰 경로 설정 추가

application.properties

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
  • prefix : JSP 파일이 어디에 있는지 경로 앞부분

  • suffix :JSP 파일의 확장자

  • 해당 설정을 하면 "new-form"이라는 뷰 이름은 자동으로 아래와 같이 변환됨

/WEB-INF/views/new-form.jsp

결론 : 뷰 리졸버란?

Spring MVC에서는 컨트롤러가 반환한 "new-form"같은 뷰 이름을 진짜 JSP 경로로 바꿔줘야 함

  • 이것을 담당하는 것이 바로 뷰 리졸버(View Resolver)

InternalResourceViewResolver

  • Spring Boot는 기본적으로 InternalResourceViewResolver를 자동으로 등록해줌
  • 위에서 설정한 prefixsuffix 정보를 활용해서 new-form을 실제 JSP 경로로 변환해주는 역할을 함
// 예시: "new-form" → "/WEB-INF/views/new-form.jsp"

만약 전체 경로를 직접 쓴다면?

return new ModelAndView("/WEB-INF/views/new-form.jsp");
  • 위와 같이 전체 경로를 직접 다 쓴다면 설정 없이도 동작하긴 함

  • 하지만 권장되지 않는 방식임

    • 설정을 바꾸기 어려워짐
    • 뷰 경로가 코드에 박혀버림(하드코딩)

정리

Whitelabel Error - 뷰를 찾지 못해서 발생
해결 - application.properties에 뷰 경로 설정 추가
InternalResourceViewResolver - 뷰 이름 -> JSP 경로로 변환
권장 방식 - 뷰 이름만 리턴하고, 실제 경로는 설정으로 처리

  • 이제 new-from.jsp 파일만 /WEB-INF/views/ 안에 잘 넣어주면, 실행 시에 정상적으로 폼 출력됨


뷰 리졸버 동작 방식


전체 흐름 요약(비유 by Chatgpt...)

상황


스프링 MVC 구성요소 비유

  • DispatcherServlet : 스프링 MVC의 중앙 컨트롤타워로 모든 요청을 이쪽에서 처리하고 분배함
  • Handler : 실제 컨트롤러(요리사)
  • Handler Adapter : 요리사마다 맞는 방식으로 조리를 도와주는 조리 도우미
  • ViewResolver : 요리 이름을 보고 어디 있는 요리인지 알려주는 안내자
  • View(InternalResourceView) : 실제로 보여줄 JSP 파일(요리 완성품)

상세 동작 단계

1️⃣ 클라이언트가 HTTP 요청

  • 브라우저에서 요청을 보냄 (예: /springmvc/old-controller)

2️⃣ DispatcherServlet이 요청 받음

  • 중앙 컨트롤타워가 요청을 받음

3️⃣ 핸들러 매핑으로 “누가 처리할지” 찾음

  • 이 요청을 처리할 컨트롤러가 뭔지 찾음 (OldController 같은 클래스)

4️⃣ 핸들러 어댑터 목록에서 알맞은 어댑터 찾음

  • 컨트롤러를 실행할 수 있는 어댑터 선택(OldController는 SimpleControllerHandlerAdapter가 실행시켜줌)

5️⃣ 컨트롤러 실행 후 ModelAndView 리턴

  • new-form이라는 논리 뷰 이름을 DispatcherServlet에 반환

6️⃣ viewResolver 호출

  • DispatcherServlet은 등록된 뷰 리졸버들을 순서대로 탐색함

✅ BeanNameViewResolver (1번 시도)

  • “new-form”이라는 이름의 빈(Bean)이 있는지 확인
    • 없으니 패스

✅ InternalResourceViewResolver (2번 시도)

  • 설정 덕분에 다음 경로로 변환함
/WEB-INF/views/new-form.jsp

7️⃣ InternalResourceView 생성됨

  • 이 뷰는 JSP를 forward로 실행할 수 있는 InternalResourceView 타입

8️⃣ render(model) 호출

  • JSP로 forward 시켜서, 그 안에서 HTML 화면을 만들어 브라우저로 응답함
  • 이게 바로 우리가 화면에서 보는 폼 출력 JSP 화면


그 외 참고 사항


JstlView

  • 위에서는 InternalResourceView가 생성되었지만, JSTL이 있다면 JstlView가 생성됨 (기능 같고 약간 부가기능 있음)
    • JSTL이 프로젝트에 있으면, 스프링이 JstlView로 JSP 뷰를 처리함
    • JstlView는 JSTL 태그가 잘 동작하도록 도와주는 기능이 있는 뷰 클래스

JSTL : JavaServer Pages Standard Tag Library

  • JSTL : JSP에서 사용하는 표준 태그 라이브러리
    • JSP에서 반복문, 조건문, 날짜 포맷, 국제화(i18n) 같은 걸 자바 코드 없이 HTML 태그처럼 사용할 수 있게 해주는 태그 라이브러리
  • 예시
<c:forEach var="item" items="${items}">
  <p>${item.name}</p>
</c:forEach>
  • 이게 바로 JSTL의 <c:forEach> 태그

사용 이유

  • JSP 환경에서는 JSTL이 사실상 필수
  • JSP는 원래 HTML 안에 자바 코드를 <% %>로 섞어서 쓰는 방식이라 복잡하고 유지보수가 어려움
  • 따라서 JSTL을 쓰면 조건문, 반복문, 날짜포맷, i18n 등을 HTML 태그처럼 표현할 수 있어서 훨씬 깔끔하고 편함

xml 파일(Maven 예시)

<dependency>
  <groupId>jakarta.servlet.jsp.jstl</groupId>
  <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
  <version>3.0.0</version>
</dependency>
<dependency>
  <groupId>org.glassfish.web</groupId>
  <artifactId>jakarta.servlet.jsp.jstl</artifactId>
  <version>3.0.0</version>
</dependency>

InternalResourceView vs JstlView

구분InternalResourceViewJstlView
JSP forward 처리✅ 지원✅ 지원
JSTL 태그 지원❌ JSTL 처리 안 함✅ JSTL 관련 설정 포함
EL context 제공❌ 없음✅ JSTL 태그가 잘 동작하도록 추가 작업 수행
  • 즉, JSTL을 제대로 쓰려면 JstlView가 필요함

Spring의 자동 처리 방식

  • JSP에 JSTL을 쓰고 JSTL 라이브러리(jakarta.servlet.jsp.jstl)가 프로젝트에 포함되어 있으면, Spring은 자동으로 JstlView를 사용해서 JSP를 처리해줌
  • 사용자가 따로 지정하지 않아도, InternalResourceViewResolver는 알아서 JstlView로 뷰 객체를 만들어냄

개발자 직접 설정

InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setViewClass(JstlView.class);
  • 이렇게 명시적으로 JstlView를 쓰라고 지정할 수도 있지만, Spring boot에선 JSTL 라이브러리만 있으면 자동으로 처리해주기 때문에 따로 설정할 필요는 없음


Thymeleaf를 사용한다면?

  • Thymelaef는 JSP + JSTL 조합과는 완전히 다른 자체 템플릿 엔진
  • 조건문, 반복문 등은 Thymeleaf 전용 속성(th:if, th:each)으로 처리함
  • 오히려 JSP + JSTL보다 문법이 직관적이고 HTML 친화적이라서 유지보수에 유리함
<tr th:each="item : ${items}">
  <td th:text="${item.name}">샘플</td>
</tr>
  • JSTL이 없기 때문에 JstlView, InternalResourceViewResolver 같은 설정은 필요 없음
  • 대신 스프링 부트에서는 ThymeleafViewResolver가 자동 등록됨
    • spring-boot-starter-thymeleaf 의존성에 포함되어 있음


뷰 리졸버 자동 등록 중요한 이유

@Controller + @GetMapping 같은 방식에서는 이 뷰 리졸버 자동 등록이 더 중요함

@Controller + @GetMapping 조합은 문자열을 리턴 -> 뷰 이름으로 처리하는 구조

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
    
        return "greeting"; // 뷰 이름으로 해석됨!
    }
}

이 문자열 "greeting"은 클라이언트 응답 바디가 아니라 뷰 이름으로 인식됨

  • 즉 실제로 greeting.html, greeting.jsp 등 화면 템플릿 파일을 찾기 위한 문자열
  • 이 문자열을 파일로 매핑해주는 역할을 하는게 바로 뷰 리졸버임

자동 등록이 중요한 이유

  • 스프링 부트가 없었다면 InternalResourceViewResolver, ThymeleafViewResolver 등을 직접 등록해야 했음
@Bean
public ViewResolver viewResolver() {

    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    
    resolver.setPrefix("/WEB-INF/views/");
    resolver.setSuffix(".jsp");
    
    return resolver;
}

스프링 부트에서는

spring-boot-starter-thymeleaf를 넣는 순간:

  • ThymeleafViewResolver 자동 등록됨
  • /templates/ 경로 + .html 확장자 자동 세팅됨

@RestController와는 다르게 @Controller에서 중요한 이유

@Controller

  • 리턴값 해석 방식 : 뷰 이름
  • 뷰 리졸버 필요 여부 : 필요함

@RestController

  • 리턴값 해석 방식 : HTTP 바디(문자열)
  • 뷰 리졸버 필요 여부 : 필요 없음
  • @RestController는 그냥 문자열을 응답 바디에 출력함 -> 뷰 리졸버가 아예 안 쓰임
  • 그래서 @Controller 방식에서는 뷰 리졸버가 없으면 화면이 아예 안 나옴

결론

  • @Controller + @GetMapping 방식에서는 return "문자열"이 뷰 이름이 되므로, 이를 실제 HTML로 바꿔줄 뷰 리졸버 자동 등록이 핵심
profile
Backend development

0개의 댓글