Front Controller
이전에는 공통된 기능이 있다면 매번 중복해서 선언하고 호출해야만 했다. 하지만 Dispatcher Servlet으로 구현된 Front Controller를 사용하면 클라이언트 요청을 Front Controller가 다 받은 다음 요청에 맞는 컨트롤러를 찾아서 호출한다.
1. 클라이언트 요청을 받기 위한 Front Controller 도입
Front Controller는 인터페이스에 의존하면서 다른 컨트롤러들을 편리하게 호출할 수 있다. 다른 컨트롤러(구현체)들은 이 인터페이스를 구현하게 된다.
public interface ControllerV1 {
void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
MemberFormControllerV1, MemberSaveControllerV1, MemberListControllerV1 구현체들은 ControllerV1 인터페이스의 구현체로 process를 정의하며 그 내용은 기존 서블릿과 동일하다.
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);
}
}
Front Controller 역할을 하는 Servlet이다. "/front-controller/v1/*"로 url 패턴을 설정해서, /front-controller/v1 을 포함한 모든 하위 호출은 이 Servlet이 호출되도록 한다.
매핑 정보를 담는 controllerMap을 선언하고(key: 요청 url, value: 호출된 컨트롤러), Constructor를 통해 각각 <호출되는 URI ↔ 컨트롤러>를 매핑시킨다.
이제 request가 들어오면 getRequestURI를 통해 URI를 가져오고 controllerMap에서 URI에 매핑되는 컨트롤러를 호출하는 것이다.
@WebServlet(name="frontControllerServletV1", urlPatterns = "/front-controller/v1/*")
public class FrontControllerSubvletV1 extends HttpServlet {
// 매핑 정보
private Map<String, ControllerV1> controllerMap = new HashMap<>();
// Constructor
public FrontControllerSubvletV1() {
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");
String requestURI = request.getRequestURI();
ControllerV1 controller = controllerMap.get(requestURI);
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return ;
}
controller.process(request, response);
}
}
View 분리
모든 컨트롤러에서 뷰로 이동하는 부분에 중복이 있고 깔끔하지 않다.
String viewPath = "/WEB-INF/views/members.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
이 부분을 분리하기 위해 별도로 뷰를 분리하는 객체를 생성한다. 구조는 다음과 같이 변형된다.
이제 Controller는 JSP로 직접 forward하지 않는다. 대신에 MyView라는 객체를 만들어 Front Controller 반환하면, Front Controller가 render()를 호출하여 JSP로 forward한다.
MyView라는 객체는 앞선 중복되었던 코드와 거의 동일하다. viewPath를 생성자로부터 받아오고, 이에 맞게 RequestDispatcher를 생성한다음 forward하여 렌더링한다.
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);
}
}
이제 인터페이스를 만든다. 이전에 ControllerV1에서 구현한 process와 동일하지만, MyView를 반환하도록 설정한다.
public interface ControllerV2 {
MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
인터페이스를 구현한 MemberFormControllerV2, MemberSaveControllerV2, MemberListControllerV2 구현체들은 마찬가지로 로직은 동일하다.
// MemberListControllerV2
public class MemberListControllerV2 implements ControllerV2 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<Member> members = memberRepository.findAll();
request.setAttribute("members", members);
return new MyView("/WEB-INF/views/members.jsp");
}
}
Front Controller 역할을 하는 Servlet은 거의 동일하지만 MyView 객체를 render 메서드를 호출하여 렌더링하는 것에 차이가 있다.
@WebServlet(name="frontControllerServletV2", urlPatterns = "/front-controller/v2/*")
public class FrontControllerSubvletV2 extends HttpServlet {
// 매핑 정보
private Map<String, ControllerV2> controllerMap = new HashMap<>();
// Constructor
public FrontControllerSubvletV2() {
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 {
String requestURI = request.getRequestURI();
ControllerV2 controller = controllerMap.get(requestURI);
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return ;
}
MyView myView = controller.process(request, response);
myView.render(request, response);
}
}
'🌱 Spring > MVC ①' 카테고리의 다른 글
[Spring] MVC _ 개발자를 위한 기능 개선 (0) | 2023.07.17 |
---|---|
[Spring] MVC _ ViewModel 추가 (0) | 2023.07.17 |
[Spring] 회원 관리 웹 어플리케이션 4 _ ServletMVC 패턴 (0) | 2023.07.15 |
[Spring] 회원 관리 웹 어플리케이션 3 _ JSP (0) | 2023.07.14 |
[Spring] 회원 관리 웹 어플리케이션 1 _ Domain/Repo/Test (0) | 2023.07.12 |