public interface ControllerV1 {
void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException;
}
@WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/*")
public class FrontControllerServletV1 extends HttpServlet {
private Map<String, ControllerV1> controllerMap = new HashMap<>();
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 {
String requestURI = request.getRequestURI();
ControllerV1 controller = controllerMap.get(requestURI);
if(controller == null){
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
controller.process(request,response);
}
}
프론트 컨트롤러 v1의 핵심은 엔트리가 하나로 줄었다는 것이다.
모든 진입점을 서블릿으로 등록했던 것과 달리 v1은 프론트 컨트롤러 부분만 서블릿으로 등록이 되어 모든 요청을 받는다.
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);
}
}
@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 {
String requestURI = request.getRequestURI();
ControllerV2 controller = controllerMap.get(requestURI);
if(controller == null){
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
MyView process = controller.process(request, response);
process.render(request, response);
}
}
V2의 핵심은 기존의 컨트롤러가 FrontController에게 뷰를 호출하는 역할을 위임했다는 것이다.
따라서 모든 요청은 서블릿 컨테이너 처음과 끝 모두 FrontController를 거치게 된다.
@WebServlet(name = "frontControllerServletV3", urlPatterns = "/front-controller/v3/*")
public class FrontControllerServletV3 extends HttpServlet {
private Map<String, ControllerV3> controllerMap = new HashMap<>();
public FrontControllerServletV3() {
controllerMap.put("/front-controller/v3/members/new-form", new MemberFormControllerV3());
controllerMap.put("/front-controller/v3/members/save", new MemberSaveControllerV3());
controllerMap.put("/front-controller/v3/members", new MemberListControllerV3());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
ControllerV3 controller = controllerMap.get(requestURI);
if(controller == null){
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
Map<String, String> paramMap = createParamMap(request);
ModelView mv = controller.process(paramMap);
MyView view = viewResolver(mv);
view.render(mv.getModel(),request, response);
}
private MyView viewResolver(ModelView mv) {
return new MyView("/WEB-INF/views/" + mv.getViewName() + ".jsp");
}
private Map<String, String> createParamMap(HttpServletRequest request) {
Map<String, String> paramMap = new HashMap<>();
//paramMap
request.getParameterNames().asIterator()
.forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));
return paramMap;
}
}
V3에서는 새롭게 ModelView와 ViewResolver가 추가되었다.
public class MemberSaveControllerV3 implements ControllerV3 {
MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public ModelView process(Map<String, String> paramMap) {
String username = paramMap.get("username");
int age = Integer.parseInt(paramMap.get("age"));
Member member = new Member(username, age);
memberRepository.save(member);
ModelView modelView = new ModelView("save-result");
modelView.getModel().put("member", member);
return modelView;
}
}
기존의 controller.process()
에서 request.setAttribute()
역할이 제거되었다. 그렇기때문에 컨트롤러로 request, response를 넘길 필요가 없어지고 단순히 비즈니스 로직만 남아있게 되었다.
대신 프론트컨트롤러에선 쿼리파라미터들을 map으로 만들어 넘기게 되는데, 이는 프론트 컨트롤러가 파라미터를 가공해서 넘길 수 있게 되었음을 의미한다.
기존은 /WEB-INF/jsp/views/members.jsp 와 같이 물리적 파일 경로로 렌더링할 파일을 선택했다면 viewResolver를 통해 members 와 같은 논리적 경로로 파일을 지정한다.
view 파일들의 디렉토리가 변경된다면 컨트롤러의 코드를 건들지 않고 ViewResolver의 코드만 변경하면 된다.