유연한 컨트롤러2 -v5

shinyeongwoon·2022년 12월 26일
0

spring MVC

목록 보기
18/32
post-custom-banner

유연한 컨트롤러2 -v5

FrontContollerServletV5ControllerV4기능도 추가해보자.

package hello.servlet1.web.v5;

import hello.servlet1.web.frontcontroller.ModelView;
import hello.servlet1.web.frontcontroller.MyView;
import hello.servlet1.web.frontcontroller.v3.controller.MemberFormControllerV3;
import hello.servlet1.web.frontcontroller.v3.controller.MemberListControllerV3;
import hello.servlet1.web.frontcontroller.v3.controller.MemberSaveControllerV3;
import hello.servlet1.web.frontcontroller.v4.controller.MemberFormControllerV4;
import hello.servlet1.web.frontcontroller.v4.controller.MemberListControllerV4;
import hello.servlet1.web.frontcontroller.v4.controller.MemberSaveControllerV4;
import hello.servlet1.web.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 = "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());

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

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

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

}

핸들러 매핑(handlerMappingMap)에 ControllerV4를 사용하는 컨트롤러를 추가하고, 해당 컨트롤러를 처리할 수 있는 어댑터인 ContollerV4HandlerAdapter

ControllerV4HandlerAdapter

경로 : hello.servlet.web.frontcontroller.v5.adapter

package hello.servlet1.web.v5.adapter;

import hello.servlet1.web.frontcontroller.ModelView;
import hello.servlet1.web.frontcontroller.v4.ControllerV4;
import hello.servlet1.web.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 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);
        Map<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;
    }

}

분석해보기

public boolean supoorts(Object handler){
	return (handler instanceof ControllerV4);
}

handlerControllerV4인 경우에만 처리하는 어댑터이다.

실행 로직

ContollerV4 controller = (ControllerV4) handler;

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

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

handler를 ContollerV4로 케스팅 하고, paramMap,model을 만들어서 해당 컨트롤러를 호출한다. 그리고 viewName을 반환 받는다.

어댑터 변환

ModelView mv = new ModelView(viewName);
mv.setModel(model);
return mv;

어댑터에서 이 부분이 단순하지만 중요한 부분이다.

어댑터가 호출하는 ControllerV4는 뷰의 이름을 반환한다.
그런데 어댑터는 뷰의 이름이 아니라 ModelView를 만들어서 반환해야 한다.
여기서 어댑터가 꼭 필요한 이유가 나온다.
ContollerV4는 뷰의 이름을 반환했지만, 어댑터는 이것을 ModelView로 만들어서 형식을 맞추어 반환한다. 마치 110v 전기 콘센트를 220v 전기 콘센트로 변경 하듯이

어댑터와 ContollerV4

public interface ControllerV4{
	String process(Map<String,String> paramMap, Map<String,Object> mode);
}

public interface MyHandlerAdapter{
	ModelView handle(HttpServletRequest request,HttpServletResponse response, Object handler) throws ServletException, IOException;
}

실행)
등록 : http://localhost:8080/front-controller/v5/v4/members/new-form
목록 : http://localhost:8080/front-contoller/v5/v4/members

정리

지금까지 v1 ~ v5로 점진적으로 프레임워크를 발전시켜 왔다. 지금까지 한 작업을 정리해보자.
v1: 프론트 컨트롤러를 도입
기존 구조를 최대한 유지하면서 프론트 컨트롤러를 도입
v2: View 분류
단순 반복 되는 뷰 로직 분리
v3: Model 추가 서블릿 종속성 제거
뷰 이름 중복 제거
v4: 단순하고 실용적인 컨트롤러 v3와 거의 비슷
구현 입장에서 ModelView를 직접 생성해서 반환하지 않도록 편리한 인터페이스 제공
v5: 유연한 컨트롤러 어댑터 도입
어댑터를 추가해서 프레임워크를 유연하고 확장성 있게 설계
여기에 애노테이션을 사용해서 컨트롤러를 더 편리하게 발전시킬 수도 있다. 만약 애노테이션을 사용해서 컨트롤러를 편리하게 사용할 수 있게 하려면 어떻게 해야할까? 바로 애노테이션을 지원하는 어댑터를 추가하면 된다!
다형성과 어댑터 덕분에 기존 구조를 유지하면서, 프레임워크의 기능을 확장할 수 있다.
스프링 MVC
여기서 더 발전시키면 좋겠지만, 스프링 MVC의 핵심 구조를 파악하는데 필요한 부분은 모두 만들어보았다. 사실은 여러분이 지금까지 작성한 코드는 스프링 MVC 프레임워크의 핵심 코드의 축약 버전이고, 구조도 거의 같다.
스프링 MVC는 지금까지 우리가 학습한 내용과 거의 같은 구조를 가지고 있다.

post-custom-banner

0개의 댓글