Spring/SpringMVC

유연한 컨트롤러1 - v5

느리지만 꾸준하게 2022. 4. 25. 01:45

이제 어댑터 패턴을 써보자

 

  • V3 V4에서 개발한 프론트 컨트롤러는 한가지 방식의 컨트롤러 인터페이스만 계속해서 사용했다.

 

  • `ControllerV3`, `ControllerV4`는 완전히 다른 인터페이스이고 호환이 불가능하다.

 

  • 어댑터 패턴을 사용해서 프론트 컨트롤러가 다양한 방식의 컨트롤러를 처리하도록 변경하자.

 

V5 구조를 만들어서 보자.

 

중간에 핸들러 어댑터를 두자. 그러면 FrontController가 직접 Controller를 호출하는 것이 아니고 어댑터를 통해서 호출을 한다.

FrontController가 어댑터한테 handler 파라미터를 넘겨서

어댑터가 컨트롤러한테 다른 걸 호출해준다.

 

그러면 핸들러 어댑터가 대신 호출해주고 핸들러한테 결과 받고 FrontController한테 ModelView를 반환한다.

핸들러 어댑터

  • 중간에 어댑터 역할을 하는 어탭터가 추가되었고 이게 핸들러 어댑터이다.

 

  • 이거 덕택에 다양한 종류의 컨트롤러를 호출할 수 있게 된다.

 

 

 

 

핸들러

  • 컨트롤러의 이름을 넓은 범위인 핸들러로 변경했는데

 

  • 이유는   어댑터가 있기 때문에 컨트롤러의 개념 뿐 아니라 어떤 것이든 해당하는 종류의 어댑터만 있으면 처리를 할 수 있기 때문이다.

 

 

 

MyHandlerAdapter를 만들어주자.

// MyHandlerAdapter

package hello.setvlet.web.frontcontroller.v5;

import hello.setvlet.web.frontcontroller.ModelView;

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

public interface MyHandlerAdapter {

    boolean supports(Object handler);

    ModelView handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}

 

 

 

FrontControllerV3에 가서 코드를 복사해서 와주고 ControllerV3HandlerAdapter에다 넣어주자.

private Map<String, String> createParamMap(HttpServletRequest request) {
    Map<String, String> paramMap = new HashMap<>();
    request.getParameterNames().asIterator()
            .forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));
    return paramMap;
}

 

 

 

 

 

ControllerV3HandlerAdapter는 아래와 같다.

// ControllerV3HandlerAdapter

package hello.setvlet.web.frontcontroller.v5.adapter;

import hello.setvlet.web.frontcontroller.ModelView;
import hello.setvlet.web.frontcontroller.v3.ControllerV3;
import hello.setvlet.web.frontcontroller.v5.MyHandlerAdapter;

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

public class ControllerV3HandlerAdapter implements MyHandlerAdapter {
    @Override
    public boolean supports(Object handler) {
        return (handler instanceof ControllerV3);
    }

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

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

        return mv;
    }

    private Map<String, String> createParamMap(HttpServletRequest request) {
        Map<String, String> paramMap = new HashMap<>();
        request.getParameterNames().asIterator()
                .forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));
        return paramMap;
    }


}

 

 

 

 

 

FrontControllerServletV5를 만들어 주고

이때도 initHandlerMappingMap과 ininHandlerAdapters 두개를 따로 command + option + m으로 뽑아서 명칭을 지정해준다.

package hello.setvlet.web.frontcontroller.v5;

import hello.setvlet.web.frontcontroller.v3.controller.MemberFormControllerV3;
import hello.setvlet.web.frontcontroller.v3.controller.MemberListControllerV3;
import hello.setvlet.web.frontcontroller.v3.controller.MemberSaveControllerV3;
import hello.setvlet.web.frontcontroller.v5.adapter.ControllerV3HandlerAdapter;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@WebServlet(name = "frontControllerServlet", 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() {
        // v3먼저 지원하는 걸 만든다.
        initHandlerMappingMap();
        ininHandlerAdapters();
    }

    private void initHandlerMappingMap() {
        handlerMappingMap.put("/front-controller/v3/members/new-form", new MemberFormControllerV3());
        handlerMappingMap.put("/front-controller/v3/members/save", new MemberSaveControllerV3());
        handlerMappingMap.put("/front-controller/v3/members", new MemberListControllerV3());
    }

    private void ininHandlerAdapters() {
        handlerAdapters.add(new ControllerV3HandlerAdapter());
    }

}

 

 

 

 

 

service 부분도 protected로 만들어 주는데

 

FrontControllerServletV3에서 가져와서 아래 부분을 가져와서

// FrontControllerServletV3

String requestURI = request.getRequestURI();

    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();// 논리이름 new-form
    MyView view = viewResolver(viewName);

    view.render(mv.getModel(), request, response);
}

 

 

 

FrontControllerServletV5에 붙여준다. 이렇게 일단 작성을 해주고 그림을 보게되면

// FrontControllerServletV5

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

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

        String viewName = mv.getViewName();// 논리이름 new-form
        MyView view = viewResolver(viewName);

        view.render(mv.getModel(), request, response);
    }

    private Object getHandler(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        return handlerMappingMap.get(requestURI);
    }

 

 

 

 

http 요청이 오면 핸들러를 조회하고 이제 핸들러 어댑터 목록에서 핸들러를 처리할 수 있는 걸 만들어보자(v3인지 v4인지 )

 

 

 

 

FrontControllerServletV5 Class에서 반복문을 돌아서 V4인지 V5 어댑터를 쓰는지 확인하는 코드를 작성해준다.

package hello.setvlet.web.frontcontroller.v5;

import hello.setvlet.web.frontcontroller.ModelView;
import hello.setvlet.web.frontcontroller.MyView;
import hello.setvlet.web.frontcontroller.v3.ControllerV3;
import hello.setvlet.web.frontcontroller.v3.controller.MemberFormControllerV3;
import hello.setvlet.web.frontcontroller.v3.controller.MemberListControllerV3;
import hello.setvlet.web.frontcontroller.v3.controller.MemberSaveControllerV3;
import hello.setvlet.web.frontcontroller.v5.adapter.ControllerV3HandlerAdapter;

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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@WebServlet(name = "frontControllerServlet", 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() {
        // v3먼저 지원하는 걸 만든다.
        initHandlerMappingMap();
        ininHandlerAdapters();
    }

    private void initHandlerMappingMap() {
        handlerMappingMap.put("/front-controller/v3/members/new-form", new MemberFormControllerV3());
        handlerMappingMap.put("/front-controller/v3/members/save", new MemberSaveControllerV3());
        handlerMappingMap.put("/front-controller/v3/members", new MemberListControllerV3());
    }

    private void ininHandlerAdapters() {
        handlerAdapters.add(new ControllerV3HandlerAdapter());
    }

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


        // getHandlerAdapter로 refactoring
        MyHandlerAdapter adapter = getHandlerAdapter(handler);

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

        String viewName = mv.getViewName();// 논리이름 new-form
        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) {
        // v3가 지원하냐 그러면 어댑터를 반환하고 없으면 다시 반복문 돌아서 v4로 확인하고 v4 어댑터 반환해주고 아무거도 없으면 예외처리
        for (MyHandlerAdapter adapter : handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
        // 예외처리
        throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다. handler=" + handler);
    }
}

 

 

 

 

자 이제 아래와 같이 FrontControllerServletV5를 완벽하게 작성을 했다.

package hello.setvlet.web.frontcontroller.v5;

import hello.setvlet.web.frontcontroller.ModelView;
import hello.setvlet.web.frontcontroller.MyView;
import hello.setvlet.web.frontcontroller.v3.ControllerV3;
import hello.setvlet.web.frontcontroller.v3.controller.MemberFormControllerV3;
import hello.setvlet.web.frontcontroller.v3.controller.MemberListControllerV3;
import hello.setvlet.web.frontcontroller.v3.controller.MemberSaveControllerV3;
import hello.setvlet.web.frontcontroller.v5.adapter.ControllerV3HandlerAdapter;

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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@WebServlet(name = "frontControllerServlet", 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() {
        // v3먼저 지원하는 걸 만든다.
        initHandlerMappingMap();
        ininHandlerAdapters();
    }

    private void initHandlerMappingMap() {
        handlerMappingMap.put("/front-controller/v3/members/new-form", new MemberFormControllerV3());
        handlerMappingMap.put("/front-controller/v3/members/save", new MemberSaveControllerV3());
        handlerMappingMap.put("/front-controller/v3/members", new MemberListControllerV3());
    }

    private void ininHandlerAdapters() {
        handlerAdapters.add(new ControllerV3HandlerAdapter());
    }

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


        // 1. 핸들러 호출하고
        Object handler = getHandler(request);
        if (handler == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }


        // 2 핸들러 어댑터를 가져오고
        MyHandlerAdapter adapter = getHandlerAdapter(handler);


        // 핸들러를 호출한다.
        ModelView mv = adapter.handle(request, response, handler);


        // 뷰네임 가져와서 뷰 리졸브 호출해주고
        String viewName = mv.getViewName();// 논리이름 new-form

        // 뷰를 얻은 다음에 뷰를 렌더 호출한다.
        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) {
        for (MyHandlerAdapter adapter : handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
        throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다. handler=" + handler);
    }

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

 

 

 

 

에러가 뜨는데  => ( applicationProperties의) 아래 코드 덕분에 디버그 내용을 볼 수 있다.

logging.level.org.apache.coyote.http11=debug

 

 

 

콘솔 로그는 이렇게 나온다.

Received [GET /front-controller/v5/v3/members/new-form HTTP/1.1
Host: localhost:8080

 

 

 

v3/~ => v5/v3/~로 바꿔준다.

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

 

 

 

잘 나온다. 대박이다. 컨트롤러를 유연하게 짜는 이부분도 SpringMVC로 들어가기 전에 다시한번 학습해보자.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

<출처 김영한: 스프링 MVC 1편 - 벡앤드 웹 개발 핵심 기술>

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard

 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., -

www.inflearn.com

 

'Spring > SpringMVC' 카테고리의 다른 글

스프링 MVC 전체 구조  (0) 2022.04.25
유연한 컨트롤러2 - v5  (0) 2022.04.25
단순하고 실용적인 컨트롤러 - v4  (0) 2022.04.25
Model 추가 - v3  (0) 2022.04.24
View 분리 - v2  (0) 2022.04.24