-
[스프링 MVC 1] 4. MVC 프레임워크 만들기INFLEARN/스프링 MVC 1편 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 객체 반환
- MyView view = viewResolver(viewName);
- 뷰 객체 통해서 HTML 화면 렌더링
- view.render(mv.getModel(), request, response);
- JSP는 reqeust.getAttribute()로 데이터를 조회하므로, 그
- 모델이 있는 데이터를 request attribute로 변경
- JSP로 포워딩 해서 JSP 렌더링
- view.render(mv.getModel(), request, response);
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는 컨트롤러를 말함
- 어댑터가 해당 컨트롤러를 처리할 수 있는지 판단하는 메서드
- 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에 매핑 핸들로(컨트롤러) 객체 반환
- Object handler = getHandler(request);
- 핸들러를 처리할 수 있는 어댑터 조회
- MyHandlerAdapter adapter = getHandlerAdapter(handler);
- handler를 처리할 수 있는 어댑터를 support를 통해서 찾음
- ControllerV3HandlerAdapter 객체가 반환
- MyHandlerAdapter adapter = getHandlerAdapter(handler);
- 어댑터 호출
- ModelView mv = adapter.handle(request, response, handler);
- 실제 어댑터 호출
- 어댑터는 handler(컨트롤러) 호출하고 어댑터에 맞춰 반환
7. 유연한 컨트롤러2 - v5
# FrontControllerServletV5 (+ControllerV4 추가)
- handlerMappingMap에 ControllerV4 사용하는 컨트롤러 추가
- 처리할 수 있는 어댑터 ControllerV4HandlerAdapter 추가
# ControllerV4HandlerAdapter
- 앞서 동일하게 supports 메서드로 처리 가능한 handler인지 체크
- return (handler instanceof ControllerV4);
- return (handler instanceof ControllerV4);
- paramMap, model을 만들어서 컨트롤러 호출하고 viewName을 반환
- 어댑터 변환 (String -> modelView)
- ControllerV4는 뷰의 이름을 반환, 어댑터는 ModelView를 반환
- 그래서 어댑터는 ControllerV4가 반환한 뷰의 이름을 ModelView로 형식 맞춰서 반환
'INFLEARN > 스프링 MVC 1편' 카테고리의 다른 글
[스프링 MVC 1] 6. 스프링 MVC - 기본 기능 (0) 2021.11.05 [스프링 MVC 1] 5. 스프링 MVC - 구조 이해 (0) 2021.10.27 [스프링 MVC 1] 3. 서블릿, JSP, MVC 패턴 (0) 2021.10.18 [스프링 MVC 1] 2. 서블릿 (0) 2021.10.16 [스프링 MVC 1] 1. 웹 애플리케이션 이해 (0) 2021.10.13