[Spring] 기초 Spring 3주차

Yuri·2025년 1월 22일

Spring

목록 보기
4/21

🧑‍🏫 목표

  • MVC 패턴과 Spring MVC의 구조 및 주요 인터페이스를 이해하고, 이를 기반으로 웹 애플리케이션의 흐름과 동작 방식을 학습한다.
  • Spring Boot를 활용하여 View Resolver, Handler Mapping 등 주요 컴포넌트의 작동 순서를 파악하고, 실습을 통해 이론을 실제로 적용해 본다.

Spring MVC

MVC 패턴에 프론트 컨트롤러 패턴, 어댑터 패턴 적용

  • Spring MVC 구조
    • DispatcherServlet: Spring의 프론트 컨트롤러
      • 요청을 처리하고 알맞은 응답을 반환
      • 핸들러 목록 정보를 알고있다.
      • 핸들러 어댑터 목록 정보를 알고있다.
    • View: 인터페이스로 구성되어있다. 확장성을 가지고 있다.
  • 실행순서
    1. Client로 부터 요청(Request)을 받는다.
    2. Handler Mapping을 통해 요청 URL에 Mapping된 Handler(Controller) 조회
    3. Handler를 처리할 Adapter 조회
    4. Handler Adapter 실행
    5. Handler 실행
    6. Model And View 반환(return)
    7. viewResolver 호출 (알맞은 view 요청)
    8. View 반환
    9. View Rendering
    • Handler Adapter
      • 자신이 처리할 수 있는 Handler 인지 확인
      • 프론트 컨트롤러에서 요청을 위임받았을 때 핸들러에게 요청을 지시하는 기능 필요
      • 반환 시 Handler로부터 전달받은 결과를 알맞은 응답으로 변환
    • Handler
      • 요청에 대한 로직을 수행하는 기능이 필요하다.

DispatcherServlet

→ Spring MVC의 핵심 기능

▶︎ DispatcherServlet Class Diagram

  1. Dispatcher Servlet은 HttpServlet을 상속 받아서 사용하고 Servlet의 한 종류이다.
  2. SpringBoot에서 Dispatcher Servlet을 서블릿으로 자동으로 등록(내장 WAS를 실행하며 등록) → (urlPatterns="/"): 모든 요청을 받는다.

service()

HttpServlet을 상속 받아서 제공하는 service() 오버라이딩
👉 HttpServlet > FrameworkServlet > DispatcherServlet

HttpServlet

FrameworkServlet

  1. Servlet이 호출되면 HttpServlet이 제공하는 service()가 호출
  2. DispatcherServlet의 부모인 FrameworkServlet에서 service() 오버라이딩
  3. FrameworkServlet.service()을 시작으로 여러 메서드가 호출됨과 동시에 DispatcherServlet.doDispatch()가 호출됨


▶︎ 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

🧑‍💻 개발자의 입장에서는...

  • 전체적인 동작 방식을 알아야 어떤 부분에서 문제가 발생했는지 파악
  • 개발자가 구현하고자 하는 기능이 어떤 인터페이스에서 확장해야 하는지 파악

Controller 인터페이스

@Component

Spring Bean 이름을 URL로 설정

  • Spring Bean에 등록하는 역할을 수행
    • Spring Bean은 애플리케이션의 구성 요소를 정의하는 객체이다.
    • 마치 Servlet이 Servlet Container에 등록되는 것과 같다.

handleRequest

호출되면 실행되어야 하는 비즈니스 로직

🚩 실행 흐름

  1. Handler Mapping
  • 핸들러 매핑에서 ExampleController를 찾을 수 있어야 한다.
    → Spring Bean의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑이 필요하다.
  1. Handler Adapter
  • Handler Mapping을 통해 찾은 핸들러를 실행할 수 있는 Handler Adapter가 필요
    → Controller 인터페이스를 실행할 수 있는 Handler Adapter를 찾고 실행한다.

위에서 필요한 HandlerMapping과 HandlerAdapter는 Spring Boot가 구현

  • 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 처리

HttpRequestHandlerAdapter
기존 방식에서 사용하는 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 호출");
    }
}


1. HandlerMapping 으로 핸들러 조회

  • BeanName으로 Handler 조회 BeanNameUrlHandlerMapping
  • ExampleRequestHandler 반환
  1. HandlerAdapter 조회
    • HandlerAdapter의 supports() 를 우선순위 순서대로 호출 → instanceof 연산자 사용
    • 반환된 HttpRequestHandlerAdapter HttpRequestHandler 인터페이스를 지원
  • DispatchServlet에서 호출
    1. 핸들러 조회
    2. 핸들러 어댑터 조회
    3. 핸들러 어댑터 실행 → ha.handle()

View Resolver

// 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");
    }
}

논리이름 "test" → 물리이름 "/WEB-INF/views/test.jsp" 로 변환 필요

  • ViewResolver
    application.properties 설정

    spring.mvc.view.prefix=/WEB-INF/views/
    spring.mvc.view.suffix=.jsp

    설정을 기반으로 SpringBoot가 InternalResourceViewResolver를 만든다.

  • ViewName으로 View를 찾지 못하는 경우(= View가 존재하지 않는 경우)

    ☹️ Whitelabel Error Page 표시

위에서 필요한 ViewResolver는 Spring Boot에서 구현

  • 우선순위 순서
  1. BeanNameViewResolver
    • Bean Name으로 View를 찾아 반환
  2. InternalResourceViewResolver(위 예시코드)
    • application.properties 설정 파일에 등록한 prefix, suffix 설정 정보를 사용하여 ViewResolver 등록
  • Spring MVC 순서
  1. Mapping된 Handler의 HandlerAdapter 호출하고 HandlerAdapter는 논리 View Name을 얻음

  2. ModelAndView 형태로 반환받고 DispatcherServlet이 ViewResolver를 위의 우선순위 대로 호출

  3. 호출된 InternalResourceViewResolver

    buildView(String viewName)
    (물리이름이 일치하는) 알맞은 View를 반환

  4. InternalResourceView

    • 서버에서 이동하는 forward()를 호출하는 경우와 같을 때 사용

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

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

profile
안녕하세요 :)

0개의 댓글