월루를 꿈꾸는 대학생

[MVC-1] MVC 프레임 워크 만들기 본문

Programing/Spring Boot

[MVC-1] MVC 프레임 워크 만들기

하즈시 2023. 8. 1. 22:07
728x90

프론트 컨트롤러 패턴 소개

일단 지금은 손수 다 만들고.... 나중에 스프링 mvc패턴까지 가서 mvc패턴을 깊게 이해해보자

  • 공통 로직을 각각 처리 아무거나 다 들어올 수 있으니까 컨트롤러마다 공통로직을 도입했어야했다 = 중복

프론트 컨트롤러 도입 후

특징

  • 프론트 컨트롤러 서블릿 하나로 클라언트 요청을 받음 (수문장)
  • 이 프론트 컨트롤러가 알아서 요청에 맞는 컨트롤러로 토스함
  • 입구를 하나로 만들어서 공통 처리를 중복되지 않도록 하여 유지보수가 쉽도록
  • 프론트 컨트롤러 제외한 나머지 컨트롤러는 서블릿 필요없음
    • 서블릿이라는 것은 http요청이 들어왔을 때 was서버가 req,resp 객체를 만들어서 서블릿 컨테이너에 등록된 서블릿에다가 던져 준 다음 서블릿이 그 요청에 맞는 로직을 실행하는 것이었음 근데 이 역할을 프론트 컨트롤러가 앞에서 다 해버리니까 나머지 뒷단 컨트롤러들은 굳이 서블릿을 만들지 않아도 알아서 호출 당하니까 굳굳
    • 다른 컨터이너에 어노테이션이나 리퀘스트 , 리스폰스 같은 거 안써도 실행가능 !

스프링 웹 mvc 패턴의 핵심도 FrontController
스프링 웹 mvc의 DispatcherServlet도 FrontController 패턴으로 되어 있다

그래서 우리는 이 frontcontroller가 어떤 것인지 또 어떻게 발전하는지 알아두면 mvc를 잘 이해할 수 있을 것!


프론트 컨트롤러 도입 - v1

  • 요청이 오면 프론트 컨트롤러가 받은 후에 요청에 따른 매핑 정보를 컨트롤러 조회함 그 후 해당하는 컨트롤러를 호출하고 컨트롤러는 로직 수행후 forward로 jsp 뷰를 응답

컨트롤러는 인터페이스로 만들것임 == 다형성

public interface ControllerV1 {  

    // 해당 인터페이스를 구현한 컨트롤러들을 만들고 로직의 일관성을 유지 = 다형성   
void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;  
}

위의 인터페이스를 구현한 컨트롤러들

  • 어노테이션 없음 = 서블릿 컨테이너에 들어가지 않는다

  • 내부로직은 전에 했던거와 거의 동일

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

프론트 컨트롤러

  • 매핑정보 = controllerMap
  • url요청이 오는 경우 (/v1/* ) 해당 하위 요청까지 일단 오면 이 프론트 컨트롤러가 실행이 됨

    프론트 컨트롤러는 생성 시 매핑 정보를 만들게 되고 http요청이 오면 service가 콜백되어서 해당 url에 맞는 process() 메서드를 실행하게 됨 == 이럴려고 앞에 인터페이스로 구현하게 해서 다형성을 쓴 거

  • get.ReqeustURI() 로 요청 경로 얻기 가능
//v1/* : 하위에 어떤 거가 들어오든 일단 해당 프론트 컨트롤러가 동작함  
@WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/*")  
public class FrontControllerServletV1 extends HttpServlet {  
    // url를 키로 삼는 맵  
    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(); // 요청 url 얻음   

ControllerV1 controller = controllerMap.get(requestURI);  
        if (controller == null) {  
            response.setStatus(HttpServletResponse.SC_NOT_FOUND); //404  
            return;  
        }  
        controller.process(request, response); // 다형성   
}  
}

분석

  1. urlPatterns
    1. /v1/* : /v1 밑에 있는 하위 경로들을 호출 할 시 해당 서블릿이 호출되도록 설정
  2. controllerMap
    1. url : contoller 객체 => 다형성 활용
  3. Service
    1. 요청받은 url를 키로 해서 해당하는 컨트롤러 객체를 꺼낸 다음 구현된 proces() 메서드를 싱행시킴

구조를 어느정도 변경했고 다음 시간은 뷰를 건들임
공통된 부분

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

View 분리 - v2

모든 컨트롤러에서 jsp 파일로 포워딩 해주는 코드가 중복됨을 알 수 있음 = 지저분 , 유지보수 빡침

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

즉 이 부분을 별도로 뷰 처리만 전담으로 하는 객체를 만들 것


컨트롤러가 그냥 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);  
    }  
}

기존 인터페이스 ControllerV1은 process 메서드의 반환 타입이 void였지만 v2에서는 MyView 객체를 반환하도록 한다

public interface ControllerV2 {  
    MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;  
}

컨트롤러 수정

public class MemberListControllerV2 implements ControllerV2 {  
    private 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");  
    }  
}

새로운 프론트 컨트롤러 부분

@Override  
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
    String requestURI = request.getRequestURI(); // 요청 url 얻음  
    ControllerV2 controller = controllerMap.get(requestURI);  
    if (controller == null) {  
        response.setStatus(HttpServletResponse.SC_NOT_FOUND); //404  
        return;  
    }  
    MyView view = controller.process(request, response);  
    view.render(request,response);  
}

원래는 그냥 controller 객체의 process() 메서드를 호출해서 각 컨트롤러에 정의되어 있는 포워드 로직을 실행했었는데 해당 로직을 분리해서 MyView객체를 바꿨고 컨트롤러의 process도 void가 아니라 MyView객체를 반환하도록 코드를 변경한 후에 프론트 컨트롤러에서 MyView 객체를 받고 그 객체에 정의된 포워드 로직을 실행해서 깔끔하게 만든 코드

  1. front-controller/v2/members/new-form 요청
  2. 프론트 컨트롤러에서 매핑 정보 확인 후 해당 객체를 사용
  3. 컨트롤러 객체에서 process 메서드를 호출해서 MyView객체를 반환 받음
  4. MyView객체의 render함수를 실행해서 jsp파일로 포워딩 시킴

Model 추가 - v3

서블릿 종속성 제거
public MyView process(HttpServletRequest request, HttpServletResponse response)

보면 request ,response 정보는 필요도 없는데 스펙상 받아야하는 경우가 있어서 모든 컨트롤러에서 중복적으로 해당 코드를 적는 중

요청 파라미터 정보를 Map으로 대신 넘기도록 설계 -> 컨트롤러는 서블릿 몰라도 동작 가능
request 객체를 Model로 사용하는 대신 별도 Model객체 만들어서 반환하도록 설계 -> request 객체 필요없어짐
==즉 서블릿 없어도 사용할 수 있도록 코드 수정하기~!==

뷰 이름 중복 제거
return new MyView("/WEB-INF/views/save-result.jsp");

뷰 이름 중복이 있으니까 이 부분을 컨트롤러가 뷰의 논리 이름 반환하도록 설정하고 물리적인 위치는 프론트 컨트롤러에서 처리하도록 하면 나중에 폴더위치 바뀔 때 컨트로러 마다 수정하는 게 아니라 프론트 컨트롤러만 수정하면 되니까 유지보수가 간편

/WEB-INF/views/save-result.jsp => save-result

v3구조

ModelView
  • 지금까지 request객체를 사용을 했기 때문에 코드 종속성이 생겼음
  • 종속성 제거를 위해 별도의 Model객체를 만들어서 Vie에 전달하도록 하자 (Model + View 같이 있는 객체)

별도로 만든 모델 객체

public class ModelView {  
    private String viewName;  
    private Map<String,Object> model = new HashMap<>();  

    public ModelView(String viewName) {  
        this.viewName = viewName;  
    }  

    public String getViewName() {  
        return viewName;  
    }  

    public void setViewName(String viewName) {  
        this.viewName = viewName;  
    }  

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

    public void setModel(Map<String, Object> model) {  
        this.model = model;  
    }  
}
  • 해당 모델 객체에 뷰이름이랑 또 랜더링 할 때 쓸 모델 객체(object)를 넣은 맵을 만들어서 그걸로 이제 꺼내서 쓰면 되도록 설계

컨트롤러 인터페이스

public interface ControllerV3 {  
    ModelView process(Map<String, String> paraMap);  
}
  • request , response 객체가 안들어가니까 매우 간단하게 정리 가능 = > 서블릿 기술 사용 안함

컨트롤러

@Override  
public ModelView process(Map<String, String> paramMap) {  
    // 이미 받을 때 프론트컨트롤러가 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");  
    // getModel로 map을 반환한 다음 그 map에다가 member 데이터 넣어줌   
mv.getModel().put("member", member);  
    return mv;  
}
  • 논리적인 이름만 넣어서 반환해주면 오케
  • 물리적인 경로는 프론트 컨트롤러에서 처리
  • mv는 단순 객체니까 getModel로 map을 반환한 후에 쓸 데이터를 넣어서 반환시킴
  • ModelView 객체를 반환해서 모델처럼 사용하도록 처리 = 서블릿 종속 x

MyView 클래스

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 void modelToRequestAttribute(Map<String, Object> model, HttpServletRequest request) {  
    model.forEach((key, value)-> request.setAttribute(key, value));  
}
  • 모델에 있는 값들을 request에 다 세팅해주고 그 request객체를 통해서 jsp로 포워딩 해줌
  • jsp는 request객체가 필요하니까 모델에 있는 값들을 다시 request로 세팅하는 과정이 필요

🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋

  • request 객체는 스코프가 http 연결 끊길 때까지니까 해당 객체는 어디서든 접근이 되는 느낌이라 return값없어도 모델에서 값 세팅한 request를 따로 반환 안 해도 되는 듯
    🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋🙋

viewResolver : 논리이름을 물리이름으로~! 실제뷰를 찾아주는 해결자

  • myView객체에 물리 경로를 넣어서 반환
    private MyView viewResolver(String viewName) {  
      return new MyView("/WEB-INF/views/" + viewName + ".jsp");  
    }

프론트 컨트롤러

@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 {  
        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(); // 뷰의 논리이름을 얻음  
        MyView view = viewResolver(viewName);  
        view.render(mv.getModel(), request, response);  
    }  
    private Map<String, String> createParamMap(HttpServletRequest request) {  
        Map<String, String> paramMap = new HashMap<>();  
        // request객체에 있는 모든 값들을 조회한 다음 해당 값들을 paramMap에 넣어줌  
        request.getParameterNames().asIterator()  
            .forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));  
        return paramMap;  
    }  
    // 해당 뷰리졸버로 논리이름을 파라미터로 받아서 물리적 경로로 변경해서 반환해줌  
    private MyView viewResolver(String viewName) {  
        return new MyView("/WEB-INF/views/" + viewName + ".jsp");  
    }  
}

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

  • v3 컨트롤러는 서블릿 종속성 제거하고 뷰 경로 중복 제거등 했음
  • 이제 보다 더욱 편리하게 쓸 수 있도록 v4를 작성해보자

  • 컨트롤러가 ModelView말고 ViewName만 반환하도록 !

프론트 컨트롤러가 모델까지 만들어서 컨트롤러에 넣어줌
반환값이 String이라 ViewName만 넣어주면 됨

public interface ControllerV4 {  

   String process(Map<String,String>paramMap, Map<String,Object> model);  
}

컨트롤러 구현

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);  
        // v3에서는 모델뷰를 여기서 만들었는데 v4프론트 컨트롤러에서 만들어서 주니까 put만 하면 되네  
        model.put("member", member);  
        return "save-result";  
    }  
}
  • 컨트롤러에서 모델을 만들필요가 없어짐
  • 또 프론트에서 만들어진 Model객체의 참조값을 파라미터로 받은 다음 컨트롤러에서 값을 수정한 거라 메모리 상에 있던 인스턴스도 값이 수정된 상태에서 처리가 된다 밑에 질문 참고
    https://www.inflearn.com/questions/371530

프론트 컨트롤러 부분

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

보면 모델을 만들고 그 모델을 각 컨트롤러의 process메서드의 파라미터로 보내서 값을 처리한다
생각해보니 껍데기만 넘겨주면 컨트롤러에서 알아서 값 넣어주니까 ㄱㅊ은건가
처리된 모델은 뷰로 넘겨서 랜더링 하도록


유연한 컨트롤러1 - v5

  • 어떤 개발자는 v3 또 다른 사람은 v4로 개발하고 싶으면 어떻게 처리할까 어떻게 해야 모두 수용할 수 있을까 기존 코드의 프론트 컨트롤러는 타입을 명시적으로 지정하고 있어서 유연한 처리가 불가
    private Map<String, ControllerV4> controllerMap = new HashMap<>();
어뎁터 패턴
  • 프론트 컨트롤러는 하나의 타입만 쓸 수 있음
  • v3 ,v4 컨트롤러들은 110v , 220v 같이 완전 다른 인터페이스라 호환이 불가
  • 이럴 때 어댑터 를 사용하면 다양한 방식 컨트롤러 처리 가능

  • 핸들러 어댑터 : 중간에 어댑터 역할을 수행 , 다양한 종류의 컨트롤러 호출 가능해짐
  • 핸들러 : 컨틀러의 더 넓은 이름 , 다른 것들도 처리 할 수 있으니까 핸들러라고 범위를 넓힘

http요청이 오면 프론트 컨트롤러에서 핸들러 매핑정보에서 처리할 v3컨트롤러를 하나 찾음
핸들러 어댑터 목록을 찾을 때 v3를 처리할 수 있는 어댑터를 찾아야함 그 때 쓰는 게 MyHandlerAdapter

즉 요청에 맞는 컨트롤러를 찾고 해당 컨트롤러로 처리할 수 있는 어뎁터를 찾는 흐름임

핸들러 어댑터를 쓰는 이유
핸들러 어댑터를 쓰면 프론트 컨트롤러와 핸들러 사이를 연결해 주기 때문에 프론트컨트롤러에서 프론트컨트롤러 인터페이스 코드 변경 없이 다양한 종류의 컨트롤러를 호출할 수 있기 때문
https://www.inflearn.com/questions/750629

핸들러

public interface MyHandlerAdapter {  
    boolean supports(Object handler); // 파라미터로 받은 컨트롤러를 지원할 수 있는지 아닌지 bool로 반환  
    ModelView handle(HttpServletRequest request, HttpServletResponse response,  
                     Object handler) throws ServletException, IOException;  
}
  • 그 전에는 프론트 컨트롤러에서 컨트롤러를 호출했지만 이제는 어댑터를 통해서 컨트롤러를 호출한다

어댑터 구현

@Override  
public boolean supports(Object handler) {  
    return (handler instanceof ControllerV3); // 파라미터 컨트롤러가 v3인지 아닌지  
}
@Override  
public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {  
    ControllerV3 controller = (ControllerV3) handler; // handle메서드는 짜피 supports 다음 호출이라 캐스팅해도 ㄱㅊ  

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

프론트 컨트롤러

// 다 지원하기 위해서 컨트롤러 버전이 아닌 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());  
}  
private void initHandlerAdapters() {  
    handlerAdapters.add(new ControllerV3HandlerAdapter());  
}
  • 생성자로 멤버 변수 초기화 시키는 메서드 호출
  • 컨트롤러 -> 핸들러 : 어댑터를 사용해서 컨트롤러 호출 . 이젠 어댑터가 지원하면 뭐라도 url매핑해서 사용 가능
@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.supports(handler)) {  
            return adapter;  
        }  
    }  
    throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다.handler=" + handler);  
}  
private MyView viewResolver(String viewName) {  
    return new MyView("/WEB-INF/views/" + viewName + ".jsp");  
}
  1. getHandler로 request에 있는 요청 url을 확인한 후에 handlerMappingMap에서 요청된 url과 매핑되는 컨트롤러를 반환 -> 여기선 v3 save

  2. v3 save를 지원하는 어댑터를 gethandleradapter메서드로 찾아서 반환

  3. 해당 어댑터의 메서드 handle을 호출해서 모델뷰를 반환

    @Override  
    public ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {  
     ControllerV3 controller = (ControllerV3) handler; // handle메서드는 짜피 supports 다음 호출이라 캐스팅해도 ㄱㅊ  
    
     Map<String, String> paramMap = createParamMap(request);  
     ModelView mv = controller.process(paramMap);  
     return mv;  
    }
  4. 해당 모델 뷰에서 받은 뷰이름을 뷰리졸버에 돌림

  5. 뷰에 랜더링 시킴


유연한 컨트롤러 2 -v5

  • 프론트 컨트롤러에 몇개만 추가하면 오케
  • 해당 설정부분만 외부 주입 시키면 OCP지키는것도 가능할 듯
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  
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);// 모델뷰를 생성 110v -> 220v    mv.setModel(model);  
    return mv;  
}
  • v4 컨트롤러의 process 는 string 뷰네임을 반환을 하는데 우리가 쓰는 프론트 컨트롤러에서는 MovdelView를 받기 원함 -> 어뎁터가 필요
  • 어댑터가 다 호환될 수 있도록 viewName을 세팅한 ModelView를 만들어서 반환

정리
  • v1: 프론트 컨트롤러를 도입
    • 기존 구조를 최대한 유지하면서 프론트 컨트롤러를 도입
  • v2: View 분류
    • 단순 반복 되는 뷰 로직 분리
  • v3: Model 추가
    • 서블릿 종속성 제거
    • 뷰 이름 중복 제거
  • v4: 단순하고 실용적인 컨트롤러
    • v3와 거의 비슷
    • 구현 입장에서 ModelView를 직접 생성해서 반환하지 않도록 편리한 인터페이스 제공
  • v5: 유연한 컨트롤러
    • 어댑터 도입
    • 어댑터를 추가해서 프레임워크를 유연하고 확장성 있게 설계

출처

https://inf.run/wFfL

728x90

'Programing > Spring Boot' 카테고리의 다른 글

[MVC-1] 스프링 MVC - 기본 기능  (0) 2023.08.01
[MVC-1] 스프링 MVC - 구조 이해  (0) 2023.08.01
[MVC-1] 서블릿 , JSP , MVC패턴  (0) 2023.07.25
[MVC-1] 서블릿  (0) 2023.07.25
[MVC-1] 웹 애플리케이션 이해  (0) 2023.07.25