Back-End (Web)/Spring

[Spring] Spring MVC 패턴

JABHACK 2024. 12. 14. 11:29

Spring MVC 구조

📌 Spring은 MVC 패턴에 프론트 컨트롤러 패턴, 어댑터 패턴이 적용된 구조를 가지고 있다.

  • MVC는 소프트웨어 설계 패턴으로 구축 개념에 가깝다. 당연히 구축 방식은 때에 따라 달라왔고 이를 Spring에서는 통합하여 하나의 템플릿으로 제공한다.

 

MVC 패턴 구조

  1. 요청이 오면 Controller에서 파라미터 정보 확인하여 비지니스 로직을 실행한다.
  2. 비지니스 로직의 결과 Data 를 Model에 담아서 View에 전달해준다.
  3. View는 모델의 Data를 참조하여 화면을 그려준다.

Spring MVC 구조

  • DispatcherServlet : Spring의 프론트(프론트엔드 아님, HTTP 요청의 최전선을 프론트라인이라 함) 컨트롤러
  • View : 인터페이스로 구성되어 있다, 확장성을 가지고 있다.

 

실행순서

  1. Client로 부터 HTTP 요청(Request)을 받는다.
  2. Handler 조회
    • Handler Mapping을 통해 요청 URL에 Mapping된 Handler(Controller)를 조회
  3. Handler를 처리할 Adapter 조회
    • Handler를 처리할 수 있는 Handler Adapter를 조회
  4. Handler Adapter 실행(handle)
    • 알맞은 ****어댑터가 존재한다면 ****Handler Adapter에게 요청을 위임한다.
  5. Handler 실행(호출)
    • Handler Adapter가 실제 Handler(Controller)를 호출하여 실행 및 결과 반환
  6. Model And View 반환(return)
    • Handler Adapter는 Handler가 반환 하는 정보를 ModelAndView 객체로 변환하여 반환
  7. viewResolver 호출(알맞은 View 요청)
    • View Resolver를 찾고 실행
  8. View 반환
    • View Resolver는 View의 논리 이름을 물리 이름으로 전환하는 역할을 수행하고 Rendering 역할을 담당하는 View 객체를 반환
  9. 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

  • 고객의 앱에서 실제로 화면이 표시됩니다.
    • 예: 고객은 "주문이 완료되었고, 곧 배달됩니다!"라는 메시지를 화면에서 확인합니다.

결론: 배달 서비스로 비유한 실행 흐름

  1. 고객이 배달 앱에 주문 요청을 보냄 (HTTP 요청)
  2. 서버가 요청을 처리할 음식점을 찾음 (Handler Mapping)
  3. 주문을 전달하는 적절한 방식을 선택 (Handler Adapter 조회)
  4. 음식점이 주문을 준비함 (Handler 실행)
  5. 음식점이 준비 상태를 서버에 반환 (ModelAndView 반환)
  6. 서버가 결과를 앱 화면에 맞게 변환 (View Resolver 호출)
  7. 결과가 앱 화면에 표시됨 (View Rendering)

 

요약

  • DispatcherServlet ( 요청과 응답의 중앙 처리국 )
    1. 클라이언트 HTTP Request를 알맞게 파싱하고 클라이언트에게 알맞은 응답을 반환
    2. 핸들러 목록 정보를 알고있다.
    3. 핸들러 어댑터 목록 정보를 알고있다.
  • HandlerAdapter (부서 담당 연결원)
    1. 자신이 처리할 수 있는 Handler인지 확인할 수 있는 기능(Method)이 필요하다.
    2. 프론트 컨트롤러에서 요청을 위임받았을 때 핸들러에게 요청을 지시하는 기능이 필요하다.
    3. return 시 Handler로부터 전달받은 결과를 알맞은 응답으로 변환한다.
  • Handler (처리자)
    1. 요청에 대한 로직을 수행하는 기능이 필요하다.

 

 

Dispatcher Servlet

📌 모든 HTTP 요청의 진입점으로 작동하는 프론트 컨트롤러(Front Controller)로, 요청 처리의 중앙 허브(총괄), 핸들러 매핑(배정), 핸들러 어댑터 실행(인력 파견), 응답 반환(우편전달)을 다 한다.

  • Spring MVC의 프론트 컨트롤러는 Dispatcher Servlet(Servlet의 한 종류)이다.

  1. Dispatcher Servlet은 HttpServlet을 상속 받아서 사용하고 Servlet의 한 종류이다.
  2. Spring Boot는 Dispatcher Servlet을 서블릿으로 자동으로 등록(내장 Tomcat WAS를 실행하면서 등록한다)하고 모든 URL 경로에 대해서 Dispatcher Servlet을 Mapping 한다. → (urlPatterns=”/”) = 요청이 들어오는 모든 url에 총괄 관리자를 붙인다.
  3. 더 자세한 URL 경로가 높은 우선순위를 가진다. = 요구사항이 자세한 손님부터 처리해 주겠다는 것 = 효율적이라서
    • 개발자가 만들 Servlet이 항상 우선순위가 높아서 실행된다.

 

DispatcherServlet의 service()

  1. Servlet이 호출되면 HttpServlet이 제공하는 service()가 호출된다.
  2. Spring MVC는 DispatcherServlet의 부모인 FrameworkServlet에서 service()를 Override 해두었다.
  3. 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
    1. HandlerMapping
    2. HandlerAdapter
    3. ViewResolver
    4. 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에 등록되는 것과 같다.

 

 

  1. Handler Mapping
    1. 핸들러 매핑에서 ExampleController( Spring 애플리케이션에서 특정 요청을 처리하는 컨트롤러 클래스)를 찾을 수 있어야 한다.
    → Spring Bean의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑이 필요하다.
  2. Handler Adapter
    1. Handler Mapping을 통해 찾은 핸들러를 실행할 수 있는 Handler Adapter가 필요
    → Controller Interface를 실행할 수 있는 Handler Adapter를 찾고 실행한다.
  • 놀랍게도 Handler Mapping은 찾기만 하고 Handler Adapter가 Handler에게 배치해줘야 한다.
  • 유지보수와 확장성에 용이하다...

 

Spring Boot의 Handler Mapping, Handler Adapter

📌 Spring Boot를 사용하면 개발에 필요하여 자동으로 등록되는 HandlerMapping과 HandlerAdapter들이 있다.

  • HandlerMapping, HandlerAdapter 모두 우선순위대로 조회한다.

 

  • HandlerMapping
    • 우선순위 순서
    1. RequestMappingHandlerMapping
      • 우선순위가 가장 높다
      • Annotation 기반 Controller의 @RequestMapping에 사용
    2. BeanNameUrlHandlerMapping(위 예시코드에 사용)
      • Spring Bean Name으로 HandlerMapping
  • HandlerAdapter
    • 우선순위 순서
    1. RequestMappingHandlerAdapter
      • Annotation 기반 Controller의 @RequestMapping에서 사용
    2. HttpRequestHandlerAdapter
      • HttpRequestHandler 처리
    3. 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

출력결과

 

 

실행순서

  1. HandlerMapping 으로 핸들러 조회
    1. BeanName으로 Handler 조회(BeanNameUrlHandlerMapping 실행)
    2. ExampleRequestHandler 반환
  2. HandlerAdapter 조회
    1. HandleAdapter의 supports()를 우선순위 순서대로 호출
    2. HttpRequestHandlerAdapter가 HttpRequestHandler Interface를 지원한다
    • HttpRequestHandlerAdapter.supports()

  1. HandlerAdapter 실행
    1. DispatcherServlet이 조회한 HttpRequestHandlerAdapter를 실행하며 Handler 정보도 넘긴다
    2. 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가 존재한다.
    1. BeanNameViewResolver
      1. Bean Name으로 View를 찾아 반환
    2. InternalResourceViewResolver(위 예시코드)
      1. application.properties 설정 파일에 등록한 prefix, suffix 설정 정보를 사용하여 ViewResolver 등록
// 아래 코드를 자동으로 해주는것과 마찬가지이다.
@Bean
InternalResourceViewResolver internalResourceViewResolver() {
	return new InternalResourceViewResolver("/WEB-INF/views", ".jsp");
}

 

 

InternalResourceViewResolver로 알아보는 Spring MVC 동작 순서

  1. HandlerAdapter 호출
    • HandlerAdapter를 통해 “test” 논리 View Name 얻음
  2. ViewResolver 호출
    • ”test” 이라는 View Name으로 viewResolver를 우선순위 대로 호출
      • BeanNameViewResolver는 View를 찾지 못한다.
      • InternalResourceViewResolver 호출
  3. InternalResourceViewResolver
    • InternalResourceViewResolver.buildView(String viewName)
    InternalResourceView 반환

 

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