💠프로그래밍 언어/Java

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

2025. 3. 14. 21:41
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