2 분 소요



김영한 스프링 MVC 강의를 듣다가, 핸들러 어댑터가 ModelAndView를 반환하는 부분이 이해가 안갔다.

스프링의 Model과 ModelAndView 에 대해 자세히 알아보자.



1. 초기 Servlet 기반의 MVC의 문제점

스프링 MVC(Spring Model-View-Controller)에서는 컨트롤러가 클라이언트의 요청을 처리하고, 결과를 뷰(View)로 전달하는 역할을 한다.

초기 Servlet 기반의 MVC에서는 뷰 이름과 모델 데이터를 각각 따로 설정해야 했으며, forward() 메서드를 통해 뷰와 데이터를 함께 전달하였다. 이는 아래와 같은 문제를 야기하였다.

  1. 서블릿(Servlet)에서는 HttpServletRequest와 HttpServletResponse를 통해 데이터를 전달하고, 뷰를 포워딩해야 했다.
  2. 만약 JSP 뷰의 이름이 변경되면(“/exampleView.jsp” → “/newView.jsp”), 모델 데이터를 설정하는 코드도 수정해야 할 수 있다.
  3. 모델 데이터가 특정 뷰에 종속적이 되어, 만약 뷰를 교체하거나 변경할 때 코드의 많은 부분을 수정해야 하는 문제가 생긴다.

** 포워딩? 서블릿에서 JSP(뷰)로 제어를 넘기는 과정

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 모델 데이터 설정
    request.setAttribute("message", "안녕!");

    // 뷰 포워딩
    RequestDispatcher dispatcher = request.getRequestDispatcher("/exampleView.jsp");  // 뷰 이름 설정
    dispatcher.forward(request, response);
}


이처럼, 모델 데이터와 뷰의 전달이 분리되지 않아 유지보수에 어려움이 있었다.



2. ModelAndView의 등장

이를 해결하기 위해 ModelAndView가 도입되었다. (현재는 잘 사용 X)

  • ModelAndView는 Spring MVC에서 Model과 View를 함께 반환할 수 있도록 만들어진 객체이다.
  • ModelAndView는 주로 초기 스프링 MVC에서 사용되었고, 뷰(View) 이름과 모델(Model) 데이터를 한 번에 처리할 수 있는 장점이 있었다.
@RequestMapping("/example")
public ModelAndView example() {
    // 뷰 이름과 모델 데이터를 한 번에 설정
    ModelAndView mav = new ModelAndView("exampleView");  // 뷰 이름 설정
    mav.addObject("message", "안녕!");  // 모델에 데이터 추가

    // ModelAndView를 반환
    return mav;
}
  • new ModelAndView("exampleView"): exampleView라는 이름의 뷰를 렌더링
  • message라는 이름으로 “안녕!” 데이터를 전달


최종 렌더링될 파일: /WEB-INF/views/exampleView.jsp

<html>
  <body>
    <h1>${message}</h1>
  </body>
</html>


하지만, 이 역시 아래와 같은 문제가 있었다.

  1. 모델과 뷰가 같은 객체에 묶여 있음
  2. 뷰 변경 시 ModelAndView 객체 전체를 수정해야 할 가능성이 있음
  3. 완벽한 분리가 아니어서 결합도가 존재



3. 현대 스프링: Model + String 방식

현대 스프링에서는 ModelAndView 대신 ModelString을 사용하여 뷰와 모델을 명확히 분리할 수 있다.

  • Model을 사용해 모델 데이터를 추가하고, 뷰 이름은 문자열(String) 으로 반환한다.
  • Model은 스프링이 자동으로 주입해 준다.
  • 뷰 이름은 단순히 논리적 이름으로 반환하며, 물리적 경로는 ViewResolver가 처리한다.
@GetMapping("/hello")
public String hello(Model model) {
    model.addAttribute("data", "안녕!"); // 모델에 데이터 추가
    return "helloView"; // 뷰 이름만 반환
}
  • 뷰 이름과 모델 데이터를 명확하게 분리할 수 있다는 점이 큰 장점이다.
  • 데이터를 Model에, 뷰 이름은 String으로 따로 반환하여 역할을 명확히 분리하여, 유지보수성이 높아진다.
  • 뷰 이름을 바꿀 때 return "newView"; 만 수정하면 된다.


Model + String 방식에서도 Model에 데이터를 추가하고, 뷰 이름을 String으로 반환하는데,
이게 진짜 데이터와 뷰를 분리한 것인지 헷갈린다!
  • ModelAndView는 하나의 객체에 모델과 뷰 정보를 동시에 담아 반환
  • 반면, Model + String 방식에서는 Model 객체와 뷰 이름(String) 을 따로 반환
// ModelAndView 방식
@RequestMapping("/example")
public ModelAndView example() {
    ModelAndView mav = new ModelAndView("exampleView"); // 뷰 이름 설정
    mav.addObject("message", "안녕!"); // 모델 데이터 추가
    return mav; // 모델과 뷰를 하나의 객체에 담아 반환
}

// Model + String 방식
@GetMapping("/hello")
public String hello(Model model) { // 모델 데이터 추가
    model.addAttribute("data", "안녕!");
    return "helloView"; // 뷰 이름만 반환, 뷰 이름과 모델 데이터를 따로 처리
}
  • ModelAndView는 뷰와 데이터를 한 객체에 담기 때문에, 뷰의 변경이 필요하면 모델 데이터도 같이 수정해야 할 가능성이 크다.
  • 반면, Model + String 방식에서는 뷰 이름만 바꾸면 되고, 모델 데이터는 Model 객체에 담겨 있어서 별도의 수정이 필요 없다.


항목 ModelAndView Model + String
데이터 전달 mav.addObject("key", value) model.addAttribute("key", value)
뷰 설정 new ModelAndView("viewName") return "viewName";
결합도 모델과 뷰가 한 객체에 묶임 모델과 뷰가 분리되어 전달
유연성 뷰 변경 시 코드 수정 필요 뷰 이름만 수정하면 됨


카테고리:

업데이트:

댓글남기기