들어가며

  • 저번 포스트에서 프론트 컨트롤러 구현을 시작하였다.
  • 먼저 회원 데이터를 저장하고 보여주는 간단한 서비스를 만들기 위해 Domain, Repository를 작성하였다. 이번 포스트에서는 각 컨트롤러 구현을 수행할 것이다.
  • 모든 진행 결과는 필자의 깃허브에서 볼 수 있다.
 

GitHub - ForteEscape/MVC-Study

Contribute to ForteEscape/MVC-Study development by creating an account on GitHub.

github.com

 

 

 

프론트 컨트롤러 구현 - 계속

컨트롤러 구현

  • 이전 포스트에서도 적었지만 프론트 컨트롤러가 각 컨트롤러의 정보를 알고 있는 상태에서 클라이언트의 요청을 처리할 수 있는 컨트롤러에 요청을 위임한다고 하였다.
  • 그리고 각 컨트롤러의 정보는 <컨트롤러의 URL Mapping, 컨트롤러 객체>를 가진 Map이다. 컨트롤러에 매핑된 URL은 String 형식으로 할 수 있지만 객체의 경우에는 각 클래스들로 타입이 다르기 때문에 저장이 힘들어 보인다.
  • 이럴때 사용할 수 있는 것이 일반화(Generalization)이다.
    • 각 컨트롤러 객체들은 처리하는 방식은 다르지만 "들어온 요청을 처리한다" 라는 책임은 동일하게 지고 있다. 그렇다면 이 객체들을 추상화 시켜 "들어온 요청을 처리하는 컨트롤러 역할"로 일반화시킬 수 있다.
    • 자바에서는 이를 인터페이스로 구현하거나, 추상 클래스로 구현할 수 있다. 필자는 인터페이스를 통해 구현할 것이다.

  • 이렇게 인터페이스로 일반화시킨다면 다형성을 활용하여 각 객체들을 Mapping Store에 저장시킬 수 있다.
public interface ControllerV1{
    void process(
        HttpServletRequest request, 
        HttpServletResponse response
    ) throws ServletException, IOException;
}
  • 이후 controller 패키지를 만들고 그 내부에 컨트롤러 구현체들을 만들 것이다.
public class MemberFormControllerV1 implements ControllerV1 {
    @Override
    public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String viewPath = "/WEB-INF/views/new-form.jsp";
        request.getRequestDispatcher(viewPath).forward(request, response);
    }
}
public class MemberListControllerV1 implements ControllerV1 {

    private static final 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";
        request.getRequestDispatcher(viewPath).forward(request, response);
    }
}
public class MemberSaveControllerV1 implements ControllerV1 {

    private static final MemberRepository memberRepository = MemberRepository.getInstance();

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

        Member member = new Member(userName, userAge);
        memberRepository.save(member);

        request.setAttribute("member", member);

        String viewPath = "/WEB-INF/views/save-result.jsp";
        request.getRequestDispatcher(viewPath).forward(request, response);
    }
}
  • Controller Interface를 구현한 구현체 컨트롤러들이다.

프론트 컨트롤러 구현

  • 뒷단에서 요청을 처리할 컨트롤러들을 구현하였고 이제 남은건 앞에서 모든 요청을 받는 프론트 컨트롤러를 구현하는 것만 남았다.
@WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/*")
public class FrontControllerServletV1 extends HttpServlet {
    private final 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("FrontControllerV1.service");
        String requestURI = request.getRequestURI();

        System.out.println(requestURI);

        ControllerV1 controllerV1 = controllerMap.get(requestURI);
        if (controllerV1 == null){


            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        controllerV1.process(request, response);
    }
}
  • /front-controller/v1/*의 urlPatterns는 해당 경로로 들어오는 모든 요청에 대하여 이 프론트 컨트롤러가 호출된다는 것이다.
  • controllerMap은 이전에 말한 것 처럼 URL을 key로, 컨트롤러 객체를 value로 하는 Map이다. ControllerV1 인터페이스를 통하여 하나의 타입으로 일반화시켜 관리가 가능하다.
  • service에서는 다음과 같은 동작을 수행한다.
    • 먼저 들어온 요청의 URI를 확인한다.
    • 이후 controllerMap에서 해당 URI를 key로 넣어 대응되는 컨트롤러가 존재하는지 확인한다.
    • 만약 찾지 못했다면 해당 리소스를 찾지 못했다는 상태 응답인 404(NOT_FOUND)를 반환시키고 종료한다.
    • 찾았다면 해당 컨트롤러에 요청을 위임시킨다.
  • 만약 공통 로직을 처리해야 한다면 프론트 컨트롤러에 공통 로직을 구현하면 된다.
  • 이로서 새로운 컨트롤러를 구현해도 controllerMap에 URI와 객체를 넣음으로서 공통 로직을 새로운 컨트롤러에 구현해야 할 필요가 사라졌다.

 

 

 

정리

  • 전체적인 프론트 컨트롤러 구현을 살펴보았다. 이번 과정에서는 프론트 컨트롤러를 도입만 수행하였고 추가적인 변경은 수행하지 않았다.
  • 다음 포스트에서는 뷰(View)를 별도로 분리하여 관리하도록 유도할 것이다. 그렇게 함으로서 어떤 이득이 들어오는지를 알아본다.
복사했습니다!