입구를 어떻게 하나로 좁힐지에 대한 생각.

하나로 좁혀서 들어갔으면서 각각 컨트롤러에 어떻게 분배해줄까에 대한 생각.

아래 코드를 보면 FrontController하나만 Servlet으로 두고 특히 /front-controller/v1/* 에서 “ * “ 을 이용해서 /front-controller/v1 으로 시작하는 것들을 모두 이 서블릿으로 받아서 HashMap의 key, value를 통해서 어떤 url("/front-controller/v1/members/new-form")이 들어오면 어떤 controller(new MemberFormControllerV1())를 실행시키는 프론트 컨트롤러 방식을 취하며 크게 구조적으로 나눠봤다.


@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());
    }

    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("FrontControllerServletV1");

        String requestURI = request.getRequestURI();
        System.out.println("requestURI = " + requestURI);

        ControllerV1 controller = controllerMap.get(requestURI);
        if(controller == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        controller.process(request, response);
    }
}

// 프론트 컨트롤러를 거쳐 각각 컨트롤러로,viewPath를 적어주고 dispathcer로 view쪽으로
forward 하고있다. (이렇게 view쪽으로 옮기는 것을 렌더링, render() 한다고 표현!)
public class MemberFormControllerV1 implements ControllerV1 {

    @Override
    public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String viewPath = "/WEB-INF/views/new-form.jsp"; // jsp 파일을 WEB-INF안에 두면 무조건 dispatcher로 forward 해야 접근할 수 있다!
        RequestDispatcher requestDispatcher = request.getRequestDispatcher(viewPath); // dispatcher가 관점을 controller에서 view로! 옮겨줌!
        requestDispatcher.forward(request, response); // forward() : 다른 서블릿이나 JSP로 이동할 수 있는 기능
    }
}

위 코드는 아래 사진처럼 모든 요청을 FrontController로 받는다는 구조적 변화에 의의가 있음. 그렇다면 뭔가 반복되는 것들이 있지 않냐!!

image.png

다음생각은 View를 분리해주자는 생각! 똑같이 FrontController가 모든 응답을 받고 그에 따른 Controller를 호출한 다음, MyView를 반환해주면? FrontController그에 따라서 render()한 값을 MyView가 JSP로 변환하여 응답을 만들어준다!! → 더 복잡해진 것 같지만?? 코드보면 훨씬 거 간결해짐!!

image.png

아래 코드를 보면 이제 ControllerV2 인터페이스가 MyView를 반환하게 하여, view화면으로 forward()하는 (render()) 를 분리해서 url 매칭과 서비스 로직에만 집중하고 있음을 볼 수 있다!!

public interface ControllerV2 {

    MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;

}

@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());
    }

    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String requestURI = request.getRequestURI();
        System.out.println("requestURI = " + requestURI);

        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);
    }
}

// 프론트 컨트롤러(서블릿)를 지나 할당받은 컨트롤러. render()는 MyView로 따로 빼서 관리하기에 URL에
// 따른 MyView 클래스만 반환해주는 것을 볼 수 있다!
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");
    }
}

// viewPath에 따라 dispatcher로 forward() (render()) 해주는 MyView 클래스! -> View를 구분.
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);
    }
}

여기까지 봐보면 개선할 점이 서블릿의 종속성을 제거하자는 의견이 나온다!!

요청 파라미터의 정보는 자바의 Map을 이용하여 넘기도록하면 지금 구조에서는 컨트롤러가 서블릿 기술을 몰라도 작동할 수 있다. 그리고 request 객체를 Model로 사용하는 대신에 별도의 Model 객체를 만들어서 반환하면 된다.