Spring (6) Spring MVC

destro·2025년 4월 30일

2. Spring

목록 보기
6/17
post-thumbnail

1. Spring MVC

Ⅰ. Spring MVC 구조

  • g MVC 구조 📚 Spring은 MVC 패턴에 프론트 컨트롤러 패턴, 어댑터 패턴이 적용된 구조를 가지고 있다. **→ 백엔드 웹 기술의 역사 네번째**
  • MVC 패턴 구조

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

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

Ⅱ. Dispatcher Servlet

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

→ Spring MVC의 핵심 기능

  • IntelliJ Class Diagram

1. Dispatcher Servlet은 HttpServlet을 상속 받아서 사용하고 Servlet의 한 종류이다.
2. Spring Boot는 Dispatcher Servlet을 서블릿으로 자동으로 등록(내장 Tomcat WAS를 실행하면서 등록한다)하고 모든 URL 경로에 대해서 Mapping 한다. → `(urlPatterns=”/”)`
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로 만들어져 있기 때문이다. → 인터페이스를 implements 하여 구현하면 내가 만든 클래스를 사용할 수 있다. (다형성) - **org.springframework.web.servlet** 1. HandlerMapping 2. HandlerAdapter 3. ViewResolver 4. View
❓ 이 복잡한 Spring MVC의 구조와, 구성 요소들을 모두 알아야 하나요??
  1. 복잡하게 구성되고 숨겨져있는 Spring Framework의 모든 내부 구조를 알 필요는 없습니다.
  2. 이미 많은 개발자들의 요구사항에 의해 다양한 인터페이스 구현체들이 만들어져 있습니다.
  3. 전체적인 동작 방식을 알아야 어떤 부분에서 문제가 발생했는지 파악할 수 있습니다.
  4. 개발자가 구현하고자 하는 기능이 어떤 인터페이스에서 확장해야 하는지 파악할 수 있습니다.

Ⅲ. Controller Interface

📚 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에 등록되는 것과 같다.

ExampleController는 어떻게 호출되는 것일까요?

❗ Spring MVC 구조에서 배운 Handler Mapping, Handler Adapter가 해당 컨트롤러(핸들러)가 호출되도록 만들어 줍니다.

  1. Handler Mapping

    1. 핸들러 매핑에서 ExampleController를 찾을 수 있어야 한다.

    → Spring Bean의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑이 필요하다.

  2. Handler Adapter

    1. Handler Mapping을 통해 찾은 핸들러를 실행할 수 있는 Handler Adapter가 필요

    → Controller Interface를 실행할 수 있는 Handler Adapter를 찾고 실행한다.

Spring Boot를 사용하면 이미 개발에 필요한 HandlerMapping과 HandlerAdapter를 대부분 구현되어 있어서 개발자가 직접 HandlerMapping과 HandlerAdapter를 구현하는 일은 거의 없습니다.

Ⅳ. 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 동작 순서
    기존 방식에서 사용하는 Servlet과 가장 유사한 Handler이다.

  • 예시코드

// 인터페이스
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()

  3. HandlerAdapter 실행

    1. DispatcherServlet이 조회한 HttpRequestHandlerAdapter를 실행하며 Handler 정보도 넘긴다
    2. HttpRequestHandlerAdapter 는 ExampleRequestHandler를 내부에서 실행 후 결과를 반환
    • HttpRequestHandlerAdapter.handle()
      → 단순히 handleRequest를 호출한다 = 오버라이딩된 handleRequest() 호출

- DispatcherServlet에서 호출 → `ha.handle()`

Ⅴ. View Resolver

반환된 ModelAndView 객체를 알맞은 View로 전달하기 위해 DispatcherServlet에서 ViewResolver를 호출하여 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
<%@ 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 를 만든다.
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 동작 순서

application.properties 설정 파일에 등록한 prefix, suffix 설정 정보를 사용하는 ViewResolver

  • 실행순서

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

  1. InternalResourceView
    • JSP와 같이 서버에서 이동하는 forward()를 호출하는 경우와 같을 때 사용한다.

`renderMergedOutputModel()` → Model을 Request로 바꾼다.

  1. view.render()

    • 외부에서 view.render()를 호출 후 ****RequestDispatcher를 가져와 forward()한다.

    → 매우 복잡한 구조를 가지고 있으니 모두 찾아볼 필요가 없습니다.

    Thymeleaf는 View와 Resolver가 이미 존재한다. 라이브러리 의존성만 추가해주면 SpringBoot가 모두 자동으로 해준다. 즉, return “viewName”; 만으로 View가 Rendering 된다.

profile
<포르투갈어> 솜씨 있는. 재간 있는. 능란한. 기민한. (재주가) 비상한.

0개의 댓글