Spring/mvc

[Spring MVC 1] - MVC 프레임워크 - 프론트 컨트롤러 도입

dev_rosieposie 2022. 8. 16. 16:32

프론트 컨트롤러 패턴 소개 

공통의 관심사를 별도로 모으는 소위 문지기 역할을 하는 것을 controller앞에 도입한다.

 

 

프론트 컨트롤러 패턴 특징

  • 프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받음
  • 프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출
  • 입구를 하나로
  • 공통 처리 가능
  • 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 됨.

 

스프링 웹 MVC와 프론트 컨트롤러

스프링 웹MVC의 핵심도 바로 프론트컨트롤러이며 스프링 웹MVC의 DispatcherServlet이 프론트 컨트롤러 패턴으로 구현되어 있음

 

 

프론트 컨트롤러 도입 전 과 후의 아키텍쳐

왼쪽 : 프론트 컨트롤러 도입 전, 오른쪽 : 프론트 컨트롤러 도입 후

Goal  : 기존 코드를 최대한 유지하면서, 프론트 컨트롤러를 도입해본다. 먼저 구조를 맞추어두고 점진적으로 리팩토링을 한다.

 

프론트 컨트롤러 도입 v1

v1 구조 아키텍쳐

 

Controller V1

 

hello/servlet/web/frontcontroller/v1/ControllerV1.java

package hello.servlet.web.frontcontroller.v1;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public interface ControllerV1 {

    void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
  • 서블릿과 비슷한 모양의 컨트롤러 인터페이스 도입
  • 각 컨트롤러는 이 인터페이스를 구현하면 됨
  • 프론트 컨트롤러는 이 인터페이스를 호출하여 구현과 관계없이 로직의 일관성을 가져갈 수 있음

 

MemberFormControllerV1 - 회원 등록 컨트롤러

 

hello/servlet/web/frontcontroller/v1/controller/MemberFormControllerV1.java

package hello.servlet.web.frontcontroller.v1.controller;

import hello.servlet.web.frontcontroller.v1.ControllerV1;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

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

    }
}

 

MemberSaveControllerV1 - 회원 저장 컨트롤러

 

hello/servlet/web/frontcontroller/v1/controller/MemberSaveControllerV1.java

package hello.servlet.web.frontcontroller.v1.controller;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.v1.ControllerV1;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

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"));   // request의 getParameter 결과는 항상 문자! 그러므로 타입을 변환해 주어야 한다

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

        //Model에 데이터를 보관한다.
        request.setAttribute("member", member);

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

 

MemberListControllerV1 - 회원 목록 조회 컨트롤러

 

hello/servlet/web/frontcontroller/v1/controller/MemberListControllerV1.java

package hello.servlet.web.frontcontroller.v1.controller;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.frontcontroller.v1.ControllerV1;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

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

 

  • 내부 로직은 기존 서블릿과 동일하다.
  • 뷰도 동일하게 사용한다.

FrontController - 프론트 컨트롤러

 

hello/servlet/web/frontController/v1/FrontControllerServletV1.java

package hello.servlet.web.frontcontroller.v1;

import hello.servlet.web.frontcontroller.v1.controller.MemberFormControllerV1;
import hello.servlet.web.frontcontroller.v1.controller.MemberListControllerV1;
import hello.servlet.web.frontcontroller.v1.controller.MemberSaveControllerV1;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name="frontControllerServletV1", urlPatterns = "/front-controller/v1/*") //v1 하위의 어떤 url이든 이 서블릿이 호출됨
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 controller = new MemberFormControllerV1();
        ControllerV1 controller = controllerMap.get(requestURI);

        if(controller == null){
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        //다형성으로 인해 override된 메서드가 호출이 된다
        controller.process(request,response);

    }
}
  • urlPatterns
    • urlPatterns="/front-controller/v1/*" : /front-controller/v1 를 포함한 하위 모든 요청은 이 서블릿에서 받아들인다
  • controllerMap
    • key:매핑 URL
    • value: 호출될 컨트롤러
  • service()
    • 먼저 requestURL를 조회해서 실제 호출할 컨트롤러를 controllerMap에서 찾고, 없다면 404를 반환한다.
    • 컨트롤러를 찾고 controller.process(request,response);을 호출해서 해당 컨트롤러를 실행한다
  • JSP
    • JSP는 이전 MVC에서 사용했던 것을 재사용한다.

http://localhost:8080/front-controller/v1/members/new-form 등록 실행 결과

 

http://localhost:8080/front-controller/v1/members 목록 조회 실행 결과

http://localhost:8080/front-controller/v1/aa 404

 

 

 

 


코드의 의미는 알겠지만 oop의 특징인 다형성을 더욱 이해해야겠다는 생각이 든다! 하면 할 수록 할 건 더 많아지는데, 빨리 더 알고 싶다. 그러나 지치지 않도록, 꾸준히 근성으로!

 

 

@ 스프링 MVC 1편 - 백엔드 웹개발 by 김영한을 참조하고 있습니다.