Spring MVC 구조
📌 Spring은 MVC 패턴에 프론트 컨트롤러 패턴, 어댑터 패턴이 적용된 구조를 가지고 있다.
- MVC는 소프트웨어 설계 패턴으로 구축 개념에 가깝다. 당연히 구축 방식은 때에 따라 달라왔고 이를 Spring에서는 통합하여 하나의 템플릿으로 제공한다.
- 요청이 오면 Controller에서 파라미터 정보 확인하여 비지니스 로직을 실행한다.
- 비지니스 로직의 결과 Data 를 Model에 담아서 View에 전달해준다.
- View는 모델의 Data를 참조하여 화면을 그려준다.
- DispatcherServlet : Spring의 프론트(프론트엔드 아님, HTTP 요청의 최전선을 프론트라인이라 함) 컨트롤러
- View : 인터페이스로 구성되어 있다, 확장성을 가지고 있다.
실행순서
- Client로 부터 HTTP 요청(Request)을 받는다.
- Handler 조회
- Handler Mapping을 통해 요청 URL에 Mapping된 Handler(Controller)를 조회
- Handler를 처리할 Adapter 조회
- Handler를 처리할 수 있는 Handler Adapter를 조회
- Handler Adapter 실행(handle)
- 알맞은 ****어댑터가 존재한다면 ****Handler Adapter에게 요청을 위임한다.
- Handler 실행(호출)
- Handler Adapter가 실제 Handler(Controller)를 호출하여 실행 및 결과 반환
- Model And View 반환(return)
- Handler Adapter는 Handler가 반환 하는 정보를 ModelAndView 객체로 변환하여 반환
- viewResolver 호출(알맞은 View 요청)
- View Resolver를 찾고 실행
- View 반환
- View Resolver는 View의 논리 이름을 물리 이름으로 전환하는 역할을 수행하고 Rendering 역할을 담당하는 View 객체를 반환
- View Rendering
- View를 통해서 View를 Rendering
1. Client로부터 HTTP 요청(Request)을 받는다
- 고객(클라이언트)이 배달 앱에서 음식을 주문합니다. (예: "치킨 1마리 배달 요청")
- 배달 앱이 이 주문을 **중앙 주문 처리 서버(SPRING MVC)**로 보냅니다.
2. Handler 조회 (Handler Mapping)
- 서버는 고객이 요청한 **주문 정보(예: 치킨 배달)**를 확인하고, 이 요청을 처리할 적절한 음식점(Handler)을 찾아야 합니다.
- 여기서 Handler Mapping은 주문에 맞는 음식점을 조회하는 과정입니다.
- 예: 고객이 "치킨"을 주문했으니, 근처의 치킨집을 선택합니다.
3. Handler를 처리할 Adapter 조회
- 음식점마다 주문을 처리하는 방식(프로세스)이 다를 수 있습니다.
- 어떤 음식점은 앱을 통해 바로 확인하고,
- 어떤 음식점은 전화로 주문을 확인해야 합니다.
- 이 단계에서는 요청을 처리할 수 있는 적절한 **Handler Adapter(음식점과 서버를 연결하는 도구)**를 찾습니다.
- 예: 치킨집은 앱으로 주문을 확인하는 시스템이 있으니, 앱 기반 주문 어댑터를 선택.
4. Handler Adapter 실행(handle)
- 적절한 **Handler Adapter(주문 어댑터)**가 음식점에 주문을 전달합니다.
- 예: 앱이 "치킨 한 마리 주문 요청"을 음식점의 주문 시스템에 보냅니다.
5. Handler 실행(호출)
- 음식점(Handler)이 실제로 주문을 준비하기 시작합니다.
- 예: 주방에서 치킨을 튀기고 포장합니다.
6. Model And View 반환(return)
- 음식점은 준비가 끝난 뒤, "주문이 완료되었습니다!"라는 메시지와 함께 준비된 치킨 데이터를 서버에 보냅니다.
- 예: "치킨 1마리, 포장 완료!"
- 여기서 ModelAndView는 이 데이터("치킨 데이터")와 응답 메시지를 함께 묶어서 반환하는 역할을 합니다.
7. View Resolver 호출 (알맞은 View 요청)
- 서버는 고객에게 응답을 보내기 전에, 결과 데이터를 어떻게 보여줄지 결정합니다.
- 예: 고객이 앱을 통해 결과를 볼 수 있어야 하므로, 앱 화면(View)을 설정해야 합니다.
- View Resolver는 "치킨 준비 완료"라는 정보를 앱의 알맞은 화면(예: 주문 완료 페이지)으로 연결합니다.
8. View 반환
- View Resolver는 "주문 완료 화면"을 앱에 전달합니다.
- 예: 고객의 앱에 "주문 완료! 배달 중!"이라는 화면이 나타남.
9. View Rendering
- 고객의 앱에서 실제로 화면이 표시됩니다.
- 예: 고객은 "주문이 완료되었고, 곧 배달됩니다!"라는 메시지를 화면에서 확인합니다.
결론: 배달 서비스로 비유한 실행 흐름
- 고객이 배달 앱에 주문 요청을 보냄 (HTTP 요청)
- 서버가 요청을 처리할 음식점을 찾음 (Handler Mapping)
- 주문을 전달하는 적절한 방식을 선택 (Handler Adapter 조회)
- 음식점이 주문을 준비함 (Handler 실행)
- 음식점이 준비 상태를 서버에 반환 (ModelAndView 반환)
- 서버가 결과를 앱 화면에 맞게 변환 (View Resolver 호출)
- 결과가 앱 화면에 표시됨 (View Rendering)
요약
- DispatcherServlet ( 요청과 응답의 중앙 처리국 )
- 클라이언트 HTTP Request를 알맞게 파싱하고 클라이언트에게 알맞은 응답을 반환
- 핸들러 목록 정보를 알고있다.
- 핸들러 어댑터 목록 정보를 알고있다.
- HandlerAdapter (부서 담당 연결원)
- 자신이 처리할 수 있는 Handler인지 확인할 수 있는 기능(Method)이 필요하다.
- 프론트 컨트롤러에서 요청을 위임받았을 때 핸들러에게 요청을 지시하는 기능이 필요하다.
- return 시 Handler로부터 전달받은 결과를 알맞은 응답으로 변환한다.
- Handler (처리자)
- 요청에 대한 로직을 수행하는 기능이 필요하다.
Dispatcher Servlet
📌 모든 HTTP 요청의 진입점으로 작동하는 프론트 컨트롤러(Front Controller)로, 요청 처리의 중앙 허브(총괄), 핸들러 매핑(배정), 핸들러 어댑터 실행(인력 파견), 응답 반환(우편전달)을 다 한다.
- Spring MVC의 프론트 컨트롤러는 Dispatcher Servlet(Servlet의 한 종류)이다.
- Dispatcher Servlet은 HttpServlet을 상속 받아서 사용하고 Servlet의 한 종류이다.
- Spring Boot는 Dispatcher Servlet을 서블릿으로 자동으로 등록(내장 Tomcat WAS를 실행하면서 등록한다)하고 모든 URL 경로에 대해서 Dispatcher Servlet을 Mapping 한다. → (urlPatterns=”/”) = 요청이 들어오는 모든 url에 총괄 관리자를 붙인다.
- 더 자세한 URL 경로가 높은 우선순위를 가진다. = 요구사항이 자세한 손님부터 처리해 주겠다는 것 = 효율적이라서
- 개발자가 만들 Servlet이 항상 우선순위가 높아서 실행된다.
DispatcherServlet의 service()
- Servlet이 호출되면 HttpServlet이 제공하는 service()가 호출된다.
- Spring MVC는 DispatcherServlet의 부모인 FrameworkServlet에서 service()를 Override 해두었다.
- FrameworkServlet.service()를 시작으로 여러 메서드가 호출됨과 동시에 가장 중요한DispatcherServlet.doDispatch()가 호출된다.
protected void doDispatch() {
...
// 1. 핸들러 조회
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response); // NotFound 404
}
// 2. 핸들러 어댑터 조회 : 핸들러를 처리할 수 있는 어댑터
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 3. 핸들러 어댑터 실행
// 4. 핸들러 어댑터를 통해 핸들러 실행
// 5. ModelAndView 반환
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 여기 안에서 render
processDispatchResult(processedRequest, response, mappedHandler, mv,dispatchException);
...
}
// processDispatchResult()
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
if (mv != null && !mv.wasCleared()) {
// View Render 호출
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
}
// render()
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
View view;
String viewName = mv.getViewName();
// 6. ViewResolver를 통해 View 조회
// 7. View 반환
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
// 8. View Rendering
view.render(mv.getModelInternal(), request, response);
}
Spring MVC의 주요 Interface
📌 Spring MVC는 DispatcherServlet 코드의 변경 없이 기능변경 및 확장이 가능하다. 기능들이 대부분 Interface로 만들어져 있기 때문이다.
- 이쯤에서 한번 더 짚고 넘어가면 왜 대부분이 클래스가 아니라 인터페이스로 구현되어 있냐는 질문엔, 다형을 위해서가 정답이다.
- org.springframework.web.servlet
- HandlerMapping
- HandlerAdapter
- ViewResolver
- View
- 당연히 이 모든 인터페이스를 알 필요는 없다. 하지만 자주 사용하는 인터페이스에 대해서는 잘 알아야 확장이나 구현, 문제 해결에 있어 용이하다.
Controller Interface
📌Spring 2.5 이전에 모든 컨트롤러가 구현해야 했던 표준 인터페이스입니다. 이 인터페이스는 클라이언트의 요청을 처리하고, 결과를 View로 전달하는 기본적인 방법을 정의합니다.
- Controller Interface를 implements 하여 구현하게되면 개발자가 원하는 Controller(Handler)를 사용할 수 있게됩니다.
- 추후 강의에 등장할 현대에 사용하는 Annotation 기반 Spring의 @Controller와는 역할이 비슷하지만 연관은 없습니다.
package com.example.springbasicmvc.controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
// Spring Bean 이름을 URL로 설정
@Component("/example-controller")
public class ExampleController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("example-controller가 호출 되었습니다.");
return null;
}
}
- http://localhost:8080/example-controller 로 HTTP 요청을 하게 되면 응답결과가 반환된다.
- 출력 결과
- @Component
- Spring Bean에 등록하는 역할을 수행한다.
- Spring Bean은 애플리케이션의 구성 요소를 정의하는 객체이다.
- 마치 Servlet이 Servlet Container에 등록되는 것과 같다.
- Spring Bean에 등록하는 역할을 수행한다.
- Handler Mapping
- 핸들러 매핑에서 ExampleController( Spring 애플리케이션에서 특정 요청을 처리하는 컨트롤러 클래스)를 찾을 수 있어야 한다.
- Handler Adapter
- Handler Mapping을 통해 찾은 핸들러를 실행할 수 있는 Handler Adapter가 필요
- 놀랍게도 Handler Mapping은 찾기만 하고 Handler Adapter가 Handler에게 배치해줘야 한다.
- 유지보수와 확장성에 용이하다...
Spring Boot의 Handler Mapping, Handler Adapter
📌 Spring Boot를 사용하면 개발에 필요하여 자동으로 등록되는 HandlerMapping과 HandlerAdapter들이 있다.
- HandlerMapping, HandlerAdapter 모두 우선순위대로 조회한다.
- HandlerMapping
- 우선순위 순서
- RequestMappingHandlerMapping
- 우선순위가 가장 높다
- Annotation 기반 Controller의 @RequestMapping에 사용
- BeanNameUrlHandlerMapping(위 예시코드에 사용)
- Spring Bean Name으로 HandlerMapping
- HandlerAdapter
- 우선순위 순서
- RequestMappingHandlerAdapter
- Annotation 기반 Controller의 @RequestMapping에서 사용
- HttpRequestHandlerAdapter
- HttpRequestHandler 처리
- SimpleControllerHandlerAdapter(위 예시코드에 사용)
- Controller Interface 처리
@RequestMapping 은 가장 높은 우선순위의 HandlerMapping인 RequestMappingHandlerMapping 과 가장 높은 우선순위의 HandlerAdapter인 RequestMappingHandlerAdapter 두가지를 사용하며 현대에 사용하는 Annotation 기반의 컨트롤러를 지원한다. |
HttpRequestHandler로 알아보는 Spring MVC 동작 순서
// 인터페이스
public interface HttpRequestHandler {
void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException;
}
// 구현체
@Component("/request-handler")
public class ExampleRequestHandler implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("request-handler Controller 호출");
// 구현 로직
}
}
Postman
출력결과
실행순서
- HandlerMapping 으로 핸들러 조회
- BeanName으로 Handler 조회(BeanNameUrlHandlerMapping 실행)
- ExampleRequestHandler 반환
- HandlerAdapter 조회
- HandleAdapter의 supports()를 우선순위 순서대로 호출
- HttpRequestHandlerAdapter가 HttpRequestHandler Interface를 지원한다
- HttpRequestHandlerAdapter.supports()
- HandlerAdapter 실행
- DispatcherServlet이 조회한 HttpRequestHandlerAdapter를 실행하며 Handler 정보도 넘긴다
- HttpRequestHandlerAdapter 는 ExampleRequestHandler를 내부에서 실행 후 결과를 반환
- HttpRequestHandlerAdapter.handle() → 단순히 handleRequest를 호출한다 = 오버라이딩된 handleRequest() 호출
DispatcherServlet에서 호출 → ha.handle()
View Resolver
📌 컨트롤러가 반환한 뷰 이름(View Name)을 실제 뷰 파일의 경로(View Path)로 변환하고, 클라이언트에게 응답할 화면(View)을 렌더링할 수 있도록 도와주는 컴포넌트입니다.
- 반환된 ModelAndView 객체를 알맞은 View로 전달하기 위해 DispatcherServlet에서 ViewResolver를 호출하여 View 정보를 설정하는 역할을 수행한다.
- 서버는 HTML 파일(또는 다른 뷰 파일)을 뷰(View)로 변환하여 클라이언트에게 응답으로 보낸다.는 말
package com.example.springbasicmvc.controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
// Spring Bean 이름을 URL로 설정
@Component("/view-controller")
public class ViewController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("view-controller가 호출 되었습니다.");
// "test"는 논리적인 ViewName이다. ViewResolver가 물리적인 이름으로 변환해야 한다.
return new ModelAndView("test");
}
}
- Template Engine JSP
- webapp/WEB-INF/form.JSP
[sql입니다]
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta charset="UTF-8">
<title>블로그 포스트 작성 페이지</title>
</head>
<body>
<h1>블로그 글쓰기</h1>
<form action="save" method="post">
title: <input type="text" name="title" placeholder="제목" />
content: <input type="text" name="content" placeholder="내용" />
<button type="submit">저장</button>
</form>
</body>
</html>
- ViewResolver
- application.properties 설정
- 설정을 기반으로 Spring Boot가 InternalResourceViewResolver 를 만든다.
[xml입니다]
spirng.mvc.view.prefix=/WEB-INF/views/
spirng.mvc.view.suffix=.jsp
localhost:8080/view-controller 호출
- ViewName으로 View를 찾지 못하는 경우(View가 존재하지 않음)
@Component("/error-controller")
public class WhitelabelErrorController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("error-controller가 호출 되었습니다.");
// viewName "sparta"는 존재하지 않는다.
return new ModelAndView("sparta");
}
}
- 컨트롤러는 호출되지만 View를 못 찾아 Whitelabel Error Page를 응답한다.
Spring Boot의 ViewResolver
📌 Spring Boot를 사용하면 개발에 필요하여 자동으로 등록되는 ViewResolver들이 있다.
- 우선순위 순서
- 아래 두 가지 이외에도 많은 ViewResolver가 존재한다.
- BeanNameViewResolver
- Bean Name으로 View를 찾아 반환
- InternalResourceViewResolver(위 예시코드)
- application.properties 설정 파일에 등록한 prefix, suffix 설정 정보를 사용하여 ViewResolver 등록
// 아래 코드를 자동으로 해주는것과 마찬가지이다.
@Bean
InternalResourceViewResolver internalResourceViewResolver() {
return new InternalResourceViewResolver("/WEB-INF/views", ".jsp");
}
InternalResourceViewResolver로 알아보는 Spring MVC 동작 순서
- HandlerAdapter 호출
- HandlerAdapter를 통해 “test” 논리 View Name 얻음
- ViewResolver 호출
- ”test” 이라는 View Name으로 viewResolver를 우선순위 대로 호출
- BeanNameViewResolver는 View를 찾지 못한다.
- InternalResourceViewResolver 호출
- ”test” 이라는 View Name으로 viewResolver를 우선순위 대로 호출
- InternalResourceViewResolver
- InternalResourceViewResolver.buildView(String viewName)
4. InternalResourceView
- JSP와 같이 서버에서 이동하는 forward()를 호출하는 경우와 같을 때 사용한다.
renderMergedOutputModel() → Model을 Request로 바꾼다.
5. view.render()
- 외부에서 view.render()를 호출 후 ****RequestDispatcher를 가져와 forward()한다.
→ 매우 복잡한 구조를 가지고 있으니 모두 찾아볼 필요가 없습니다.
Thymeleaf는 View와 Resolver가 이미 존재한다. 라이브러리 의존성만 추가해주면 SpringBoot가 모두 자동으로 해준다. 즉, return “viewName”; 만으로 View가 Rendering 된다. |
'Back-End (Web) > Spring' 카테고리의 다른 글
[Spring] Request Mapping (1) | 2024.12.16 |
---|---|
[Spring] Spring Annotation (1) | 2024.12.15 |
[Spring] Spring Boot (0) | 2024.12.10 |
[Spring] Spring Framework (2) | 2024.12.09 |
[Spring] 웹 개발의 흐름 (1) | 2024.12.05 |