FrontController

wangjh789·2022년 8월 2일
0

[Spring] 스프링-mvc-1

목록 보기
6/15

FrontController

  • 서블릿 하나로 클라이언트 요청을 다 받는다.
  • 요청에 맞는 컨트롤러를 찾아서 호출한다.
  • 입구가 하나인 건물의 수문장 역할을 한다.
  • 공통 로직 처리를 프론트 컨트롤러에서 처리 가능하다.
  • 스프링 웹 MVC에서의 DispatherServlet 과 같은 역할

Version 1

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은 프론트 컨트롤러 부분만 서블릿으로 등록이 되어 모든 요청을 받는다.

Version2

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를 거치게 된다.

Version3

@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가 추가되었다.

ModelView

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으로 만들어 넘기게 되는데, 이는 프론트 컨트롤러가 파라미터를 가공해서 넘길 수 있게 되었음을 의미한다.

ViewResolver

기존은 /WEB-INF/jsp/views/members.jsp 와 같이 물리적 파일 경로로 렌더링할 파일을 선택했다면 viewResolver를 통해 members 와 같은 논리적 경로로 파일을 지정한다.
view 파일들의 디렉토리가 변경된다면 컨트롤러의 코드를 건들지 않고 ViewResolver의 코드만 변경하면 된다.

profile
기록

0개의 댓글