자바 백엔드 웹 기술의 진화과정(3) - 웹 프론트 컨트롤러 v4, v5

2022. 6. 13. 14:40·Back-End/Spring MVC

자바 백엔드 웹 기술의 진화과정(3) - 웹 프론트 컨트롤러 v4, v5

프론트 컨트롤러 특징

  • 프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받음
  • 요청에 맞는 컨트롤러를 찾아서 호출
  • 프론트 컨트롤러를 제외한 나머지 컨트롤러는 서블릿을 사용하지 않아도 된다.
  • 단계적으로 도입
    • v1 : 프론트 컨트롤러 패턴의 도입
    • v2 : view render를 처리해주는 ****MyView 도입
    • v3 : 서블릿 종속성 제거 및 뷰의 물리경로를 가지는 뷰 리졸버의 사용
    • v4 : v3코드에서 컨트롤러가 직접 뷰의 논리 이름을 반환하도록 변경
    • v5 : 어댑터(Adapter) 패턴의 도입 → 다양한 종류의 컨트롤러 처리

V4 - 컨트롤러의 ViewName반환

  • 기존 V3구조에서 Controller가 ModelView객체가 아닌 String인 viewName을 반환하도록 변경
    • 매번 ModelView객체를 return하지 않아서 편리해짐
    • ModelView를 사용하지 않으므로 model정보를 저장하는 Map<String, Object> model을 받음

ControllerV4 인터페이스

public interface ControllerV4 {

    /**
     * @param paramMap
     * @param model
     * @return viewName
     */
    String process(Map<String, String> paramMap, Map<String, Object> model);
}

MemberFormControllerV4

public class MemberFormControllerV4 implements ControllerV4 {
    @Override
    public String process(Map<String, String> paramMap, Map<String, Object> model) {
        return "new-form";  //단순하게 new-form이라는 논리 이름만 반환
    }
}

MemberListControllerV4

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);  //Map에 members를 저장
        return "members";               //String인 members를 반환
    }
}

FrontControllerServletV4

@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 {
        String requestURI = request.getRequestURI();

        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<>(); //추가된 부분

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

        MyView view = viewResolver(viewName);

        view.render(model, request, response);
    }

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

    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;
    }
}
  • Map<String, Object> model = new HashMap<>() : 모델객체를 프론트 컨트롤러에서 생성해서 넘겨준다. 컨트롤러가 여기에 값을 담게 된다.

V5 - 핸들러 어댑터의 추가

  • 여러가지 인터페이스를 사용하기위해 어댑터(Adapter)의 사용
  • 다른 인터페이스인 ControllerV3, ControllerV4를 같이 사용 할 수 있다!

MyHandlerAdapter - 어탭터용 인터페이스

public interface MyHandlerAdapter {

    boolean supports(Object handler);
    ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler);
}
  • boolean supports(Object handler) : 어댑터에서 해당 컨트롤러를 처리 할 수 있는지 판단해주는 메서드
  • ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) : 어댑터에서 실제 컨트롤러의 로직을 호출 해주는 메서드

FrontControllerServletV5

@WebServlet(name = "frontControllerServletV5", urlPatterns = "/front-controller/v5/*")
public class FrontControllerServletV5 extends HttpServlet {
    //맵에서 value값이 Controller에서 Object로 변경 되었다.
    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());

        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);
        MyView view = viewResolver(mv.getViewName());
        view.render(mv.getModel(), request, response);
    }

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

    //Adapter를 가져올때는 supports 메서드를 이용해서 메서드를 찾는다.
    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");
    }
}
  • 매핑의 값이 Controller에서 Object로 변경되었다.

ControllerV3HandlerAdapter

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) {
        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;
    }
}
  • supports() : handler의 클래스가 ControllerV3인지 판단해줌 → handler가 V3일 경우에만 처리
  • ModelView() : handler를 V3로 변환한 후 V3인터페이스에 맞게 호출

ControllerV4HandlerAdapter

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

    @Override
    public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        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 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;
    }
}
  • supports() : handler의 클래스가 ControllerV4인지 판단해줌 → handler가 V4일 경우에만 처리
  • ModelView() : handler를 ControllerV4로 캐스팅 후, paramMap과 model을 만들어서 호출
    • ControllerV4는 원래 ViewName을 반환했지만, 어댑터는 ModelView로 만들어서 반환한다.
  • ModelView mv = new ModelView(viewName); //어댑터 변환 mv.setModel(model); return mv;
반응형
'Back-End/Spring MVC' 카테고리의 다른 글
  • 스프링 MVC의 구조 이해(2) - 실무에서 사용하는 방식 @RequestMapping, 스프링 MVC 컨트롤러
  • 스프링 MVC의 구조 이해(1) - DispatcherServlet, 핸들러 매핑과 핸들러 어댑터, 뷰 리졸버
  • 자바 백엔드 웹 기술의 진화과정(2) - 웹 프론트 컨트롤러 v1, v2, v3
  • 자바 백엔드 웹 기술의 진화과정(1), 서블릿, jsp, MVC 패턴
LightSource
LightSource
어제보단 발전한 오늘의 나를 위한 블로그
    반응형
  • LightSource
    LightSourceCoder
    LightSource
  • 전체
    오늘
    어제
    • 분류 전체보기 (152)
      • Git (4)
      • Language (6)
        • Java (6)
      • Back-End (63)
        • Spring Boot (4)
        • MyBatis (1)
        • Oracle (1)
        • PL SQL (3)
        • JPA (26)
        • Spring Data JPA (5)
        • Spring MVC (8)
        • Spring (12)
        • Spring Security (2)
        • Redis (1)
      • Front-End (38)
        • 아이오닉 (2)
        • JSP (7)
        • JavaScript (4)
        • React (16)
        • TypeScript (3)
        • Angular (6)
      • AWS (1)
      • CI & CD (1)
      • 개발지식 (13)
        • 네트워크 (9)
        • CS 지식 (4)
      • 데이터모델링 (2)
      • Tool (1)
      • 프로젝트 (5)
      • 독후감 (2)
      • 잡생각 (0)
      • 면접 준비 (1)
      • 알고리즘 (14)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    리액트
    배열요소삭제
    배요소열추가
    react
    배열요소수정
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
LightSource
자바 백엔드 웹 기술의 진화과정(3) - 웹 프론트 컨트롤러 v4, v5
상단으로

티스토리툴바