참고
프론트 컨트롤러
NOTE
FrontController + HandlerMapping 도입
FrontController 도입 전
•
각각의 Controller에 공통된 코드들이 많다는 문제가 있다
•
이로 인해 중복이 발생하고 코드의 변경이 필요할 때 유지보주성이 떨어진다는 문제가 발생한다.
FrontController 도입 후
•
각 컨트롤러 앞단에 FrontController를 두어 공통 로직을 FrontController에서 처리하도록 한다,
FrontController 패턴 특징
•
[참고]
◦
Spring의 웹 MVC와 FrontController
▪
Spring 웹 MVC의 핵심도 바로 FrontController다
▪
Spring 웹 MVC의 DispatcherServlet이 FrontController 패턴으로 구현 됨
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와 관련된 로직만을 수행하는 객체이다
•
저장하고 있는 경로를 통해 데이터 흐름을 넘기는 작업을 하며, 여기서 구현되어 있지 않지만, 때에 따라 prefix와 suffix를 분리하여 공통으로 관리할 수 있다.
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로 분리되었다.