4. MVC 프레임워크 만들기

ys·2024년 1월 1일

Spring-mvc1

목록 보기
4/7

김영한 강사님의 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술을 듣고 정리한 내용입니다. 자세한 내용은 강의를 참고해주세요

이번강의에서는, MVC 패턴에서의 반복되는 코드와, 공통처리의 불편함을 해결하고자 MVC 프레임워크를 만들어본다. Spring MVC 프레임워크를 사용하면 되지 않냐? 라는 질문에 이렇게 하나하나 문제점을 해결해 보면서 Spring MVC와 유사한 프레임 워크를 만들어봄에 따라 코딩실력의 증가와 Spring MVC 컨테이너를 잘 이해하기 위함이다라고 답하며 시작해 보도록 하겠다.

  • 우리는 MVC 패턴의 Controller(각 페이지마다)의 코드의 중복을 하나의 공통 Controller(Front Controller)을 이용해서 반복을 처리해보겠다!!!

FrontController의 특징

  • 프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받음
  • 프론트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출해준다
  • 공통처리가 가능하다
  • 이렇게 되면 프론트 컨트롤러 제외의 컨트롤러들이 Servlet을 사용하지 않아도 된다!!!
  • 이제부터 v1,v2,v3,v4,v5 이렇게 5가지 버전의 MVC 프레임워크를 만들어서 문제점을 조금씩 해결에 나아가 보도록 하겠다

V1 - 프론트 컨트롤러 도입

  • 우리는 다형성을 적극 활용하기 위해서, 컨트롤러라는 인터페이스를 만들고 -> 이를 구현할 것이다
  • 저번시간에 만든 요청사항처럼 컨트롤러v1(인터페이스)를 가지고 회원 폼 컨트롤러, 회원 저장 컨트롤러, 회원List컨트롤러를 구현할 것이다
  • 먼저 인터페이스부터! -> 컨트롤러의 인터페이스를 만들고, 각 컨트롤러들은 이 인터페이스를 구현하면 된다!
  • 프론트 컨트롤러는 이 인터페이스를 의존! -> 구현과 관계없이 로직의 일관성 + DIP도 잘 지킬 수 있다!!!
public interface ControllerV1 {
    void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
  • 그리고 이를(인터페이스) 구현하는 컨트롤러 3개를 만들어준다
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 {
    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);

        //Model에 데이터를 보관한다
        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);
    }
}
  • 로직은 MVC 패턴과 비슷하지만, 서블릿을 구현하지 않은 특징이 있다
  • 이제는 Front Controller이다
@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(); // 요청받는 URI를 얻을 수 있다

        ControllerV1 controller = controllerMap.get(requestURI);
        if (controller == null){
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        controller.process(request,response);


    }
}
  • urlPatterns = "/front-controller/v1/*" : /front-controller/v1 를 포함한 하위 모든 요청 은 이 서블릿에서 받아들인다.
  • ControllerMap이라고 key값은 매핑 URL, value값은 호출될 컨트롤러를 넣고 우리가 구현한 컨트롤러를 생성자로 넣어준다
  • v1의 하위 디렉토리가 호출되면, FrontControllerServlet1이 실행되고, 요청받은 URL에 따라 컨트롤러가 지정된 JSP를 랜더링해준다

V2 - View의 분리

V1을 성공적으로 만들었다... 그런데 컨트롤러에서 뷰로 이동하고 랜더링 하는 부분의 코드가 중복된다

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

이부분을 해결하기 위해서 별도로 뷰를 처리하는 객체를 만들어서 해결해 보자!!!

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);
    }
 }
  • 이렇게 공통으로 처리하는 부분을 Myview로 클래스로 만든다
    이제 각 컨트롤러에서 dispatcher.forward()를 직접 호출하지 않고, MyView 객체를 생성하고 그 안에 ViewPath만 넣고 반환해주면 된다!!!
  • 이제 각 구현체들을 -> (컨트롤러)들을 봐보자
  • 마지막에 MyView객체를 반환한다. 생성자가 viewPath이므로, 랜더링 되는 부분의 경로를 넣어주는 것을 볼 수 있다.
public class MemberFormControllerV2 implements ControllerV2 {
    @Override
    public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        return new MyView("/WEB-INF/views/new-form.jsp");
    }
}
public class MemberSaveControllerV2 implements ControllerV2 {
    MemberRepository memberRepository = MemberRepository.getInstance();
    @Override
    public MyView 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);

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

        return new MyView("/WEB-INF/views/save-result.jsp");
    }
}
public class MemberListControllerV2 implements ControllerV2 {
    MemberRepository memberRepository = MemberRepository.getInstance();
    @Override
    public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        List<Member> members = memberRepository.findAll();
        request.setAttribute("members", members);
        return new MyView("/WEB-INF/views/members.jsp");
    }
}
  • 이제 FrontController을 봐보도록 하겠다
@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 {
        System.out.println("FrontControllerServletV2.service");

        String requestURI = request.getRequestURI(); // 요청받는 URI를 얻을 수 있다

        ControllerV2 controller = controllerMap.get(requestURI);
        if (controller == null){
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        MyView view = controller.process(request, response);
        view.render(request,response);

    }
}
  • 공통으로 Map에 들어간 주소에 따라서, 컨트롤러에 process 메서드와 렌더링을 Front Controller에서 공통으로 처리해주어서 편리하게 코드를 바꿨다.

V3 - Model 추가

  • 컨트롤러 입장에서 보면.. HttpServletRequet, HttpServletResponse가 꼭 필요할까?
  • 요청 파라미터 정보는 자바의 Map으로 대체!, 컨트롤러가 서블릿 기술을 몰라도 동작 가능
  • request 객체의 Model 부분도 별도의 Model 객체를 만들자
  • 뷰이름도 중복 제거하자!

모델 V3의 핵심은 컨트롤러에서 서블릿의 종속성을 없애자는 것이다. MVC패턴의 model부분도 request를 이용하지말고, Model 부분의 객체를 별도로 만들어보자! + 유지보수를 위해 view이름의 중복도 제거할 것이다.

  • 먼저 Model과 View를 모두 가지고 잇는 ModelView클래스를 만든다!
  • 이때 뷰의 이름과, 렌더링할 때 필요한 mdoel객체를 가지고 있다
  • model은 view에 필요한 데이터를 key,value로 넣어주면 된다.

MyView

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

    public void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        modelToRequestAttribute(model, request);
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request,response);
    }
    private static void modelToRequestAttribute(Map<String, Object> model, HttpServletRequest request) {
        model.forEach((key, value) -> request.setAttribute(key,value));
    }
}

ModelView

public class ModelView {
    private String ViewName; // view의 논리적인 이름
    private Map<String,Object> model = new HashMap<>();

    public ModelView(String viewName) {
        ViewName = viewName;
    }

    public String getViewName() {
        return ViewName;
    }

    public void setViewName(String viewName) {
        ViewName = viewName;
    }

    public Map<String, Object> getModel() {
        return model;
    }

    public void setModel(Map<String, Object> model) {
        this.model = model;
    }

다음은 Controller3(인터페이스)이고 특이하게 ModelView를 반환 받는 메서드를 가지고 있다

public interface ControllerV3 {
    ModelView process(Map<String,String> paramMap);
}
public class MemberFormControllerV3 implements ControllerV3 {
    @Override
    public ModelView process(Map<String, String> paramMap) {
        return new ModelView("new-form");
    }
}
public class MemberSaveControllerV3 implements ControllerV3 {
    private 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 mv = new ModelView("save-result");
        mv.getModel().put("member", member);
        return mv;


    }
}
public class MemberListControllerV3 implements ControllerV3 {
    private MemberRepository memberRepository = MemberRepository.getInstance();
    @Override
    public ModelView process(Map<String, String> paramMap) {
        List<Member> members = memberRepository.findAll();

        ModelView mv = new ModelView("members");
        mv.getModel().put("members", members);
        return mv;

    }

}
  • 모두 modelView를 반환한다
  • 이때 생성자로 url을 받기 때문에 논리 이름을 넣은 new ModelView를 반환한다
  • 그리고 렌더링할 모델을 put을 이용해 데이터를 넣어준다
  • 이제 Front Controller을 봐보겠다
@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 {
        System.out.println("FrontControllerServletV3.service");

        String requestURI = request.getRequestURI(); // 요청받는 URI를 얻을 수 있다

        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);
        String viewName = mv.getViewName();// 논리이름만 얻을 수 있다. ex) new-form

        /// WEB-INF/views/new-form.jsp
        MyView view = viewResolver(viewName);
        view.render(mv.getModel(),request,response);

    }

    private static MyView viewResolver(String viewName) {
        return new MyView("/WEB-INF/views/" + viewName + ".jsp");
    }

    private static Map<String, String> createParamMap(HttpServletRequest request) {
        // ParamMap을 넘겨줘야 한다!
        Map<String, String> paramMap = new HashMap<>();
        request.getParameterNames().asIterator()
                .forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));
        return paramMap;
    }
}
  • 먼저 request.getRequestUrl을 이용해, ControllerMap안에 있는 ControllerV3을 Controller에 받는다
  • 이때, 우리가 컨트롤러를 인터페이스를 구현하고, 인터페이스로 받았기 때문에, 다형성을 사용해 코드를 확장성 있게 짤 수 있다.
  • 그리고 CreateParamMap 메서드를 이용해, 메서드 안의 필드값인 paramMap에 요청 request에서 이터레이터를 이용해 값을 하나하나 저장한 paramMap을 반환해준다
  • 그리고 그 값을 paramMap이라고 밖에 지정해둔 필드값인 Map에 넣어준다 -> 이 값은 나중에 렌더링할 때 인자값으로 들어가게된다
  • 그다음 우리가 받은 논리 주소를 viewResolver메서드로 인해 다시 절대주소를 가진 MyView로 반환한다.
  • 그 후에 view에 render을 해주는데 이때 인자 값은 렌더링할 모델, request, response이다!!!
    -> 이부분은 확실한 이해가 필요하다!!!

V4 - 단순하고 실용적인 컨트롤러

앞서 만든 v3 컨트롤러는 서블릿 종속성을 제거하고 뷰 경로의 중복도 제거하고 잘 설계된 컨트롤러이다.. 그런데 좋은 프레임 워크는 아키텍쳐도 중요하지만 실제 개발하는 개발자가 단순하고 편리하게 이용할 수 있어야한다

그런데 V3은 컨트롤러 인터페이스를 구현하는 개발자 입장에서는 항상 ModelView객체를 생성하고 반환해야 하는 부분이 조금은 번거롭다

public interface ControllerV4 {
    String process(Map<String,String> paramMap,Map<String,Object> model);
}
  • 컨트롤러 V4에서는 String으로 주소를 반환 받고
  • model과 paramMap을 모두 FrontController에서 처리한다
  • 구현체들을 봐보자
    @Override
    public String process(Map<String, String> paramMap, Map<String, Object> model) {
        return "new-form"; // modelView 없이 그냥 문자열로 반환!
    }
public class MemberSaveControllerV4 implements ControllerV4 {
    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public String process(Map<String, String> paramMap, Map<String, Object> model) {
        String username = paramMap.get("username");
        int age = Integer.parseInt(paramMap.get("age"));

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

        model.put("member", member);
        return "save-result";
    }
}
public class MemberListControllerV4 implements ControllerV4 {
    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public String process(Map<String, String> paramMap, Map<String, Object> model) {
        List<Member> members = memberRepository.findAll();

        model.put("members", members);
        return "members";
    }

다음은 Front Controller 이다

@WebServlet(name = "frontControllerServletV4", urlPatterns = "/front-controller/v4/*")
public class FrontControllerServletV4 extends HttpServlet {
    private Map<String, ControllerV4> controllerMap = new HashMap<>();

    public FrontControllerServletV4() {
        controllerMap.put("/front-controller/v4/members/new-form", new MemberFormControllerV4());
        controllerMap.put("/front-controller/v4/members/save", new MemberSaveControllerV4());
        controllerMap.put("/front-controller/v4/members", new MemberListControllerV4());
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("FrontControllerServletV4.service");

        String requestURI = request.getRequestURI(); // 요청받는 URI를 얻을 수 있다

        ControllerV4 controller = controllerMap.get(requestURI);
        if (controller == null){
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        Map<String, String> paramMap = createParamMap(request);
        Map<String, Object> model = new HashMap<>(); // 추가 -> 컨트롤러에서 modelView 생성하던거 그냥 프론트 컨트롤러에서 빈거 만들고, 추가만 해준다
        String viewName = controller.process(paramMap, model);

        /// WEB-INF/views/new-form.jsp
        MyView view = viewResolver(viewName);
        view.render(model,request,response);

    }

    private static MyView viewResolver(String viewName) {
        return new MyView("/WEB-INF/views/" + viewName + ".jsp");
    }

    private static Map<String, String> createParamMap(HttpServletRequest request) {
        // ParamMap을 넘겨줘야 한다!
        Map<String, String> paramMap = new HashMap<>();
        request.getParameterNames().asIterator()
                .forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));
        return paramMap;
    }
}
  • Front Controller에서 paramMap과 model을 모두 생성해서 process 메서드에 넣어버린다!
  • 그리고 viewName을 반환받는다. 이를 viewResolver에 넣고 다시 절대 경로로 받은 후에
  • 거기에다가 render을 진행한다 이때는 오버로딩이 된 render에 넣어주고, process 메서드에서 렌더링이 될 데이터가 추가된 model과 request, response를 넣어준다!

V5 - 유연한 컨트롤러

  • 지금까지의 만든 MVC 프레임워크는... 다형성을 이용하기 위해서 프론트 컨트롤러에 한가지 방식의 컨트롤러 인터페이스만 넣어줄 수 있었다.
  • 즉 ControllerV3,ControllerV4와 같이 다른 인터페이스는 호환이 불가능하다!
    이런 문제를 해결하기 위해서 어댑터의 개념이 나왔다
  • 어뎁터 패턴을 사용해서 프론트 컨트롤러가 다양한 방식의 컨트롤러를 처리할 수 있도록 변경해보자

  • 핸들러 : 컨트롤러의 이름이 더 넓은 범위인 핸들러로 변경되었다. 어댑터가 잇기 때문에 컨트롤러 개념 뿐만 아니라 어떠한 것이든 해당하는 종류의 어댑터만 있으면 처리가 가능하기 때문이다
  • 핸들러 어댑터 : 중간에 어댑터 역할을 한다. 어댑터 역할을 해주는 덕분에 다양한 종류의 컨트롤러를 Front Controller에서 호출할 수 있다

먼저 어뎁터용 인터페이스이다 인터페이스로 만들어서 V3,V4의 구현한 어뎁터를 이용해 V5에서 V3,V4방식을 사용할 수 있도록 해보겠다

public interface MyHandlerAdapter {
    boolean support(Object handler); // handler가 넘어왔을 때, 이 컨트롤러(핸들러) 지원할 수 있는지
    ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException;
}
  • support 메서드를 통해, 인자로 온 handler(컨트롤러)가 지원하는 핸들러인지 확인하는 메서드
  • handle 메서드를 통해, 실제 컨트롤러가 ModelView를 반환하지 못하면 어댑터가 ModelView를 직접 생성해서 반환하게 코드를 짠다
  • 다음은 V3,V4 컨트롤러를 지원하기 위한 어뎁터이다
  • 요청 파라미터에서 파라미터를 이터레이터로 저장해 넣어주고 ModelView를 반환해준다
  • V4는 String으로 받지만, 그 문자열을 이용해 ModelView객체를 생성하고 파라미터도 넣어서 반환해준다
public class ControllerV3HandlerAdapter implements MyHandlerAdapter {
    @Override
    public boolean support(Object handler) {
        return handler instanceof ControllerV3;
    }

    @Override
    public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
        ControllerV3 controllerV3 = (ControllerV3) handler;

        Map<String, String> paramMap = createParamMap(request);
        ModelView mv = controllerV3.process(paramMap);
        return mv;
    }

    private static Map<String, String> createParamMap(HttpServletRequest request) {
        // ParamMap을 넘겨줘야 한다!
        Map<String, String> paramMap = new HashMap<>();
        request.getParameterNames().asIterator()
                .forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));
        return paramMap;
    }
public class ControllerV4HandlerAdapter implements MyHandlerAdapter {
    @Override
    public boolean support(Object handler) {
        return (handler instanceof ControllerV4);
    }

    @Override
    public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
        ControllerV4 controller = (ControllerV4) handler;

        Map<String, String> paramMap = createParamMap(request);
        HashMap<String, Object> model = new HashMap<>();

        String viewName = controller.process(paramMap, model);

        ModelView mv = new ModelView(viewName);
        mv.setModel(model);
        return mv;
    }
    private static Map<String, String> createParamMap(HttpServletRequest request) {
        // ParamMap을 넘겨줘야 한다!
        Map<String, String> paramMap = new HashMap<>();
        request.getParameterNames().asIterator()
                .forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));
        return paramMap;
    }
}
  • 그다음은 Front Controller이다
@WebServlet(name = "frontControllerServletV5", urlPatterns = "/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {

    private final Map<String, Object> handlerMappingMap = new HashMap<>();
    private final List<MyHandlerAdapter> handlerAdapters = new ArrayList<>();

    public FrontControllerServletV5() {
        initHandlerMappingMap();
        initHandlerAdapters();
    }

    private void initHandlerMappingMap() {
        handlerMappingMap.put("/front-controller/v5/v3/members/new-form", new MemberFormControllerV3());
        handlerMappingMap.put("/front-controller/v5/v3/members/save", new MemberSaveControllerV3());
        handlerMappingMap.put("/front-controller/v5/v3/members", new MemberListControllerV3());
        // v4 추가
        handlerMappingMap.put("/front-controller/v5/v4/members/new-form", new MemberFormControllerV4());
        handlerMappingMap.put("/front-controller/v5/v4/members/save", new MemberSaveControllerV4());
        handlerMappingMap.put("/front-controller/v5/v4/members", new MemberListControllerV4());
    }
    private void initHandlerAdapters() {
        handlerAdapters.add(new ControllerV3HandlerAdapter());
        handlerAdapters.add(new ControllerV4HandlerAdapter());
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        Object handler = getHandler(request);

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

        MyHandlerAdapter adapter = getHandlerAdapter(handler);

        ModelView mv = adapter.handle(request, response, handler);

        String viewName = mv.getViewName();// 논리이름만 얻을 수 있다. ex) new-form

        /// WEB-INF/views/new-form.jsp
        MyView view = viewResolver(viewName);
        view.render(mv.getModel(),request,response);
    }
    private Object getHandler(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        return handlerMappingMap.get(requestURI);
    }

    private MyHandlerAdapter getHandlerAdapter(Object handler) {
        MyHandlerAdapter a;
        for (MyHandlerAdapter adapter : handlerAdapters) {
            if(adapter.support(handler)){
                return adapter;
            }
        }
        throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다. handler=" + handler);
    }

    private static MyView viewResolver(String viewName) {
        return new MyView("/WEB-INF/views/" + viewName + ".jsp");
    }

}
  • 먼저 핸들러 매핑 정보를 저장할 handlerMappingMap과 핸들러 어댑터 목록을 저장할 handlerAdapters의 리스트를 만들어준다. 그리고 이것들을 생성자에 잘 넣어준다

  • getHandleAdapter 메서드를 이용해 안에서 for문과 support 메서드로 핸들러 어뎁터 목록에서 어뎁터를 찾는다!!!

  • 찾은 어뎁터를 이용해 handle메서드를 이용해 ModelView를 받는다

  • viewResolver메서드를 이용해, 절대 경로를 반환받고 이를 렌더링한다

  • 이전에는 프론트 컨트롤러가 실제 컨트롤러를 호출했지만, 어뎁터가 컨트롤러를 호출해(handle메서드를 통해), ModelView를 받는다

    • 어댑터를 사용하기 때문에, 컨트롤러 뿐만 아니라 어댑터가 지원하기만 하면 어떤 것이라도 URL에 매핑해서 사용할 수 있다. 그래서 이름을 컨트롤러에서 더 넓은 범위의 핸들러로 변경한다

    • ControllerV3는 ModelView를 반환해서 상관 없지만 ControllerV4는 뷰의 이름을 String으로 반환한다
  • 그런데 어댑터는 뷰의 이름이 아니라 ModelView를 반환해야 한다 -> 어뎁터가 필요한 이유

  • 서로 다른 컨트롤러를 지원할때, 형식을 맞추어서 반환하게 된다

  • V4에서도 반환받은 String 즉 이름을 가지고 ModelView객체를 새로 만들고 거기 안에 setter을 이용해 파라미터 model정보를 넣어준 후에 반환한다

  • V3은 어뎁터와 반환 로직인 ModelView가 비슷해서 단순하지만 V4에서 왜 어뎁터가 필요한지 반환을 맞춰주는 어뎁터의 진가가 나온다!!!

profile
개발 공부,정리

0개의 댓글