본문 바로가기

개발

[Spring] Model vs ModelAndView 어떤 상황에서 뭘 써야할까?

우선 여기서 말하는 Model 이란,

import org.springframework.ui.Model; 의 Model 이다.

  • ex)
    public String createForm(Model model) {
        model.addAttribute("memberForm", new MemberForm());
        return "members/createMemberForm";
    }

ModelModelAndView 둘 다 컨트롤러에서 사용됩니다. 그러나 정말 이름처럼 Model에는 데이터만 저장되고, ModelAndView에는 데이터와 뷰가 함께 저장이 돼요. 아래에서 더 자세히 알아볼게요.

 


Model

Model은 인터페이스로, addAttribute(), mergeAttribute(), containsAttribute(), getAttribute(), asMap() 총 5개의 메서드를 구현하도록 합니다. 다섯 개 모두 모델 데이터와 관련된 메서드에요.

 

public interface Model {

    Model addAttribute();
    Model mergeAttributes();
    boolean containsAttribute();

    @Nullable
    Object getAttribute();

    Map<String, Object> asMap();
}

즉, 모델은 속성에 대한 홀더를 정의하고 주로 모델에 속성을 추가하도록 설계되었어요.


ModelAndView

그에 반해, ModelAndView는 클래스이에요.

view 필드에는 View 객체나 View 이름 문자열이 들어갈 수 있구요. model 필드는 모델 데이터를 ModelMap 객체에 담고 있어요.

 

public class ModelAndView {

    private Object view;
    private ModelMap model;

       …
}

ModelMap

여기서 우리는 ModelAndView에 앞서, 잠시 ModelMap에 대해 알아보면 좋을 것 같아요.

 

ModelMap 역시 클래스인데요. LinkedHashMap<String, Object>를 상속받아서 만들어졌어요. 구현하고 있는 메서드는 addAttribute(), addAllAttributes(), mergeAttributes(), containsAttribute(), getAttribute() 총 5개 입니다.

 

Model 인터페이스에서 정의된 메서드와 거의 유사해요.

 

public class ModelMap extends LinkedHashMap<String, Object> {

    ModelMap addAttribute();
    ModelMap addAllAttributes();
    ModelMap mergeAttributes();
    boolean containsAttribute();

    @Nullable
    Object getAttribute();
}

 

클래스와 인터페이스 외에 다른 점은 ModelMap은 UI 도구와 함께 사용할 모델 데이터를 작성할 때 사용할 맵을 구현한다는 점이에요.

 

또한, 대부분의 메서드의 반환형이 ModelMap이기 때문에 chained calls 즉, 메서드 체이닝을 지원하구요.

 

정말 특이하게 model 속성의 이름을 넣어주지 않아도, 자동으로 이름을 생성해주는 기능 역시 지원하고 있어요.

 

이건 정말 신기해서 그 매커니즘을 찾아봤는데 org.springframework.core.Conventions.getVariableName() 에서 그 역할을 담당하고 있더라구요. 그래서 아래에 코드를 남겨보아요.

 

public static String getVariableName(Object value) {
    Assert.notNull(value, "Value must not be null");
    Class<?> valueClass;
    boolean pluralize = false;

    if (value.getClass().isArray()) {
        valueClass = value.getClass().getComponentType();
        pluralize = true;
    }
    else if (value instanceof Collection) {
        Collection<?> collection = (Collection<?>) value;
        if (collection.isEmpty()) {
            throw new IllegalArgumentException(
                    "Cannot generate variable name for an empty Collection");
        }
        Object valueToCheck = peekAhead(collection);
        valueClass = getClassForValue(valueToCheck);
        pluralize = true;
    }
    else {
        valueClass = getClassForValue(value);
    }

    String name = ClassUtils.getShortNameAsProperty(valueClass);
    return (pluralize ? pluralize(name) : name);
}

 

편의성 측면에서 너무 좋은 것 같아요. 스프링을 개발자 친화적인 프레임워크라고 부를 수 있는 이유로 컨테이너, IoC를 넘어서 이런 편의 메서드가 될 수도 있을 것 같아요.

 


ModelAndView

다시 ModelAndView로 돌아와서, 이 클래스는 컨트롤러가 모델과 뷰를 모두 단일 반환 값으로 반환할 수 있도록 하기 위해 두 가지를 다 보유하고 있어요. 즉, ModelAndViewModelMap 및 뷰 객체를 위한 컨테이너일 뿐인거죠. 이를 통해 컨트롤러는 두 값을 모두 단일 값으로 반환할 수 있는 거에요.

 


정리

마지막으로, ModelAndView@Controller를 이용하기 전부터 사용된 전통적인 방식이라고 해요.

 

그런데 Spirng MVC가 아주 편리한 @Controller annotation을 지원하기 시작한 이후로는 편의성에서 밀려서 ModelAndView는 잘 사용하지 않게 되었다고 합니다.