Front Controller란?

모든 요청의 최초 진입점이 되는 컨트롤러다. 프론트 컨트롤러가 모든 요청을 받고 요청을 분석하여 하위 컨트롤을 호출한다. 즉, 모든 요청이 프론트 컨트롤러를 거침으로써 공통 로직을 처리할 수 있다. 프론트 컨트롤러가 먼저 모든 요청을 받기 때문에 나머지 컨트롤러에는 서블릿을 사용하지 않아도 된다. Spring MVC의 핵심이 이 프론트 컨트롤러에 있다.

출처 : 김영한. 스프링 MVC 1편 강의자료. 인프런(https://www.inflearn.com/course/스프링-mvc-1)

 

ControllerV1 - 인터페이스

public interface ControllerV1 {
    void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}

먼저 실제 로직을 실행할 컨트롤러의 인터페이스를 정의한다. 각 컨트롤러가 이 인터페이스를 구현하면 프론트 컨트롤러는 하위 컨트롤러의 내용과 상관없이 동일하게 사용할 수 있다.

 

ControllerV1 - 구현체

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

public class MemberSaveControllerV1 implements ControllerV1 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));

        Member member = new Member(username, age);
        memberRepository.save(member);

        request.setAttribute("member", member);

        String viewPath = "/WEB-INF/views/save-result.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }
}

public class MemberListControllerV1 implements ControllerV1 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List<Member> members = memberRepository.findAll();

        request.setAttribute("members", members);

        String viewPath = "/WEB-INF/views/members.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }
}

 

 

FrontControllerServletV1 - 프론트 컨트롤러

@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 {
        System.out.println("FrontControllerServletV1.service");

        String requestURI = request.getRequestURI();
        ControllerV1 controllerV1 = controllerMap.get(requestURI);
        if (controllerV1 == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        controllerV1.process(request, response);
    }
}

프론트 컨트롤러는 요청의 URL과 그 요청을 처리할 컨트롤러의 구현체가 담긴 HashMap을 필드로 갖는다. 그리고 생성자 메서드가 실행될 때 URL과 컨트롤러 구현체가 ControllerMap의 key, value로 저장된다. 프론트 컨트롤러에 지정된 urlPatterns는 /front-controller/v1/*이다. 그리고 컨트롤러 구현체의 key인 URL을 보면 /front-controller/v1 으로 시작되는 것을 볼 수 있다. /front-controller/v1의 하위 요청은 모두 프론트 컨트롤러가 받기 때문에 각 컨트롤러 구현체를 프론트 컨트롤러에서 호출할 수 있다.

현재 요청의 URI를 조회하여 실제 호출할 컨트롤러를 필드인 ControllerMap에서 찾는다. 만약 존재하지 않으면 SC_NOT_FOUND(404Error)를 응답한다. 존재한다면 해당 컨트롤러의 process()를 호출하여 요청을 처리한다. 컨트롤러는 인터페이스를 통해 구현되었기 때문에 프론트 컨트롤러는 어느 컨트롤러인지 상관없이 로직의 일관성을 가져갈 수 있다.

그러나 여기서도 중복이 있다. 모든 컨트롤러에서 뷰로 이동하는 부분이다. 이 부분을 분리하기 위해 화면을 렌더링하는 뷰 객체를 만든다.

+ Recent posts