[Spring] 모델 생성하기


모델 생성하기


@RequestMapping 어노테이션이 적용된 메서드의 파라미터의 리턴 타입으로 ModelAndView, Model, ModelMap, Map, 커맨드 객체 등을 이용해서 모델을 뷰에 전달하게 된다.

본 절에서는 컨트롤러가 이들 클래스를 이용해서 모델을 어떻게 뷰에 전달하는 지 살펴보자.


1. 뷰에 전달되는 모델 데이터

@RequestMapping 메서드가 ModelAndView, Model, Map을 리턴하는 경우 이들에 담긴 모델 데이터가 뷰에 전달된다. 또한, 추가로 다음의 항목도 뷰에 함께 모델로 전달된다.

- 커맨드 객체

- @ModelAttribute 어노테이션이 적용된 메서드가 리턴한 객체

- 메서드의 Map, Model, ModelMap 타입의 파라미터를 통해 설정된 모델


import org.springframework.stereotype.Controller;

import org.springframework.ui,ModelMap;

import org.springframework.web.bind.annotation.ModelAttribute;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.servlet.ModelAndView;


@Controller

public class GameSearchController {

private SearchService searchService;

@ModelAttribute("searchTypeList")

  // 이 코드에서 @ModelAttribute 어노테이션이 적용된 referenceSearchTypeList() 메서드가 리턴한 모델 객체는 "searchTypeList"라는 이름으로 뷰에 전달된다.

public List<SearchType> referenceSearchTypeList() {

List<SearchType> options = new ArrayList<SearchType>();

options.add(new SearchType(1, "전체"));

options.add(new SearchType(2, "아이템"));

options.add(new SearchType(3, "캐릭터"));

return options;

}

@RequestMapping("/search/game.do")

public ModelAndView search(@ModelAttribute("command") SearchCommand command, ModelMap model) {

String[ ] queryList = getPopularQueryList();

model.addAttribute("popularQueryList", queryList);

ModelAndView mav = new ModelAndView("search/game");

SearchResult result = searchService.search(command);

mav.addObject("searchResult", result);

return mav;

}

  //@RequestMapping 어노테이션이 적용된 search() 메서드는 파라미터로 커맨드 객체와 ModelMap 객체를 전달받는다. search() 메서드 내부에서는 ModelMap 타입 파라미터인

// model을 이용해서 "popularQueryList"이름의 모델 객체를 뷰에 전달한다. 또한, 내부적으로 ModelAndView 타입의 mav 객체를 만든 뒤 mav를 이용해서 "searchResult"라는 이름의

// 모델 객체를 뷰에 전달한다.

}


이들 모델 객체는 서로 다른 방법으로 생성되었지만, DIspatcherServlet은 이들 모델 객체에 저장된 모델 데이터를 모두 뷰에 전달한다.

따라서 뷰 코드에서는 다음과 같이 이들 모델 객체를 모두 사용할 수 있게 된다.

<body>

인기 키워드: <c:forEach var="popularQuery" items="${popularQueryList}">${popularQuery} </c:forEach>

<form action="game.do">

<select name="type">

<c:forEach var="searchType" items="${searchTypeList}">

<option value="${searchType.code}" <c:if test="${command.type == searchType.code}">selected</c:if>>

${searchType.text}</option>

</c:forEach>

</select>

<input type="text" name="query" value="${command.query}"/>

<input type="submit" value="검색" />

</form>

검색 결과: ${searchResult}

</body>


2. Map, Model, ModelMap을 통한 모델 설정

Map, Model, ModelMap을 이용하면 뷰에 전달할 모델을 생성할 수 있다.


(1) 첫 번째 방법은 이들 세 가지 타입 중 한 가지를 파라미터로 전달받을 것이다.

@RequsetMapping("search1.do")

public String search1(Map model){

model.put("result", searchResult);

}


@RequsetMapping("search2.do")

public String search2(Model model){

model.addAttribute("result", searchResult);

}


@RequsetMapping("search3.do"){

public String search3(ModelMap model){

model.addAttribute("result", searchResult);

}


(2) 두 번째 방법은 Map과 Model을 리턴하는 것이다. 이 둘은 모두 인터페이스이기 때문에 실제로는 인터페이스를 구현한 클래스의 객체를 생성해서 리턴하면 된다. 

@RequestMapping("/search1.do")

public Map<String, Object> search1(){

...

HashMap<String, Object> model = new HashMap<String, Object>();

model.put("result", searchResult);

return model;

}


@RequestMapping("/search2.do")

public Model search2(){

...

Model model = new ExtendedModelMap();

model.addAttribute("result", searchResult);

return model;

}

스프링은 org.springframework.ui.Model 인터페이스의 구현 클래스인 ExtendedModelMap 클래스를 동일한 패키지에 제공하고 있으므로, ExtendedModelMap 클래스를 이용해서 모델을 설정하면 된다.

Map의 경우 많이 사용하는 java.util.HashMap 클래스를 이용해서 모델을 설정한다.


(3) Model 인터페이스의 주요 메서드

org.springframework.ui.Model 인터페이스는 모델을 설정할 수 있도록 다음과 같은 메서드를 제공하고 있다.


1 Model addAttribute(String name, Object value)

value 객체를 name 이름으로 추가한다. 뷰 코드에서는 name으로 지정한 이름을 통해서 value를 사용한다.


2 Model addAttribute(Object value)

value를 추가한다. value의 패키지 이름을 제외한 단순 클래스 이름을 모델 이름으로 사용한다. 이 때 첫 글자는 소문자로 처리한다.

value가 배열이거나 컬렉션인 경우 첫 번째 원소의 클래스 이름 뒤에 "List"를 붙인 걸 모델 이름으로 사용한다.

이 경우에도 클래스 이름의 첫자는 소문자로 처리한다.


3 Model addAllAttribute(Collection<?> values)

addAttribute(Object value) 메서드를 이용해서 컬렉션에 포함된 객체들을 차례대로 추가한다.


4 Model AddAllAttributes(Map<String, ?> attributes)

Map에 초함된 <키, 값>에 대해 키를 모델 이름으로 사용해서 값을 모델로 추가한다.


5 Model mergeAttributes(Map<String, ?> attributes)

Map에 포함된 <키, 값>을 현재 모델에 추가한다. 단, 키와 동일한 이름을 갖는 모델 객체가 존재하지 않는 경우에만 추가된다.


6 boolean containsAttributes(String name)

지정한 이름의 모델 객체를 포함하고 있는 경우 true를 리턴한다.


다음 코드는 컨트롤러 요청 처리 메서드에서 Model 타입의 파라미터를 이용해서 모델을 설정하는 예를 보여주고 있다.

@RequestMapping("/search/game.do")

public String search(SearchCommand command, Model model){

...

model.addAttrubute("searchResult", result);

model.addAttribute("searchTypeList", searchTypeList);

...

}


컨트롤러 요청 처리 메서드에서 Model 타입을 리턴하고 싶다면 Model 인터페이스의 구현 클래스인 org.springframework.ui.ExtendedModelMap 클래스를 사용하면 된다.

@RequestMapping("search/gmae.do")

public Model search(SearchCommand command){

...

Model model = new ExtendedModelMap();

model.addAttribute("searchResult", result);

model.addAttribute("searchTypeList", searchTypeList);

return model;

}


(4) ModelMap 클래스
org.springframework.ui.ModelMap 클래스는 Map의 구현 클래스로서 제공하는, 컨트롤러 메서드의 파라미터를 통해서 전달받는다.

@RequestMapping("search/game.do")
public String search(SearchCommand command, ModelMap model){
...
model.addAttribute("result", searchResult);
...
}
ModelMap 클래스가 제공하는 모델 설정 관련 메서드는 Model 인터페이스와 동일하므로 추가 설명은 하지 않겠다.

3. ModelAndView를 통한 모델 설정

org.springframework.web.servlet.ModelAndView 클래스는 컨트롤러의 처리 결과를 보여 줄 뷰에 전달할 값을 저장하는 용도로 사용되는데, 본 절에서는 ModelAndView 객체를 생성하는 방법을 살펴보자.


(1) modelAndView 객체를 생성하는 방법은 setViewName(String viewName) 메서드를 이용하여 뷰 이름을 설정하고, addObject(String name, Object value) 메서드를 이용하여 뷰에 전달할 값을 추가한다.

@RequestmMapping("/search/game.do")

public ModelAndView search(SearchCommand command) {

ModelAndView mav = new ModelAndView();

mav.setViewName("search/game");

mav.addObject("greeting", getGreeting());

return mav;


(2) Map에 저장된 <키, 값> 쌍 전체를 뷰에 전달할 값으로 ModelAndView 객체에 추가하고 싶다면 다음과 같이 addAllObjects(Map modeMap) 메서드를 사용하면 된다.

Map referenceMap = referenceData();

mav.addAllObjects(referenceData);


(3) 생성자를 사용해서 뷰 이름과 Map을 전달할 수도 있다.

Map referenceMap = referenceData();

return new ModelAndView("search/game", referenceData);


(4) 뷰에 전달할 객체가 한 개 뿐이라면 다음과 같은 생성자를 이용해서 코드 분량을 줄일 수 있을 것이다.

return new ModelAndView("search.game", "result", referenceData");


4. @ModelAttribute 어노테이션을 이용한 모델 데이터 처리


@ModelAttribute 어노테이션을 이용하면 다음의 두 가지 작업을 수행할 수 있다.

1 @RequestMapping 어노테이션이 적용되지 않은 별도 메서드로 모델에 추가될 객체를 생성

2 커맨드 객체의 초기화 작업을 생성


cp.) @ModelAttribute의 속성은 아래와 같다.


(1) 참조 데이터 생성

웹 어플리케이션을 구현하다 보면 동일한 모델 데이터를 두 개 이상의 요청 처리 결과 화면에서 보여주어야 할 때가 있다.

예를 들어, 검색 메인 화면과 검색 결과 화면에서 검색 타입과 인기 검색어를 보여줄 수 있을 것이다. 

이 경우 다음과 같이 이들 공통 모델 데이터를 설정해주는 메서드를 구현한 뒤 요청 처리 메서드에서 호출하도록 구현 할 수 있을 것이다.

private void referenceData(Model model){

...

model.addAttribute("searchTypeList", searchTypeList);

model.addAttribute("popularQueryList", queryList);

}


@RequestMapping("/search/main.do")

public String main(Model model){

referenceData(model);

return "search/main";


@RequestMapping("search/game.do")

public ModelAndView search(

@ModelAttribute("command") SeachCommand command, Model model){

referenceData(model);

ModelAndView mav = new ModelAndView("search/game");

SearchResult result = searchService.search(Command);

mav.addObject("searchResult", result);

return mav;

}


하지만, 위와 같이 구현하려면 다음과 같은 단점이 발생한다.

1 Model이나 ModelMap과 같이 모델 정보를 설정할 때 사용할 타입이 동일해야 한다.

2 각 요청 처리 메서드에 공통으로 사용되는 모델을 설정하기 위한 코드가 중복된다.


@ModelAttribute 어노테이션을 사용하면 이런 단점 없이 두 개 이상의 요청 처리 메서드에서 공통으로 사용되는 모델을 설정할 수 있다.

@ModelAttribute 어노테이션을 메서드에 적용하면 해당 메서드가 생성한 객체가 뷰에 전달된다.

따라서, @ModelAttribute 어노테이션을 사용하면 두 개 이상의 요청 처리 메서드에서 공통으로 사용되는 모델 객체를 생성할 수 있다.

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.ModelAttribute;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.servlet.ModelAndView;


@Controller

public class GameSearchController {

@Autowired

private SearchService searchService;


@ModelAttribute("searchTypeList")

public List<SearchType> referenceSearchTypeList() {

List<SearchType> options = new ArrayList<SearchType>();

options.add(new SearchType(1, "전체"));

options.add(new SearchType(2, "아이템"));

options.add(new SearchType(3, "캐릭터"));

return options;

}


@ModelAttribute("popularQueryList")

public String[] getPopularQueryList() {

return new String[] { "게임", "창천2", "위메이드" };

}


@RequestMapping("/search/main.do")

public String main() {

return "search/main";

}


@RequestMapping("/search/game.do")

public ModelAndView search(@ModelAttribute("command") SearchCommand command) {

ModelAndView mav = new ModelAndView("search/game");

System.out.println("검색어 = " + command.getQuery().toUpperCase());

SearchResult result = searchService.search(command);

mav.addObject("searchResult", result);

return mav;

}

...

}

위 코드에서 main() 메서드와 search() 메서드는 둘 다 referenceSearchTypeList() 메서드와 getPopularQueryList() 메서드를 호출하고 있지 않다.

하지만 reference SearchTypeList() 메서드와 getPopularQueryList() 메서드에는 @ModelAttribute 어노테이션이 적용되어 있으므로 main() 메서드와 search() 메서드가 실행될 때 refereceSearchTypeList() 메서드와 getPopularQueryList() 메서드가 리턴한 객체가 모델로 뷰에 함께 전달된다.

이때 @ModelAttribute 어노테이션의 값이 뷰에 전달되는 객체의 이름으로 사용되므로, 뷰 코드에서는 다음과 같이 @ModelAttribute 어노테이션의 값을 이용해서 모델 객체에 접근할 수 있게 된다.


<body>

인기 키워드: <c:forEach var="popularQuery" items="${popularQueryList}">${popularQuery} </c:forEach>

<form action="game.do">

<select name="type">

<c:forEach var="searchType" items="${searchTypeList}">

<option value="${searchType.code}" <c:if test="${command.type == searchType.code}">selected</c:if>>

${searchType.text}</option>

</c:forEach>

</select>

<input type="text" name="query" value="${command.query}"/>

<input type="submit" value="검색" />

</form>


(2) 커맨트 객체 초기화

@ModelAttribute 어노테이션을 사용하면 커맨드 객체의 초기화 작업을 수행할 수도 있다.

예를 들어, GET 요청시에는 폼의 특정 값을 미리 초기화하고 POST 요청시에는 폼에 입력한 값을 커맨트 객체로 받아야 한다고 해보자.

이런 경우 @ModelAttribute 어노테이션을 사용하면 다음과 같이 GET 요청과 POST 요청에 대해 알맞게 커맨트 객체를 초기화할 수 있다.

아래 코드는 @ModelAttribute 어노테이션의 사용 예를 보여주고 있다.


@Controller

@RequestMapping("/account/create.do")

public class CreateAccountController {


@ModelAttribute("command")

public MemberInfo formBacking(HttpServletRequest request) {

if (request.getMethod().equalsIgnoreCase("GET")) {

MemberInfo mi = new MemberInfo();

Address address = new Address();

address.setZipcode(autoDetectZipcode(request.getRemoteAddr()));

mi.setAddress(address);

return mi;

} else {

return new MemberInfo();

}

}


@RequestMapping(method = RequestMethod.GET)

public String form() {

return "account/creationForm";

}


@RequestMapping(method = RequestMethod.POST)

public String submit(@ModelAttribute("command") MemberInfo memberInfo)

return "account/created";

}

}

위 코드에서 주목할 점은 formBacking() 메서드의 @ModelAttribute 어노테이션값과 submit() 메서드의 파라미터에서 사용된 @ModelAttribute 어노테이션의 값이 "command"로 동일하다는 것이다. submit() 메서드는 POST 요청을 처리하도록 되어 있는데, POST 요청이 전송될 경우 formBacking() 메서드가 생성한 MemberInfo 객체는 "command"라는 이름의 모델 객체로 저장되며, 이렇게 생성된"command" MemberInfo 객체는 submit() 메서드의 동일한 모델 이름을 갖는 커맨드 객체(첫 번째 파라미터)로 전달된다.


@ModelAttribute 어노테이션이 적용된 메서드가 @RequestMapping 어노테이션이 적용된 메서드 보다 먼저 호출되기 때문에, 

커맨드 객체에 대한 초기화 작업이 필요하다면 커맨드 객체와 동일한 이름을 갖는 @ModelAttribute 어노테이션이 적용된 메서드를 이용하면 된다.


(3) @ModelAttribute 어노테이션 적용 메서드에 전달 가능한 파라미터 타입
@ModelAttribute 어노테이션이 적용된 메서드는 @RequestMapping 어노테이션이 적용된 메서드와 동일한 타입의 파라미터를 가질 수 있다. 
앞서 코드에서는 HTTP 메서드를 확인하기 위해 HttpServletRequest를 파라미터로 전달받았다.

@ModelAttribute("command")
public MemberInfo formBacking(HttpServletRequest request) {
if (request.getMethod().equalsIgnoreCase("GET")) {
...
} else {
return new MemberInfo();
}
}
@ModelAttribute 어노테이션이 적용 메서드는 HttpServletRequest 뿐만 아니라 필요에 따라 Locale, @RequestParam 어노테이션 적용 파라미터, @PathVariable 어노테이션 적용 파라미터 등을 이용해서 모델 객체를 생성하는 데 필요한 정보를 구할 수 있다.


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

[Spring] 파일업로드 처리  (2) 2014.12.18
[Spring] 요청 URI 매칭  (0) 2014.12.18
[Spring] 뷰 지정  (0) 2014.12.18
[Spring] 컨트롤러 메서드의 파라미터 타입  (2) 2014.12.18
[Spring] HTML 폼과 자바빈 객체  (0) 2014.12.18