💬 정적 페이지와 동적 페이지
정적 페이지
src/resources/static/admin/index.html
라는 파일이 있다고 하자.
이 html 코드를 특정 URL을 입력했을 때 웹 페이지로 띄우고 싶다. 이 때 스프링에서 지원하는 정적 컨텐츠 기능을 사용할 수 있다. 아래와 같이 html 파일 경로를 포함하여 문자열을 반환하고 실행하였다. localhost:8080/admin
을 주소창에 입력하면 내가 만든 index.html
파일이 뜨는 것을 확인할 수 있다.
@GetMapping("/admin")
public String getAdminPage() {
return "/admin/index";
}
이렇게 Static Resource의 경로만 반환했을 뿐인데 html이 웹 페이지로 렌더링되었다.
📝 Static Resource란?src/resource/static
위치에 생성된 모든 html, css, javascript, image와 같이 컴파일이 필요 없는 파일을 Static Resource라고 한다.
동적 페이지
그럼 정적 페이지 말고, 동적 페이지도 이렇게 띄울 수 있나?
📝 동적 페이지란?
동적 페이지는 정적 페이지와 다르게 사용자에 따라 서로 다른 페이지를 제공한다. 이를 위해서는 사용자가 요청을 보낼 때 자신이 원하는 값을 전달한다.
이 때 템플릿 엔진을 활용해서 동적으로 페이지를 띄울 수 있다.
동적 페이지의 경우 src/resources/templates
경로에 html 파일을 만들고 해당 경로를 문자열로 반환하기만 하면 된다. 마찬가지로 해당 경로에 파일을 만들고 경로를 문자열로 반환하여 웹 페이지를 렌더링 해보자.
@GetMapping("/admin/reservation")
public String getReservationPage() {
return "/admin/reservation";
}
🤔 동작 원리
👩💻 코드로 보는 동작 원리
어떻게 경로를 문자열로 반환했을 뿐인데 해당 컨트롤러 메서드에 정의된 API를 호출하면 웹 페이지를 볼 수 있는걸까?
해답의 포인트는 ViewResolver이다.
Javadoc과 Spring 공식문서에 나온 설명을 가져와보았다.
Interface to be implemented by objects that can resolve views by name.
- Javadoc
Spring MVC defines the ViewResolver and View interfaces that let you render models in a browser without tying you to a specific view technology. ViewResolver provides a mapping between view names and actual views.
- Spring docs
즉, ViewResolver는 뷰의 이름을 통해 실제 뷰를 매핑해주는 기능을 제공한다. 그래서 메서드에서 파일의 경로가 포함된 메서드를 정의하기만 하면, ViewResolver가 그 경로를 통해 실제 뷰를 브라우저에 렌더링할 수 있는 것이다.
잠깐 DispatcherServlet에서 ViewResolver를 어떻게 사용하고 있는지 보자.
public class DispatcherServlet extends FrameworkServlet {
/** List of ViewResolvers used by this servlet. */
@Nullable
private List<ViewResolver> viewResolvers;
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
}
DispatcherServlet은 웹 페이지를 렌더링 하기 위해 뷰 이름 (viewName)을 기반으로 ViewResolver에게 뷰 이름을 검색해줘! 라고 요청한다. 그럼 ViewResolver는 resolveViewName이라는 메서드에서 뷰 이름을 기반으로 뷰를 조회해서 DispatcherServlet에게 반환한다.
디버깅해본 결과, ContentNegotiatingViewResolver라는 ViewResolve의 구현 클래스의 resolveViewName 메서드가 호출되어 아래와 같이 가장 최적의 뷰를 찾는 것을 볼 수 있다.
그리고 이 뷰를 받은 DispatcherServlet은 뷰를 렌더링하여 응답으로 최종 반환하는 것이다.
이렇게 코드에서 DispatcherServlet으로부터 ViewResolver가 어떻게 사용되는 지 알아보았다.
🕵️♂️ 그림으로 보는 동작원리
간단하게 그림으로도 살펴보자.
위 사진은 스프링 MVC에서 클라이언트와 서버가 HTTP 통신을 하는 간단한 구조이다. 프론트 컨트롤러 패턴을 사용한 구조이고, DispatcherServlet이 요청을 받고 응답을 전달하는 프론트 컨트롤러 역할을 한다.
요청을 받았을 때 DispatcherServlet이 제일 앞단에서 요청을 받아 다른 친구들에게 요청을 보내는 것을 볼 수 있다. @ResponseBody나 ResponseEntity와 같이 응답 객체를 직렬화하여 JSON으로 보내지 않는다면 DispatcherServlet은 자동으로 HandlerAdapter가 아닌 ViewResolver에게 요청을 보내게 된다.
해당 내용은 아래 링크에서 확인할 수 있다.
https://mingyum119.tistory.com/303
결론적으로, 파일이 포함된 경로를 문자열로 반환했을 때 자동으로 브라우저에서 웹 페이지를 렌더링할 수 있는 이유는 DispatcherServlet이 ViewResolver에게 View를 반환하는 요청을 보내기 때문이다. 뷰의 이름을 통해 뷰를 반환하는 것이 ViewResolver의 역할이다.
참고
https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/viewresolver.html
https://galid1.tistory.com/527
'우아한테크코스 > 레벨2' 카테고리의 다른 글
[OOP] POJO (Plain Old Java Objects) 란? (feat. 스프링의 등장 배경) (0) | 2024.04.27 |
---|---|
[Spring] 직렬화 (Serialization)와 역직렬화 (Deserialization) 의 정의 및 스프링에서 사용하는 방법 (4) | 2024.04.27 |
[OOP] DTO(Data Transfer Object) 란 무엇인가? 어떻게 사용하는가? (0) | 2024.04.26 |
[회고] 우아한테크코스 6기 백엔드 레벨2 1주차 회고 (6) | 2024.04.21 |
[Spring] @ResponseBody와 ResponseEntity의 차이점과 동작 원리 (2) | 2024.04.18 |