본문 바로가기
Spring Tutorial

[스프링MVC 22] MVC 프레임워크 만들기: 프론트 컨트롤러 (FrontController)

by 미소5 2023. 7. 28.

  • FrontController 패턴으로공통 처리 가능 (입구 하나로!)
    • 프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받음
    • 프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출
    • 프론트 컨트롤러를 제외한 나머지 컨트롤러(A, B, C)는 서블릿을 사용하지 않아도 됨
  • 스프링 웹 MVC의 핵심이 바로 FrontController
    • 스프링 웹 MVCDispatcherServletFrontController 패턴으로 구현되어 있다.

 

 

 

 

 

 


기존 코드를 최대한 유지하면서, 프론트 컨트롤러 단계적으로 도입해보자! 

먼저 구조를 맞추어두고 점진적으로 리팩터링 해보자.

구조

 

 


  • 서블릿과 비슷한 모양컨트롤러 인터페이스 도입
// 서블릿 모양의 컨트롤러 인터페이스
// 각 컨트롤러들은 이 인터페이스를 구현
public interface ControllerV1 {

    void process(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException;
}

각 컨트롤러들은 이 인터페이스를 구현하면 된다. 프론트 컨트롤러는 이 인터페이스를 호출해서 구현과 관계없이 로직의 일관성을 가져갈 수 있다. 이제 이 인터페이스를 구현한 컨트롤러를 만들어보자. 

 

  • 회원 등록 컨트롤러
/**회원 등록 컨트롤러*/
public class MemberFormControllerV1 implements ControllerV1 {

    @Override
    public void process(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String viewPath = "/WEB-INF/views/new-form.jsp";
        RequestDispatcher dispatcher = req.getRequestDispatcher(viewPath);
        dispatcher.forward(req, resp);
    }
    
}
  • 회원 저장 컨트롤러
/**회원 저장 컨트롤러*/
public class MemberSaveControllerV1 implements ControllerV1 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

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

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

        req.setAttribute("member", member);

        String viewPath = "/WEB-INF/views/save-result.jsp";
        RequestDispatcher dispatcher = req.getRequestDispatcher(viewPath);
        dispatcher.forward(req, resp);
    }
}
  • 회원 목록 컨트롤러
/**회원 목록 컨트롤러*/
public class MemberListControllerV1 implements ControllerV1 {
    
    private MemberRepository memberRepository = MemberRepository.getInstance();

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

        req.setAttribute("members", members);

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

내부 로직은 기존 서블릿과 거의 같다. 

 


  • 프론트 컨트롤러
/**프론트 컨트롤러*/

@WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/*")
public class FrontControllerServletV1 extends HttpServlet {

    private Map<String,ControllerV1> controllerMap=new HashMap<>();
    //controllerMap(매핑 URL, 호출될 컨트롤러)

    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 req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("FrontControllerServletV1.service");

        String requestURI = req.getRequestURI(); //requestURI를 조회해서

        // 실제 호출할 컨트롤러를 controllerMap 에서 찾는다
        ControllerV1 controller = controllerMap.get(requestURI);

        if (controller == null) { //만약 없다면 404(SC_NOT_FOUND) 상태 코드를 반환한다
            resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        controller.process(req, resp); //해당 컨트롤러(찾은 컨트롤러)를 실행
    }
}
  • urlPatterns = "/front-controller/v1/*"
    • /front-controller/v1 를 포함한 하위 모든 요청은 이 서블릿에서 받아들인다.
    • 예) /front-controller/v1 , /front-controller/v1/a , /front-controller/v1/a/b
    • "http://localhost:8080/front-controller/v1/아무거나" 이렇게 실행해도, service 실행된다(서블릿 호출된다)
  •  controllerMap
    • key: 매핑 URL
    • value: 호출될 컨트롤러

 

  • service()
    1. 먼저 requestURI 를 조회해서 실제 호출할 컨트롤러를 controllerMap 에서 찾는다.
      • ControllerV1 controller = controllerMap.get(requestURI); 
        • key에 /front-controller/v1/members 넣으면, value는 객체 MemberListControllerV1가 반환된다
        • 즉, "ControllerV1 controller =  new MemberListControllerV1();"  →다형성 (인터페이스 받을수있다)
    2. 만약 없다면(null) 404(SC_NOT_FOUND) 상태 코드를 반환한다.
    3. 컨트롤러를 찾고 controller.process(request, response); 을 호출해서 해당 컨트롤러를 실행한다.
      • MemberListControllerV1 process가 호출된다.

※JSP는 이전 MVC에서 사용했던 것을 그대로 사용한다.

 


  • 기존과 동일하게 실행된다

 

 



[스프링MVC 21] MVC 패턴의 한계 (tistory.com)

 

[스프링MVC 21] MVC 패턴의 한계

[스프링MVC 20] MVC 패턴으로 회원 관리 웹 애플리케이션 (tistory.com) [스프링MVC 20] MVC 패턴으로 회원 관리 웹 애플리케이션 직접 MVC 패턴을 적용해서 프로젝트를 리팩터링 해보자. 컨트롤러로 서블

joly156.tistory.com

[스프링MVC 20] MVC 패턴으로 회원 관리 웹 애플리케이션 (tistory.com)

 

[스프링MVC 20] MVC 패턴으로 회원 관리 웹 애플리케이션

직접 MVC 패턴을 적용해서 프로젝트를 리팩터링 해보자. 컨트롤러로 서블릿을 사용하고, 뷰로 JSP를 사용하고, Model은 HttpServletRequest 객체를 사용한다. request는 내부에 데이터 저장소를 가지고 있

joly156.tistory.com

[스프링MVC 19] MVC 패턴 (tistory.com)

 

[스프링MVC 19] MVC 패턴

1. 하나의 서블릿이나 JSP만으로 비즈니스 로직과 뷰 렌더링까지 모두 처리하게 되면, 너무 많은 역할을 하게되고, 결과적으로 유지보수가 어려워진다. (비즈니스 로직을 호출하는 부분에 변경이

joly156.tistory.com

[스프링 MVC 7] 서블릿: Hello 서블릿 (tistory.com)

 

[스프링 MVC 7] 서블릿: Hello 서블릿

스프링 부트 환경에서 서블릿을 등록하고 사용해보자. 스프링 부트 서블릿 환경 구성 @ServletComponentScan 스프링 부트가 서블릿을 직접 등록해서 사용할 수 있도록 해준다. @ServletComponentScan //서블

joly156.tistory.com

 

728x90
반응형