본문 바로가기
Programming/Spring

Spring MVC (커맨드 객체, 모델)

by TinKerBellBass 2017. 8. 25.
728x90
반응형

참고도서

초보 웹 개발자를 위한 스프링4 프로그래밍 입문
국내도서
저자 : 최범균
출판 : 가메출판사 2015.03.02
상세보기



1. 커맨드 객체


(1) 컨트롤러에서 커맨드 객체 사용하기

폼 요청을 처리하는 컨트롤러는 각 파라미터의 값을 구하기 위해 HttpServletRequest 객체를 이용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
@RequestMapping(value = "/register/step3", method = RequestMethod.POST)
public String handleStep3(HttpServletRequest request) {
    String email = request.getParameter("email");
    String name = request.getParameter("name");
    String password = request.getParameter("password");
    String confirmPassword = request.getParameter("confirmPassword");
    
    RegisterRequest regReq = new RegisterRequest();
    regReq.setEmail(email);
    regReq.setName(name);
    ... 생략
}
cs


처리해야 할 파라미터가 늘어나면 늘어날 수록 작성해야 할 코드가 많아진다.

그래서 스프링은 커맨드(Command) 객체라는 것을 지원하고 있다.

http 요청 파라미터의 이름을 이용한 setter 메소드를 작성한 클래스를 만들고, 

이 클래스의 객체(커맨드 객체)를 메소드의 파라미터 값으로 넣어주면,

스프링은 요청 파라미터의 값을 커맨드 객체에 담아준다.


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
26
27
28
29
30
31
32
33
34
35
<!-- ContextPath/WEB-INF/view/register/step2.jsp -->
<!-- /register/step3 요청 경로로 컨트롤러에 요청하는 뷰 -->
<form action="step3" method="post">
    <input type="text" name="name" id="name">
    <input type="submit" value="제출">
</form>
<!-- 폼 값으로 tinkerbell 을 넣고 제출 -->
 
 
@RequestMapping(value = "/register/step3", method = RequestMethod.POST)
// RegisterRequest 커맨드 객체를 메소드의 파라미터로 사용
// 폼에서 넘어오는 값들이 RegisterRequest 커맨드 객체에 설정된다 
public String handleStep3(RegisterRequest regReq) {
 
    try{
        System.out.println(regReq.getName()); 
        // 출력 결과는 폼에서 넘어온 tinkerbell
 
        memberRegisterService.regist(regReq);
        return "register/step3";
    catch(AlreadyExistionMemberException ex){
        return "register/step2";
    }
}
 
 
// 커맨드 객체를 생성하는 클래스
class RegisterRequest{
 
    private String eamil;
    private String password;
    private String confirmPassword;
    private String name;
 
    ... setter, getter
}
cs


요청 파라미터 이름이 name

=> 커맨드 객체의 setter 로 요청 파라미터의 값을 넣어줌, public void setNane(String Name){this.name=name;}

=> setter 로 필드에 값이 들어간 커맨드 객체를 요청을 처리할 컨트롤러의 메소드의 파라미터로 설정

=> 요청을 처리할 컨트롤러의 메소드에서 요청 파라미터의 값을 사용할 수 있음



(2) 뷰 JSP 에서 커맨드 객체 사용하기


스프링 MVC 는 커맨드 객체의(첫 글자를 소문자로 바꾼) 클래스 이름과 동일한 속성 이름을 사용해서 커맨드 객체를 뷰에 전달한다.

따라서 뷰로 사용하는 JSP 에서 커맨드 객체에 접근할 수 있다.


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
26
27
28
29
// 커맨드 객체를 생성하는 클래스
class RegisterRequest{
 
    private String eamil;
    private String password;
    private String confirmPassword;
    private String name;
 
    ... setter, getter
}
 
@RequestMapping(value = "/register/step3", method = RequestMethod.POST)
public String handelStep3(RegisterRequest regReq) {
    try {            
        memberRegisterService.regist(regReq);
        // 커맨드 객체 RegisterRequest 를 모델에 담고
        // register/step3 을 뷰에 담아서 리턴
        // viewResolver 에 의해 ContextPath/WEB-INF/view/register/step3.jsp 가 호출됨
        return "register/step3";
    } catch (AlreadyExistingMemberException ex) {
        return "register/step2";
    }
}
 
 
<!-- ContextPath/WEB-INF/view/register/step3.jsp --> 
<!-- 뷰 에 전달되는 커맨드 객체 이름, registerRequest -->
<!-- 커맨드 객체에 담긴 값 사용 -->
${registerRequest.name}
cs



뷰에서 커맨드 객체에 접근할 때 사용할 속성명을 변경하려면, @ModelAttribute 어노테이션을 사용하면 된다.


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
26
27
28
29
30
31
// 커맨드 객체를 생성하는 클래스
class RegisterRequest{
 
    private String eamil;
    private String password;
    private String confirmPassword;
    private String name;
 
    ... setter, getter
}
 
@RequestMapping(value = "/register/step3", method = RequestMethod.POST)
// @ModelAttribute 을 사용해서 모델에서 사용할 속성 이름을 formData 로 설정
// 커맨드 객체는 모델에 담길 때, formData 속성명을 가지고 담긴다
public String handelStep3(@ModelAttribute("formData") RegisterRequest regReq) {
    try {            
        memberRegisterService.regist(regReq);
        // 커맨드 객체 RegisterRequest 를 속성값 formData 로 지정해서 모델에 담고
        // register/step3 을 뷰에 담아서 리턴
        // viewResolver 에 의해 ContextPath/WEB-INF/view/register/step3.jsp 가 호출됨
        return "register/step3";
    } catch (AlreadyExistingMemberException ex) {
        return "register/step2";
    }
}
 
 
<!-- ContextPath/WEB-INF/view/register/step3.jsp --> 
<!-- 뷰 에 전달되는 커맨드 객체 이름, formData -->
<!-- 커맨드 객체에 담긴 값 사용 -->
${formaData.name}
cs



(3) 커맨드 객체와 스프링 폼 연동


회원 정보 입력 폼에서 중복된 이메일 주소를 입력하면 다시 입력 폼을 보여 주는데,

이 경우 앞에서 입력했던 내용이 사라진 텅 빈 폼 화면을 보게 된다.

다시 입력 폼을 보여줄 때 커맨드 객체의 값을 이용해서 앞에서 입력한 값을 폼에서 보여줄 수 있다.


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
26
27
28
29
30
31
32
33
<!-- ContextPath/WEB-INF/view/register/step2.jsp -->
<!-- /register/step3 요청 경로로 컨트롤러에 요청하는 뷰 -->
<form action="step3" method="post">
    <input type="text" name="name" id="name">
    <input type="submit" value="제출">
</form>
 
 
@RequestMapping(value = "/register/step3", method = RequestMethod.POST)
// RegisterRequest 커맨드 객체를 메소드의 파라미터로 사용
// 폼에서 넘어오는 값들이 RegisterRequest 커맨드 객체에 설정된다 
public String handleStep3(RegisterRequest regReq) {
 
    try
        memberRegisterService.regist(regReq);
        return "register/step3";
    catch(AlreadyExistionMemberException ex){
        // 예외가 발생하면
         // 커맨드 객체 RegisterRequest 를 모델에 담고
        // register/step2 을 뷰에 담아서 리턴
        // viewResolver 에 의해 요청이 넘어왔던 ContextPath/WEB-INF/view/register/step2.jsp 가 다시 호출됨
        return "register/step2";
    }
}
 
 
<!-- ContextPath/WEB-INF/view/register/step2.jsp -->
<!-- 예외가 발생하여 다시 넘어온 뷰 -->
<!-- 커맨드 객체를 사용하여 예외가 발생하기 전에 입력한 값을 넣어서 출력 -->
<form action="step3" method="post">
    <input type="text" name="name" id="name" value="${registerRequest.name">
    <input type="submit" value="제출">
</form> 
cs



2. 커맨드 객체 : 중첩, 컬렉션 프로퍼티

(1) 포함관계에 있는 클래스


HTTP 요청 파라미터 이름이 "프로퍼티이름.프로퍼티이름" 과 같은 형식이면, 중첩 프로퍼티의 값을 처리한다.


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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class Respondent {
 
    private String name;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
}
 
 
public class AnsweredData {
    
    // 포함관계를 맺는다
    private Respondent res;
 
    public Respondent getRes() {
        return res;
    }
 
    public void setRes(Respondent res) {
        this.res = res;
    }
}
 
/*
AnsweredData 클래스에서 res.setName("냥냥"); 으로 
Respondent 클래스의 필드값을 설정해 줄 수 있다(중첩 프로퍼티).
이런 포함관계에 있는 클래스가 있을 때, 
뷰 JSP 에서 AnsweredData 커맨드 객체를 통해 Respondenet 객체의 값을 설정하려면,
<input type="text" name="res.name"> 처럼
<input> 태그의 name 속성을 설정해 주면 된다.
그러면 스프링은
commandObj.getRes().setName(request.getParameter("res.name"));
과 유사한 방식으로 커맨드 객체에 파라미터 값을 전달한다.
*/
 
// 컨트롤러
@Controller
@RequestMapping("/survey")
public class SurveyController {
 
    // 주소창에 직접 survey 경로를 입력하면 이 메소드가 실행
    // 리다이렉트 방식이 아니므로 주소 변동 없음, 그대로 요청한 경로 그대로 survey
    // 이 메소드가 리턴하는 뷰 surveyForm.jsp 에서 POST 방식으로 요청을 보내면
    // 주소에 변동이 없었기 때문에, POST 방식으로 /survey 요청 
    @RequestMapping(method = RequestMethod.GET)
    public String form() {
        return "survey/surveyForm";
    }
 
    // survey/surveyForm.jsp 에서 POST 방식으로 /survey 요청이 들어오면 이 메소드가 실행
    @RequestMapping(method = RequestMethod.POST)
    public String submit(@ModelAttribute("ansData") AnsweredData data) {
        return "survey/submitted";
    }
}
 
 
<!-- survey/surveyForm.jsp -->
<!-- POST 방식으로 /survey 요청을 하는 뷰 -->
<!-- AnsweredData와 포함관계에 있는 Respondent 객체의 name 필드에 값이 들어간다 --> 
<input type="text" name="res.name">
 
 
<!-- ansDate 속성명을 가지는 AnsweredData 커맨드 객체의 값 사용 --> 
<!-- survey/submitted.jsp -->
<!-- survey/surveyForm.jsp 에서 POST 방식으로 /survey 요청을 했을 때 결과를 보여주는 뷰 -->
 
${ansData.res.name}
<!-- 커맨드객체속성명.프로퍼티이름.프로퍼티이름 -->
<!-- AnsweredData 커맨드 객체와 포함관계에 있는 Respondent 객체의 name 필드 값이 출력 -->
cs

   


(2) 컬렉션 프로퍼티


HTTP 요청 파라미터 이름이 "프로퍼티이름[인덱스]" 형식이면 List 타입의 프로퍼티의 값 목록으로 처리힌다.


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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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;
    }
}
 
/*
뷰 JSP 에서 AnsweredData 커맨드 객체의 
이름이 responses 이고 List 타입인 컬렉션에 값을 담으려면,
요청 파라미터 이름으로 responses 를 사용하고,
인덱스 값을 [] 를 이용해서 지정해 주면 된다.
<input type="text" name="responses[0]"> => List 컬레션의 첫 번째 인덱스에 들어가는 값
<input type="text" name="responses[1]"> => List 컬레션의 두 번째 인덱스에 들어가는 값
폼 입력으로 responses[0] 에 냥냥, responses[1] 에 멍멍 을 입력하고 전송
==> List 컬렉션에는 {"냥냥", "멍멍"} 이 담기게 된다.  
*/
 
// 컨트롤러
@Controller
@RequestMapping("/survey")
public class SurveyController {
     
    @RequestMapping(method = RequestMethod.GET)
    public String form() {
        return "survey/surveyForm";
    } 
    
    @RequestMapping(method = RequestMethod.POST)
    public String submit(@ModelAttribute("ansData") AnsweredData data) {
        return "survey/submitted";
    }
}
 
 
<!-- survey/surveyForm.jsp -->
<!-- POST 방식으로 /survey 요청을 하는 뷰 -->
 
<input type="text" name="responses[0]">
<input type="text" name="responses[1]">
 
 
<!-- ansDate 속성명을 가지는 AnsweredData 커맨드 객체의 값 사용 -->
<!-- survey/submitted.jsp --%>
<!-- survey/surveyForm.jsp 에서 POST 방식으로 /survey 요청을 했을 때 결과를 보여주는 뷰 -->
 
<c:forEach var="response" items="${ansData.responses}" varStatus="status">
    ${status.index + 1}번: ${response}</li>
</c:forEach>
 
<!-- froEach JSTL 을 사용해서 AnsweredData 객체의 
resoponses 이름을 가지는 List 컬렉션에 담긴 값을 하나씩 꺼내와서 출력 --> 
cs

 

3. Model, ModelAndView 을 통해 컨트롤러에서 뷰에 데이터 전달하기

(1) Model 

폼 입력을 통해 넘어온 값을 결과를 출력할 뷰에 넘겨 줄 때, 커맨드 객체를 사용하였다.
폼 입력을 통해 넘어오는 데이터가 아니라 
컨트롤러에서 뷰가 응답화면을 구성하는데 필요한 데이터를 직접 생성해서 전달해야 하는 경우,
컨트롤러에서 데이터를 생성하고 Model 객체에 담아서 데이터를 전달해 줄 수 있다.
 
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<!-- Model 객체에 담을 Question 객체를 생성하는 클래스 -->
public class Question {
 
    private String title;
    private List<String> options;
 
    public Question(String title, List<String> options) {
        this.title = title;
        this.options = options;
    }
 
    public String getTitle() {
        return title;
    }
 
    public List<String> getOptions() {
        return options;
    }
 
    public boolean isChoise() {
        return options != null && !options.isEmpty();
    }
}
 
 
@Controller
@RequestMapping("/survey")
public class SurveyController {
 
    @RequestMapping(method = RequestMethod.GET)
    // Model 객체를 파라미터로 설정
    public String form(Model model) {
        // Question 객체를 생성해서 컬렉션 List 에 담는 메소드 호출
        List<Question> questions = createQuestions();
        // Model 객체에 
        // [속성명 questions, 속성값 List 컬렉션 questions(Question 객체가 담긴 List)] 을 담는다.
        model.addAttribute("questions", questions);
        // survey/surveyForm.jsp 호출
        return "survey/surveyForm";
    }
 
    // Question 객체를 생성해서 컬렉션 List 에 담는 메소드
    // Arrays.asList 배열을 컬렉션 List 로 반환
    private List<Question> createQuestions() {
        Question q1 = new Question("당신의 역할은 무엇입니까?", Arrays.asList("서버""프론트"));
        Question q2 = new Question("많이 사용하는 개발도구는 무엇입니까?", Arrays.asList("이클립스""인텔리J"));
        return Arrays.asList(q1, q2);
    }
}
 
 
<!-- survey/surveyForm.jsp -->
<!-- 속성명 questions 속성값 Question 객체가 담긴 컬렉션 List 가 Model 객체를 통해 넘어왔다 -->
<form method="post">
        // 속성명 questions 를 이용해서 데이터를 꺼낸다
        <c:forEach var="q" items="${questions}" varStatus="status">
            <p>
                ${status.index + 1}. ${q.title}
                <br />
                <c:if test="${q.choice}">
                    <c:forEach var="option" items="${q.options}">
                        <label><input type="radio" name="responses[${status.index}]" value="${option}"> ${option}</label>
                    </c:forEach>
                </c:if>
                <c:if test="${! q.choice }">
                    <input type="text" name="responses[${status.index}]">
                </c:if>
            </p>
        </c:forEach>
cs



(2) ModelAndView


Model 과 View 객체를 같이 담는 통 => ModelAndView 객체


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
// Model 객체 사용
@RequestMapping(method = RequestMethod.GET)
public String form(Model model) {
        
    List<Question> questions = createQuestions();        
    model.addAttribute("questions", questions);        
    return "survey/surveyForm";
}
 
 
// ModelAndView 객체 사용
@RequestMapping(method = RequestMethod.GET)
// 리턴 타입 ModelAndView 
public ModelAndView form(Model model) {
        
    List<Question> questions = createQuestions(); 
    // ModelAndView 객체 생성
    ModelAndView mav = new ModelAndView();
    // ModelAndView 객체의 메소드 addObject() 를 사용해서 Model 에 데이터를 담는다       
    mav.addObject("questions", questions);
    // ModelAndView 객체의 메소드 SetViewName() 을 사용해서 View 이름을 설정
    mav.setViewName("survey/surveyForm");        
    // ModelAndView 객체 반환
    return mav;
}
cs


728x90
반응형

댓글