[Spring] 리팩토링하며 점진적으로 Spring MVC 프레임워크 만들기 !!

728x90

 

이전 글의 MVC 패턴을 점진적으로 리팩토링 하여 최종적으로 Spring MVC 프레임워크를 직접 만들 것이다.

마지막에 최종 정리가 있다.

이전 글의 MVC 패턴

회원 추가 컨트롤러

@WebServlet(name = "mvcMemberFormServlet", urlPatterns = "/servlet-mvc/members/newform")
public class MvcMemberFormServlet extends HttpServlet {
 
  @Override
  protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    String viewPath = "/WEB-INF/views/new-form.jsp";
    RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
    dispatcher.forward(request, response);
  }
}

회원 저장 컨트롤러

@WebServlet(name = "mvcMemberSaveServlet", urlPatterns = "/servlet-mvc/members/save")
public class MvcMemberSaveServlet extends HttpServlet {
 
  private MemberRepository memberRepository = MemberRepository.getInstance();
 
  @Override
  protected void service(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);
    request.setAttribute("member", member);
    
    String viewPath = "/WEB-INF/views/save-result.jsp";
    RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
    dispatcher.forward(request, response);
  }
}

회원 조회 컨트롤러

@WebServlet(name = "mvcMemberListServlet", urlPatterns = "/servlet-mvc/members")
public class MvcMemberListServlet extends HttpServlet {
 
  private MemberRepository memberRepository = MemberRepository.getInstance();
 
  @Override
  protected void service(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 패턴의 문제점

- dispatcher.forward() 중복, viewPath 중복

- 사용하지 않는 request, rsponse 파라미터

- 요청을 처리하는 서블릿마다 중복 코드가 발생하기 때문에 공통처리가 어려움

 

따라서 컨트롤러가 호출되기 전,

공통 기능을 처리하는 `프런트 컨트롤러 패턴`을 도입하면 해결할 수 있다.

 

프런트 컨트롤러 패턴을 통하여 리팩토링을 진행해 보자.

 

프런트 컨트롤러 도입 - v1

프론트 컨트롤러 패턴 특징

- 프론트 컨트롤러 서블릿 하나로 클라이언트의 요청을 받는다.

(나머지 컨트롤러는 서블릿을 사용하지 않아도 된다.)

- 프론트 컨트롤러가 요청에 맞는 컨트롤러를 호출한다.

- 공통 처리가 가능하다.

 

컨트롤러 인터페이스 v1

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

 

회원 추가 컨트롤러 v1

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

회원 저장 컨트롤러 v1

public class MemberSaveControllerV1 implements ControllerV1 {

  private 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);
    request.setAttribute("member", member);
    
    String viewPath = "/WEB-INF/views/save-result.jsp";
    RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
    dispatcher.forward(request, response);
  }
}

회원 조회 컨트롤러 v1

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

프론트 컨트롤러 v1

@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 {
    String requestURI = request.getRequestURI();
    
    ControllerV1 controller = controllerMap.get(requestURI);
    if (controller == null) {
      response.setStatus(HttpServletResponse.SC_NOT_FOUND);
      return;
    }
    controller.process(request, response);
  }
}

 

기존 컨트롤러가 컨트롤러 인터페이스 ControllerV1 을 구현하도록 한다.

 

controllerMap 은 key 로 매핑 URL 과 value 로 호출될 컨트롤러를 넣어 매핑한다.

이후 요청이 발생하면 요청이 발생한 URI 와 매핑된 컨트롤러를 찾아 호출한다.

 

프런트 컨트롤러 도입 - v1 특징

- 프론트 컨트롤러가 컨트롤러 인터페이스를 호출하여 로직의 일관성을 가져갈 수 있다.

 

프론트 컨트롤러 도입 - v1 단점

- 모든 컨트롤러에서 뷰로 이동하는 부분에 중복이 있다.

- `viewPath` 중복, `dispatcher.forward()` 중복

 

이 부분을 깔끔하게 분리하기 위해 별도로 `뷰를 처리하는 객체`를 만들어 리팩토링을 진행해 보자.

 

View 분리 - v2

뷰 객체 v2

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

컨트롤러 인터페이스 v2

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

회원 추가 컨트롤러 v2

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

회원 저장 컨트롤러 v2

public class MemberSaveControllerV2 implements ControllerV2 {

  private 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);
    
    request.setAttribute("member", member);
    
    return new MyView("/WEB-INF/views/save-result.jsp");
  }
}

회원 조회 컨트롤러 v2

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

프런트 컨트롤러 v2

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

ControllerV2 는 반환 타입이 뷰다.

그리고 컨트롤러가 컨트롤러 인터페이스 ControllerV2 를 구현하여 뷰를 반환하도록 한다.

 

그러면 프런트 컨트롤러에서 해당 컨트롤러가 반환하는 뷰를 가지고,

`view.render()` 를 호출하여 `dispatcher.forward()` 로직을 수행해서 JSP 가 실행되도록 한다.

 

View 분리 - v2 특징

JSP 를 실행하는 View 객체를 생성하고,

View 를 반환하는 컨트롤러 인터페이스를 통해 각각의 컨트롤러가 view 를 반환하면,

프런트 컨트롤러에서 각각의 컨트롤러를 호출해도 `dispatcher.forward()` 로직을 일관되게 처리할 수 있다.

 

View 분리 - v2 단점

- 컨트롤러에서 HttpServletRequest, HttpServletResponse 을 사용하는 서블릿이 꼭 필요하지 않다.

- 뷰의 물리 위치 이름이 중복된다.

 

여기서 request 객체에 데이터를 담을 때 사용하는 서블릿 대신,

별도의 Model 객체에 데이터를 담도록 하면 컨트롤러가 서블릿 기술을 전혀 사용하지 않을 수 있다.

 

데이터를 담는 `Model 객체`를 만들어 리팩토링을 진행해 보자.

 

Model 추가 - v3

view 객체 v3

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 void modelToRequestAttribute(Map<String, Object> model, HttpServletRequest request) {
    model.forEach((key, value) -> request.setAttribute(key, value));
  }
}

ModelView 객체 v3

public class ModelView {

  private String viewName;
  private Map<String, Object> model = new HashMap<>();
  
  public ModelView(String viewName) {
    this.viewName = viewName;
  }
  
  // getter setter 생략

컨트롤러 인터페이스 v3

public interface ControllerV3 {
  ModelView process(Map<String, String> paramMap);
}

회원 추가 컨트롤러 v3

public class MemberFormControllerV3 implements ControllerV3 {
  
  @Override
  public ModelView process(Map<String, String> paramMap) {
    return new ModelView("new-form");
  }
}

회원 저장 컨트롤러 v3

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

회원 조회 컨트롤러 v3

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

프런트 컨트롤러 v3

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

viewName 과 임시 저장소 Model 을 갖는 ModelView 객체를 생성한다.

그리고 ControllerV3 인터페이스가 ModelView 를 반환하도록 하여

각각의 컨트롤러가 서블릿 기술을 사용하지 않고, model 에 담긴 데이터를 조회/입력할 수 있다.

 

프런트 컨트롤러가 ModelView 객체에서 논리 이름을 가져와 물리적인 이름으로 변경하여 뷰 객체를 생성한다.

생성한 뷰 객체에게 model 객체를 파라미터로 전달하여 JSP 호출한다.

 

Model 추가 - v3 특징

- 각각의 컨트롤러에서 서블릿 종속성을 제거하였다.

- 뷰의 물리적 이름에 대한 중복도 제거하였다.

 

Model 추가 - v3 단점

- 프론트 컨트롤러가 파라미터 맵 생성, 뷰 이름 변환 등 복잡해졌다.

- 항상 ModelView 객체를 생성하고 반환해야 하는 부분이 번거롭다.

 

따라서 컨트롤러가 ModelView 를 반환하지 않고, `ViewName` 만 반환하도록 리팩토링을 진행해 보자.

 

ViewName 반환 - v4

컨트롤러 인터페이스 v4

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

회원 추가 컨트롤러 v4

public class MemberFormControllerV4 implements ControllerV4 {
  
  @Override
  public String process(Map<String, String> paramMap, Map<String, Object> model) {
    return "new-form";
  }
}

회원 저장 컨트롤러 v4

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

회원 조회 컨트롤러 v4

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

프런트 컨트롤러 v4

@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 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;
  }
  
  private MyView viewResolver(String viewName) {
    return new MyView("/WEB-INF/views/" + viewName + ".jsp");
  }
}

 

컨트롤러 인터페이스에 ModelView 가 없기 때문에 결과로 뷰 이름만 반환해 주면 된다.

 

그리고 모델 객체를 프런트 컨트롤러에서 생성하여 넘겨주어 모델 객체에 값을 담으면 그대로 담겨있게 된다.

 

ViewName 반환 - v4 특징

- ModeView 객체를 사용하지 않고, 뷰 이름만 반환하여 간단하게 사용한다.

 

ViewName 반환 - v4 단점

- ModeView 객체를 반환하고 싶을 수도 있고, 뷰 이름만 반환하고 싶을 수도 있다.

 

따라서 `어댑터 패턴`을 도입하여 유연한 컨트롤러로 리팩토링을 진행해 보자.

 

Adapter 추가 - v5

어댑터 패턴의 특징

- 한 가지 방식의 컨트롤러 인터페이스에 국한되지 않는다.

- 실제 컨트롤러를 호출하고, 결과로 ModelView 를 반환한다.

- 만약 뷰 이름만 반환한다면, ModelView 를 직접 생성해서 반환한다.

 

ModelView 반환을 지원하는 어뎁터 v5

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

뷰 이름에서 ModelView 반환을 지원하는 어뎁터 v5

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

프런트 컨트롤러 v5

@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.supports(handler)) {
        return adapter;
      }
    }
    throw new IllegalArgumentException("handler adapter를 찾을 수 없습니다.");
  }
  
  private MyView viewResolver(String viewName) {
    return new MyView("/WEB-INF/views/" + viewName + ".jsp");
  }
}

 

요청이 들어오면, 매핑된 URI : 핸들러(컨트롤러)를 통해 핸들러 저장한다.

그리고 해당 핸들러를 작동하기 위한 핸들러 어댑터를 HandlerAdapters 에서 찾아서 ModelView 객체를 결과로 받는다.

 

컨트롤러에서 뷰 이름만 반환받는 경우,

해당 뷰 이름으로 ModelView 를 생성한 후, `setModel()` 을 통해 Model 을 저장하여 ModelView 객체로 반환한다.

 

최종 정리

- v1 : 프런트 컨트롤러 도입

  > 프론트 컨트롤러에 컨트롤러 인터페이스를 사용하여 URI 와 컨트롤러를 매핑해 저장한다.

  > 프런트 컨트롤러에서 요청에 맞는 컨트롤러를 직접 찾아서 실행한다.

- v2 : View 분리

  > 컨트롤러는 뷰의 물리 이름으로 View 객체를 생성하여 반환한다.

  > `dispatcher.forward()` 로 JSP 렌더링 하는 공통 부분을 View 에서 처리한다.

- v3 : Model 추가

  > 컨트롤러에서 `request.setAttribute()` 대신 Model 에 데이터를 직접 담아 서블릿 종속성을 제거한다.

  > 컨트롤러는 뷰의 논리 이름과 Model 을 담는 ModelView 객체를 반환한다.

  > 물리적인 `viewPath` 대신 뷰의 논리 이름을 적용하고, 프런트 컨트롤러에서 물리 이름으로 조합하여 View 를 호출한다.

- v4 : ViewName 반환

  > ModelView 객체를 사용하지 않고, 프론트 컨트롤러에서 Model 객체를 생성하여 컨트롤러에게 파라미터로 제공한다.

  > 컨트롤러는 ModelView 객체가 아닌 뷰의 논리 이름만 반환한다. 

- v5 : Adapter 추가

  > ViewName 을 반환하는 컨트롤러의 경우, HandlerAdapter1 에서 ModelView 객체를 생성해 반환한다.

  > ModelView 객체를 반환하는 컨트롤러의 경우, HandlerAdapter2 에서 그대로 Model 객체를 반환한다.

  > 프런트 컨트롤러에서 핸들러 어댑터 인터페이스를 사용하여 위의 2가지 종류의 핸들러 어댑터를 저장한다. 

  > 프론트 컨트롤러에서 요청된 URI 에 매핑된 컨트롤러를 찾아, 처리할 수 있는 핸들러 어댑터를 찾고 실행한다. 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

출처 | 스프링 MVC 1(김영한) - 인프런

 
728x90