INFLEARN/스프링 MVC 1편

[스프링 MVC 1] 4. MVC 프레임워크 만들기

0298 2021. 10. 23. 00:25

4. MVC 프레임워크 만들기

 

[강의 정리] 김영한님 :: 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술

 

 

1. 프론트 컨트롤러 패턴 소개

 

# 프론트 컨트롤러 도입 전/후

  • 프론트 컨트롤러 도입 전 : 클라이언트에서 요청이 들어왔을 때 controller를 바로 호출 (공통 로직은 각각의 Controller에서 알아서 처리) 
  • 프론트 컨트롤러 도입 후 : 클라이언트에서 요청이 들어왔을 때 먼저 Front Controller (공통 로직을 처리)에서 받은 후 Front Controller에서 Controller를 호출

# FrontController 패턴 특징

  • 프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받음
  • 프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출
  • 공통 로직을 프론트 컨트롤러에서 처리
  • 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 됨

# 스프링 웹 MVC와 프론트 컨트롤러

  • 스프링 웹 MVC의 DispatcherServlet이 FrontController 패턴으로 구현되어 있음

 

 

2. 프론트 컨트롤러 도입 - v1

 

# v1 구조

출처 : 김영한님 강의 자료

 

# ControllerV1

  • 서블릿과 비슷한 모양의 컨트롤러 인터페이스 도입
  • 프론트 컨트롤러는 이 인터페이스를 호출해서 구현과 관계없이 로직의 일관성 가져가기

 

# 회원 등록 컨트롤러 / 회원 저장 컨트롤러 / 회원 목록 컨트롤러 

  • 내부 로직은 기존 서블릿과 거의 같음

 

# FrontControllerServletV1 - 프론트 컨트롤러

  • urlPatterns
    • /front-controller/v1을 포함한 모든 하위 요청을 서블릿에서 받음

  • controlllerMap
    • key : 맵핑 URL (/front-controller/v1/members/new-form)
    • value : 호출될 컨트롤러 (new MemberFormControllerV1())

  • service()
    • requestURI 조회 : String requestURI = request.getRequestURI();
    • controllerMap에서 찾기 : ControllerV1 controller = controllerMap.get(requestURI);
    • 컨트롤러 없으면 404(SC_NOT_FOUND) 상태 코드 반환 : response.setStatus(HttpServletResponse.SC_NOT_FOUND);
    • 컨트롤러 찾으면 호출하고 해당 컨트롤러 실행: controller.process(request, response);
  • JSP
    • 이전 MVC에서 사용했던 것 그대로 사용

 

 

3. View 분리 - v2

 

# 중복 부분

  • 모든 컨트롤러에서 뷰로 이동하는 부분에 중복 존재
  • 별도로 뷰를 처리하는 객체 생성

  • 이 부분을 MyView라는 클래스로 따로 뺌

 

# v2 구조

  • controller에서 JSP를 직접 forward 하지 않음

출처 : 김영한님 강의 자료

# ControllerV2

  • 앞서 만든 V1과 동일하지만 return 값이 MyView
  • 컨트롤러에서 MyView를 반환

 

# 회원 등록 컨트롤러 / 회원 저장 컨트롤러 / 회원 목록 컨트롤러 

  • 기존에 각각 컨트롤러에서 호출하던 복잡한 dispatcher.forward() 부분을 호출하지 않아도 됨
  • 단순히 MyView 객체를 생성하고 뷰 이름을 넣고 반환

# 프론트 컨트롤러

  • 앞서 ControllerV2의 반환 타입이 MyView이므로, 프론트 컨트롤러도 해당 타입으로 받음
  • view.render()를 호출하면 forward 로직을 수행해서 JSP가 실행됨
  • 프론트 컨트롤러의 도입으로 MyView 객체의 render() 호출 영역은 일관된 처리 가능 
  • 각각의 컨트롤러는 MyView 객체를 생성만 해서 반환하면 됨

 

 

4. Model 추가 - v3

 

# v3 구조

출처 : 김영한님 강의 자료

# 서블릿 종속성 제거

  • 컨트롤러 입장에서 HttpServletRequest, HttpServletResponse 꼭 필요하지 않음
  • 요청 파라미터 정보는 Map을 사용 (컨트롤러 서블릿 기술 몰라도 됨)
  • request 객체를 Model로 사용하는 대신 별도의 Model 객체 생성 후 반환 (기존의 request 역할)

 

# 뷰 이름 중복 제거

  • 컨트롤러에서 지정하는 뷰 이름에 중복이 있음 ("/WEB-INF/views/...")
  • 컨트롤러는 뷰의 논리 이름(ex. new-form)을 반환하고, 실제 물리 위치(/WEB-INF/views/new-form.jsp) 는 프론트 컨트롤러에서 처리

 

# ModelView

  • 기존 방식 : 컨트롤러에서 HttpServletRequest 사용하고, Model은 request.setAttribute() 통해 저장하고 뷰에 전달
  • 서블릿 종속성 제거 : Model 직접 생성, View 이름까지 전달하는 객체 생성

 

# ControllerV3

  • 해당 컨트롤러에서는 서블릿 기술 사용하지 않음
  • 구현이 단순해지고, 테스트에 용이
  • 기존에 HttpServletRequest가 제공하는 파라미터는 프론트 컨트롤러가 paramMap에 담아서 호출
  • 응답 결과로 ModelView 객체 반환 (뷰 이름과 뷰에 전달할 Model 데이터)

 

# MemberFormControllerV3 - 회원 등록 폼

  • ModelView를 생성할 때 논리 이름 지정하고 반환 (물리 이름은 프론트 컨트롤러에서 처리)

 

# MemberSaveControllerV3 - 회원 저장

  • 파라미터 정보는 map에 담겨있고, map에서 요청 파라미터 조회

  • member 객체 모델에 담고 반환

 

# FrontControllerServletV3

  • 뷰 리졸버
    • MyView view = viewResolver(viewName);
    • 컨트롤러가 반환한 논리 뷰 이름을 실제 물리 뷰 경로로 변경
    • 실제 물리 경로가 있는 MyView 객체 반환
  • 뷰 객체 통해서 HTML 화면 렌더링
    • view.render(mv.getModel(), request, response);
    • JSP는 reqeust.getAttribute()로 데이터를 조회하므로, 그
    • 모델이 있는 데이터를 request attribute로 변경
    • JSP로 포워딩 해서 JSP 렌더링

 

 

5. 단순하고 실용적인 컨트롤러 - v4

 

# V4 구조

  • 기존 구조는V3와 같고, 대신 컨트롤러가 ModelView를 반환하지 않고 ViewName만 반환 (3)
  • 항상 ModelView 객체를 생성하고 반환하는 부분 번거로움

출처 : 김영한님 강의 자료

# ControllerV4

  • ModelView 없음
  • model 객체는 파라미터로 전달되고, 뷰의 이름만 반환

# MemberFormControllerV4 / MemberSaveControllerV4 / MemberListControllerV4

  • 논리 이름만 반환하면 된다 (ex. retrun "new-form");
  • 모델이 파라미터로 전달되므로, 직접 생성하지 않아도 됨

# FrontControllerServletV4

  • V3 버젼과 거의 동일, 추가 된 부분 모델 객체 전달 
  • 모델 객체를 프론트 컨트롤러에서 생성해서 넘겨줌
  • 컨트롤러에서 모델 객체에 값을 담으면 여기에 그대로 담겨있음 

  • 컨트롤러가 직접 뷰의 논리 이름 반환 (이 값으로 물리 뷰 찾음)

 

 

6. 유연한 컨트롤러1 - v5

 

# 어댑터 패턴

  • 기존의 프론트 컨트롤러는 한가지 방식의 컨트롤러 인터페이스만 사용 가능 (완전히 다른 컨트롤러에 대해서 호환 불가능)
  • 프론트 컨트롤러가 다양한 방식의 컨트롤러를 처리할 수 있도록 

# v5 구조

  • 핸들러 어댑터
    • 중간에 어댑터 역할을 하는 어댑터
    • 다양한 종류의 컨트롤러 호출 가능
  • 핸들러 
    • 컨트롤러의 이름을 더 넓은 범위인 핸들러로 변경

출처 : 김영한님 강의 자료

 

# MyHandlerAdapter

  • boolean supports(Object Handler); 
    • handler는 컨트롤러를 말함
    • 어댑터가 해당 컨트롤러를 처리할 수 있는지 판단하는 메서드
  • ModelView handle(HttpServletRequest request, HttpServletResponse response, Object Handler) throws ServletException, IOException; 
    • 어댑터는 실제 컨트롤러 호출, 결과로 ModelView 반환
    • 이전에는 프론트 컨트롤러가 실제 컨트롤러를 호출했다면, 이제는 어댑터를 통해 컨트롤러를 호출

 

# ControllerV3HandlerAdapter

  • 해당 컨트롤러(V3)를 처리할 수 있는 어댑터인지 체크

  • handler를 컨트롤러 V3로 변환하고 호출 (V3는 ModelView 반환하므로 똑같이)

 

# FrontControllerServletV5

  • 컨트롤러(Controller) -> 핸들러 (Handler)
    • 이전에는 컨트롤러를 직접 매핑해서 사용
    • 어댑터가 지원하면 어떤 것이라도 URL 매핑해서 사용 가능 
  • 매핑 정보
    • 매핑 정보의 값을 아무 값이나 받을 수 있도록 Object로 변경
    • private final Map<String, Object> handlerMappingMap = new HashMap<>();
  • 핸들러 매핑
    • Object handler = getHandler(request);
    • 핸들러 매핑 정보에서 URL에 매핑 핸들로(컨트롤러) 객체 반환

  • 핸들러를 처리할 수 있는 어댑터 조회
    • MyHandlerAdapter adapter = getHandlerAdapter(handler);
    • handler를 처리할 수 있는 어댑터를 support를 통해서 찾음
    • ControllerV3HandlerAdapter 객체가 반환
  • 어댑터 호출
    • ModelView mv = adapter.handle(request, response, handler);
    • 실제 어댑터 호출
    • 어댑터는 handler(컨트롤러) 호출하고 어댑터에 맞춰 반환

 

7. 유연한 컨트롤러2 - v5

 

# FrontControllerServletV5 (+ControllerV4 추가)

  • handlerMappingMap에 ControllerV4 사용하는 컨트롤러 추가
  • 처리할 수 있는 어댑터 ControllerV4HandlerAdapter 추가

# ControllerV4HandlerAdapter

  • 앞서 동일하게 supports 메서드로 처리 가능한 handler인지 체크
    • return (handler instanceof ControllerV4);
  • paramMap, model을 만들어서 컨트롤러 호출하고 viewName을 반환
  • 어댑터 변환 (String -> modelView)
    • ControllerV4는 뷰의 이름을 반환, 어댑터는 ModelView를 반환
    • 그래서 어댑터는 ControllerV4가 반환한 뷰의 이름을 ModelView로 형식 맞춰서 반환