Spring MVC 커맨드 객체
1. 커맨드 객체
1.1 커맨드 객체를 이용해서 요청 파라미터 사용하기
step2.jsp가 생성하는 폼은 다음 파라미터를 이용해서 정보를 서버에 전송한다.
- name
- password
- confirmPassword
폼 전송 요청을 처리하는 컨트롤러 코드는 각 파라미터의 값을 구하기 위해 다음과 같은 코드를 사용할 수 있다.
1
2
3
4
5
6
7
8
9
@PostMapping("/register/step3")
public String hadleStep3(HttpServletRequest request){
String email = request.getParameter("email");
String name = request.getParameter("name");
Strinng password = request.getParameter("password");
String confirmPassword = request.getParameter("confirmPassword");
...
}
위 코드가 올바르게 동작하지만, 요청 파라미터 개수가 증가할 때마다 handleStep3()메서드의 코드 길이도 함꼐 길어지는 닩점이 있다. 파라미터 개수가 20개가 넘은 복잡한 폼은 파라미터 값을 읽어와 설정하는 코드만 40줄 이상 작성해야 한다.
스프링은 이런 불편함을 줄이기 위해 요청 파라미터의 값을 커맨드(command) 객체에 담아주는 기능을 제공한다. 예를 들어 이름이 name 인 요청 파라미터의 값을 커맨드 객체의 setName() 메서드를 사용해서 커맨드 객체에 전달하는 긴으을 제공한다. 요청 파라밑터의 값을 전달 받을 수 있는 세터 메서드를 포함하는 객체를 커맨드 객체러 사용하면 된다.
커맨드 객체는 다음과 같이 요청 매핑 애노테이션이 적용된 메서드의 파라미터에 위치한다.
1
2
3
4
@PostMapping("/register/step3")
public Stirng handleStep3(RegisterRequest regReq) {
...
}
RegisterRequest 클래스에는 setEmail(), setName(), setPassword(), setConfirmPassword() 메서드가 있다, 스프링은 이들 메서드를 사용해서 email, name, password, confirmPassword 요청 파라미터의 값을 커맨드 객체에 복사한 뒤 regReq 파라미터로 전달한다. **즉, 스프링 MVC가 handleStep3() 메서드에 전달할 RegisterRequest 객체를 생성하고 그 객체의 세터 메서드를 이용해서 일치하는 요청 파라미터의 값을 전달한다.
1.2 뷰 JSP 코드에서 커맨드 객체 사용하기
예를 들어 회원 가입할 때 사용한 이메일 주소와 이름을 회원 가입 완료 화면에서 보여준다고 가정하자. HTTP 요청 파라미터를 이용해서 회원 정보를 전달했으므로 JSP의 표현식 등을 이용해서 정보를 표시해도 되지만, 커맨드 객체를 사용해서 정보를 표시할 수도 있다.
step3.jsp 코드는 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page contentType="text/html; charset=utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>회원가입</title>
</head>
<body>
<p><strong>${registerRequest.name}님</strong>
회원 가입을 완료했습니다.</p>
<p><a href = "<c:url value='/main'/>">[첫 화면 이동]</a></p>
</body>
</html>
위의 코드를 보면 ${registerRequest.name} 코드가 있다. 여기서 registerRequest가 커맨드 객체에 접근할 때 사용한 속성 이름이다. 스프링 MVC는 커맨드 객체의(첫 글자를 소문자로 바꾼) 클래스 이름과 동일한 속성 이름을 사용해서 커맨드 객체를 뷰에 전달한다. 커맨드 객체의 클래스 이름이 RegsiterReques인 경우 JSP 코드는 registerRequest라는 이름을 사용해서 커맨드 객체에 접근할 수 있다.
1.3 @ModelAttribute 애노테이션으로 커맨드 객체 속성 이름 변경
커맨드 객체에 접근할 때 사용할 속성 이름을 변경하고 싶다면 커맨드 객체로 사용할 파라미터에 @ModelAttribute 애노테이션을 적용하면 된다, 다음은 적용 예이다.
1
2
3
4
5
6
import org.springframework.web.bind.annotation.ModelAttribute
@PostMapping("/register/step3")
public String handleStep3(@ModelAttribute("formData") RegisterRequest regReq){
...
}
@ModelAttribute 애노테이션은 모델에서 사용할 속성 이름을 값으로 설정한다. 위 설정을 사용하면 뷰 코드에서 “formData”라는 이름으로 커맨드 객체에 접근할 수 있다.
1.4 커맨드 객체와 스프링 폼 연동
예를 들어 회원 정보 입력 폼에서 중복된 이메일 주소를 입력하면 텅 빈 폼을 보여준다 폼이 비어 있을므로 입력한 값을 다시 입력해야 하는 불편함이 따른다. 다시 폼을 보여줄 때 커맨드 객체의 값을 폼에 채워주면 이런 불편함을 해소할 수 있다.
1
<input type="text" name="email" id="email" value="${registerRequest.email}">
스프링 MVC가 제공하는 커스텀 태그를 사용하면 좀 더 간단하게 커맨드 객체의 값을 출력할 수 있다. 스프링은 <.form:form> 태그와 <.form:input> 태그를 제공하고 있다. 이 두 태그를 사용하면 다음과 같이 커맨드 객체의 값을 폼에 출력할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<%@ page contentType="text/html; charset=utf-8" %>
<%@ taglib prefix="form" uri="www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<title>회원가입</title>
</head>
<body>
<h2>회원 정보 입력</h2>
<form:form action="step3" modelAttribute="registerRequest">
<p>
<label>이메일:<br>
<form:input path="email"/>
</label>
<p>
...
<p>
<label>비밀번호:<br>
<form:password path="password"/>
</label>
<p>
...
<input type="submit" value="가입 완료">
</form:form>
</body>
modelAttribute 속성을 사용한다. 스프링 4.3 버전까지는 commandName 속성을 사용했는데 스프링 5 버전부터 속성 이름이 modelAttribute로 바뀌었다, 스프링 4버전을 사용하는 환경이라면 modelAttribute 속성 대신 commandName 속성을 사용하면 된다.
< form:form>태그는 HTML의 < form>태그를 생성한다. < form:form>태그의 속성은 다음과 같다.
- action : < form> 태그의 action 속성과 동일한 값을 사용한다.
- modelAttribute : 커맨드 객체의 속성 이름을 지정한다. 설정하지 않는 경우 “command”를 기본값으로 사용한다, 예제에서 커맨드 객체의 속성 이름은 “registerRequest”이므로 이 이름을 modelAttribute 속성값을 설정했다.
< form:input> 태그는 < input> 태그를 생성한다. path로 지정한 커맨드 객체의 프로퍼티를 < input> 태그의 value 속성값을 사용한다. < form:input path=”name”/>는 커맨드 객체의 name 프로퍼티 값을 value 속성으로 사용한다. 만약 커맴ㄴ드 객체의 name 프로퍼티 값이 “스프링”이었다면 다음과 같은 < input>태그를 생성한다.
1
<input id="name" name="name" type="text" value="스프링>
<.form:password> 태그도 <.form:input>태그와 유사하다. password 타입의 <.input>태그를 생성하므로 value 속성의 값을 빈 문자열로 설정한다.
< form:form> 태그를 사용하려면 커맨드 객체가 존재해야 한다. 위 코드에서 < form:form> 태그를 사용하기 때문에 step1에서 step2로 넘어오는 단계에서 이름이 “regitsterReqeust”인 객체를 모델에 넣어야 < form:form> 태그가 정상 동작한다. 이를 위해 RegisterController 클래스의 hanleStep2() 메서드에 다음과 같은 코드를 추가했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.ui.Model;
@Controller
public class RegisterController{
...
@PostMapping("register/step2")
public String handleStep2(
@RequestParam(value = "agree", defaultValue = "false") Boolean agree,
Model model)
{
if(!agree){
return "register/step1"
}
model.addAttribute("registerRequest", new RegisterRequest());
return "register/step2"
}
}
1.5 커맨드 객체 : 중첩 ∙ 콜렉션 프로퍼티
세 개의 설문 항목과 응답자의 지역과 나이를 입력받는 설문 조사 정보를 담기 위해 다음과 같은 클래스가 있다고 하자
1
2
3
4
5
6
7
8
9
10
11
12
public class Respondent{
private int age;
private String location;
public int getAge(){
return age;
}
public void setAge(int age){
this.age = age;
}
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class AnsweredData{
private List<String> responses;
private Respondent res;
public List<String> getResponses(){
return responses;
}
public void setResponses(List<String> responses){
this.responses = responses;
}
public Respondent getRes(){
return res;
}
public void setRes(Respondent res){
this.res = res;
}
}
AnsweredData 클래스는 앞서 커맨드 객체로 사용한 클래스와 비교하면 다음 차이가 있다.
- 리스트 타입의 프로퍼티가 존재한다. reponses 프로퍼티는 String 타입의 값을 갖는 List 콜렉션이다.
- 중첩 프로퍼티를 갖는다. res 프로퍼티는 Respondent 타입이며 res 프로퍼티는 다시 age 와 location 프로퍼티를 갖는다. 이를 중첩된 형식으로 표시하면 res.age 프로퍼티나 res.location 프로퍼티로 표현할 수 있다.
스프링 MVC는 커맨드 객체가 리스트 타입의 프로퍼티를 가졌거나 중첩 프로퍼티를 가진 경우에도 요청 파라미터의 값을 알맞게 커맨드 객체에 설정해주는 기능을 제공하고 있다. 규칙은 다음과 같다.
- HTTP 요청 파라미터 이름이 “프로퍼티이름[인덱스]” 형식이면 List 타입 프로퍼티의 값 목록으로 처리한다.
- HTTP 요청 파라미터 이름이 “프로퍼티이름.프로퍼티이름”과 같은 형식이면 중첩 프로퍼티 값을 처리한다.
Ref.
- 최범균, 스프링프로그래밍입문5, 가메출판사.