Search
Duplicate
📒

[Spring Study] 04-2. FrontController V1(Handler)~V2( View 분리)

상태
수정중
수업
Spring Study
주제
MVC
연관 노트
3 more properties
참고

프론트 컨트롤러

NOTE
FrontController + HandlerMapping 도입
FrontController 도입 전
각각의 Controller에 공통된 코드들이 많다는 문제가 있다
이로 인해 중복이 발생하고 코드의 변경이 필요할 때 유지보주성이 떨어진다는 문제가 발생한다.
FrontController 도입 후
각 컨트롤러 앞단에 FrontController를 두어 공통 로직을 FrontController에서 처리하도록 한다,

FrontController 패턴 특징

[참고]
Spring의 웹 MVC와 FrontController
Spring 웹 MVC의 핵심도 바로 FrontController
Spring 웹 MVC의 DispatcherServletFrontController 패턴으로 구현 됨

FrontController 패턴 특징

NOTE
FrontController 하나로 클라이언트의 요청을 받음!
FrontController가 요청에 맞는 컨트롤러를 찾아서 호출
공통 처리 가능 FrontControllr를 제외한 나머지 Controller는 Servlet을 사용하지 않음

FrontController - V1

NOTE
앞의 MVC 패턴과 대표적인 차이점은 요청 url을 통해 알맞은 Handler (Controller)를 찾아와서 실행을 시킨다
맵핑정보(Handler)를 조회해서 컨트롤러를 실행시킴
이러한 과정에서 URL과 매핑된 Handler 를 저장하는 공간을 Handler Mapping이라고 부른다 즉 Handler Mapping으로부터 Handler 를 조회해서 알맞은 Handler (Controller)를 실행하는 과정이다.

V1 - FrontController

NOTE
@WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/*") public class FrontControllerServletV1 extends HttpServlet { // URI를 맵핑할 Handler 저장소 private Map<String, ControllerV1> controllerMap = new HashMap<>(); // 생성자에 Handler를 저장한다. public FrontControllerServletV1() { controllerMap.put("/front-controller/v1/members/new-form", new MemberFormControllerV1()); controllerMap.put("/front-controller/v1/members/save", new MemberSaveControllerV1()); controllerMap.put("/front-controller/v1/members", new MemberListControllerV1()); } @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("FrontControllerServletV1.service"); // /front-controller/v1/members String requestURI = request.getRequestURI(); ControllerV1 controller = controllerMap.get(requestURI); if(controller == null){ response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } controller.process(request, response); } }
Java
복사
Map<String, ControllerV1> controllerMap은 다양한 Controller 객체를 저장하는 Handler Mapping이다.
public class MemberFormControllerV1 implements ControllerV1 { @Override public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String viewPath = "/WEB-INF/views/new-form.jsp"; RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath); dispatcher.forward(request, response); } }
Java
복사
URI에 따른 Controller로 들어가서 View를 호출한다.
controllMap 객체에 URI 패턴을 등록한다!
urlPatterns = "/front-controller/v1/*"
패턴 매칭으로 /front-controller/v1 를 포함한 모든 하위 요청을 이 서블릿에서 처리한다.
ex) /front-controller/v1 , /front-controller/v1/a , /front-controller/v1/a/b
service()
핸들러 매핑에서 URI를 조회해서 실제 호출할 Controller를 ControllerMap에서 찾는다
만약 없다면 404(SC_NOT_FOUNT) 상태 코드 반환
Controller를 찾았다면 controller.process(request, response) 실행

FrontController - V2

NOTE
View 관련 로직 별도의 객체로 분리한다!
V2 구조는 V1구조에서 View와 관련된 로직을 View 객체(MyView)로 분리시킨 구조다.
String viewPath = "/WEB-INF/views/new-form.jsp"; RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath); dispatcher.forward(request, response);
Java
복사
모든 Controller에서 정의된 View 로직
모든 Controller들은 로직을 수행한 후 특정 VIew로 흐름을 이동하기 위해 이와 같은 코드를 작성했다.
그러나 위와 같은 코드가 모든 Controller에 존재하기에 중복이 발생하고 이로인해 유지보수에 어려움이 생겼다.
(경로나 형식이 바뀐다면 모든 Controller의 코드를 일일히 수정해줘야 한다는 문제가 발생한다)
이를 해결하기 위해, View 관련 로직을 별도의 객체(MyView)로 분리하는 방법을 고안해냈고 이를 활용한게 V2이다.

V2 - FrontController

NOTE
@WebServlet(name = "frontControllerServletV2", urlPatterns = "/front-controller/v2/*") public class FrontControllerServletV2 extends HttpServlet { private Map<String, ControllerV2> controllerMap = new HashMap<>(); public FrontControllerServletV2() { controllerMap.put("/front-controller/v2/members/new-form", new MemberFormControllerV2()); controllerMap.put("/front-controller/v2/members/save", new MemberSaveControllerV2()); controllerMap.put("/front-controller/v2/members", new MemberListControllerV2()); } @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // /front-controller/V2/members String requestURI = request.getRequestURI(); ControllerV2 controller = controllerMap.get(requestURI); if(controller == null){ response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } MyView view = controller.process(request, response); view.render(request, response); } }
Java
복사
V1과 크게 변하지는 않았지만 view.render(request, response)를 통해 데이터 흐름을 이동시키고 있다
MyView view 객체를 활용해 중복된 코드를 줄이고 역할을 위임시켰다.

V2 - MyView

NOTE
public class MyView { private String viewPath; public MyView(String viewPath) { this.viewPath = viewPath; } public void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath); dispatcher.forward(request, response); } }
Java
복사
MyView 클래스는 View와 관련된 로직만을 수행하는 객체이다
저장하고 있는 경로를 통해 데이터 흐름을 넘기는 작업을 하며, 여기서 구현되어 있지 않지만, 때에 따라 prefixsuffix를 분리하여 공통으로 관리할 수 있다.

V2 - ControllerV2

NOTE
public class MemberFormControllerV2 implements ControllerV2 { @Override public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { return new MyView("/WEB-INF/views/new-form.jsp"); } }
Java
복사
View와 관련된 로직은 MyView에서 처리하므로 경로를 넘겨주어 반환시키도록 변환

V2 - V1 회원등록 폼 비교

NOTE
// V1 @Override public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String viewPath = "/WEB-INF/views/new-form.jsp"; RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath); dispatcher.forward(request, response); } // V2 @Override public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { return new MyView("/WEB-INF/views/new-form.jsp"); }
Java
복사
foward로직이 VIew로 분리되었다.