인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

조남철님의 프로필 이미지
조남철

작성한 질문수

스프링 웹 MVC

핸들러 메소드 5부 @ModelAttribute

@ModelAttribute 바인딩이 되지 않습니다.

작성

·

8.9K

0

== Controller ==

@Controller

public class  MainController {

@RequestMapping(value = "/test", method = RequestMethod.POST)
@ResponseBody
public int addTest(@ModelAttribute TestVo _testVo) {
TestVo testVo = _testVo;
System.out.printf("%s", testVo.getName());
// null 출력
return 0;
}

}

=== Model ===

public class TestVo {

private int id;
private String name;

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}

}

이렇게 작성한 후에 PostMan 프로그램을 통해서 아래 와 같이 전달 하였는데 전달 _testVo 객체의 값이 모두 비어 있습니다. 강좌를 이해하기로는 변수명이 맞으면 바인딩이 되는 것처럼 보였는데 왜 바인딩이 되지 않는 것인지 궁금합니다.

{

"id": 10

, "name": "cho"

}

@RequestBody 어노테이션을 이용하면 값이 매핑 되는데 무슨 차이가 있는지 궁금합니다. 

spring-webmvc : 4.3 버전을 사용하고 있습니다.

강좌 너무 감사히 잘 보고 있습니다.

고맙습니다.

답변 7

5

백기선님의 프로필 이미지
백기선
지식공유자

폼 데이터는 요청의 본문(body)을 통해 전달 되는 데이터가 맞습니다. 그런데 헤더에 해당 요청이 폼 데이터를 전송한다고 알려주고 있고, 그 데이터를 스프링이 받아서 RequestAttribute라는 걸로 파싱을 하고 그 안에 있는 걸 @RequestAttribute나 @ModelAttribute로 받을 수 있습니다. 쿼리 스트링은 바디나 헤더가 아니라 요청 라인이라는 부분이고요. 그쪽으로 들어오는 데이터도 마찬가지로 처리됩니다.

그런데 요청 헤더에 현재 요청 본문이 폼 데이터라는 헤더가 없는 경우, 그 경우에 바디에 들어있는 데이터(보통 JSON이거나 XML)를 파싱 하려면 그때는 @RequestBody가 필요하도 MessageConverter를 사용하는 겁니다.

2

백기선님의 프로필 이미지
백기선
지식공유자

POST 본문 데이터가 폼 데이터가 아닌 경우에도 @RB를 사용해서 바인딩 받을 수가 있기는 한데 그걸 처리할 수 있는 적절한 메시지 컨버터가 필요합니다. 그 부분에 대해서는 뒤에 어딘가 관련 수업이 있을겁니다.

@MA에 대해서는 이 수업에서 설명 드렸으니 다시 수업을 들어보시기 바랍니다.
@RB에 대해서는 메시지 컨버터를 다룰 때 설명이 나올겁니다.

관련 스프링 문서도 읽어보시기 바랍니다.

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestBody.html

간단히 말해서, 폼 데이터가 아닌 경우에 요청 본문이 XML인지 JSON인지 뭔지 알고 그걸 어떻게 바인딩을 하나요?? 요청을 만들어 보내신 조남철님이야 그게 JSON이고 name이 뭐구나 하고 알겠지만 스프링 입장에서 @RB가 없으면 본문을 바인딩 하라는 건줄도 모르고 (바인딩이 안되는 이유), @RB가 있더라도 그 본문을 바인딩할 때 사용할 메시지 컨버터가 없으면 바인딩을 못해서 에러가 나는겁니다. (415 에러가 난 이유)


1

백기선님의 프로필 이미지
백기선
지식공유자

요청 본문에 있는 값을 바인딩 받으려면 @RequestBody 애노테이션도 같이 사용해야합니다. @RB가 없을 때는 reuqest attriute에 들어있는 값들로 바인딩 하는데, 보통 화면에서 Form 서브밋 하거나, URL 뒤에 쿼리 매개변수로 (?name=cho)넘겨주면 그쪽으로 들어가죠.

0

조남철님의 프로필 이미지
조남철
질문자

HTTP 메시지 컨버터 2부 JSON 강좌 영상 7:50 부분이 지금 제가 문의 드렸던 내용과 같은 이유라고 생각 됩니다. (강좌를 처음부터 보지 않은 덕택에 더 많이 공부하고 있음에 감사합니다 ^^;)

그래서 HTTP Message 구조를 찾아 보았습니다.

https://developer.mozilla.org/ko/docs/Web/HTTP/Messages

HTTP Message는 시작줄, Header, Body으로 이루어 지는데  리소스를 가져오는 요청은 본문이 필요가 없고, 업데이트를 하기 위한 요청  정보에 본문을 이용한다.

그래서 POST 요청을 보내는 정보는 Body에 들어가고@RequestBody 어노테이션은 그 HTTP Message Body 값을 읽는다. 라고 이해 했습니다.

그런데 이해가 안 되는 부분이 있습니다.

HTML 에서 Submit 할 때의 Form data와 브라우저 주소창에 입력하는 Query String은 Http Message에서 Body에 들어가지 않으면 Header 부분에 들어 가게 되는건가요? 

PostMan 프로그램데 Body 탭에 form-data, x-www-form-urlencoded, raw, binary가 함께 있는데 실제 Post Man에서 요청을 보낼 때는 다른 구조의 Http Message가 생성되는 건가요?

오늘도 좋은 하루 보내세요~

0

조남철님의 프로필 이미지
조남철
질문자

Http Request 어떻게 이루어져 있는지 정확히 몰라서 이해가 안 되었나 봅니다.

요청을 보내면 Header와 Body로만 이루어져 있다고 생각 했었습니다.

말씀주신대로 다시 강좌도 다시 보고Form data와 Request Body (Payload)에 대해서도 찾아봐야 겠습니다.

예전에는 그냥 그려려니 했던 Expressjs Request Parser 가 두 가지 다른 타입을 처리하는 이유도 말씀 주신 내용과 동일한 이유일 것이라고 생각되는군요.

app.use(bodyParser.json()) // for parsing application/json
app.use(bodyParser.urlencoded({ extended: true })) // for parsing application/x-www-form-urlencoded

매일밤 답변 달아주셔서 정말 감사합니다.

행복한 하루 보내세요.

0

조남철님의 프로필 이미지
조남철
질문자

저는 그냥 스프링 부트 프로젝트를 사용하지 않고 Spring 4.3.13 Release를 사용한 팀 프로젝트에서 기선님 강좌를 따라해도 큰 문제는 없을 것이다 라고 생각을 갖고

@ModelAttribute를 사용해 보려고 한 것인데, 원하는 결과를 얻지 못해서 질문을 드렸습니다.

하지만 어제부터 하루 종일 이것 저것 고쳐 보아도 원하는 결과를 얻지 못해서 (ContentType에 문제가 있어서 바인딩을 하지 못 하는 것인가 생각 했습니다)

오늘 아침 "핸들러 메소드 4부 폼 서브밋" 강좌와 동일하게 스프링 부트 프로젝트를 새로 생성해서 테스트 해보았습니다.

1. 스프링 부트 프로젝트 생성

web, thymeleaf 참조

2. Person Vo class 생성 (getter, setter 생성)

private int age;
private String firstName;
private String lastName;
private boolean gender;

3. MainController 생성

@Controller
public class MainController {

	@GetMapping("/person")
	public String getPerson(@ModelAttribute("person") Person person, Model model) {
		return "/person";
	}

	@PostMapping("/person")
	@ResponseBody
	public Person person(@ModelAttribute("person") Person person, Model model) {
		return person;
	}
}

4. templates 하위에 person.html 생성

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="#" th:action="@{/person}" method="post" th:object="${person}">
        <input type="text" th:field="*{age}" />
        <input type="text" th:field="*{firstName}" />
        <input type="text" th:field="*{lastName}" />
        <input type="text" th:field="*{gender}" />
        <input type="submit" value="Submit" />
    </form>
</body>
</html>

이렇게 준비해서 실제 프로젝트 Run 후에 localhost:8080/person 접속 후 값을 채워 submit을 하면 person 객체가 json 형식으로 브라우저에 표시 되었습니다.

그리고 나서 강좌에서 보여주신대로 postman을 실행해서

- Get 방식으로 호출해서 html 결과가 표시 되는 것을 확인 (정상 동작)

- POST 방식

- form-data 방식 Header 추가 되지 않았음

> 정상 동작

- x-www-form-urlencoded 방식 Header에 Content-Type = application/x-www-form-urlencoded 자동 추가 되었음

> 정상 동작

- raw 방식 Header에 Content-Type = application/json 자동 추가 되었음

{ // 요청 값

"age": 99

, "firstName": "Nathan"

, "lastName": "Cho"

, "gender": true

}

> 값이 초기화 상태인 Person 객체가 전달 됨.

왜 이 경우에 값이 바인딩 되지 않는 것인가요? (원하지 않은 결과입니다. age, gender에 올바르지 않은 값을 보내도 Bad Request 에러가 발생하지 않습니다.)

> 이 경우에는 @PostMapping 어노테이션을 사용한 컨트롤러 함수에서 @ModelAttribute 대신 @RequestBody 어노테이션을 이용하는 것이 올바른 방법 인가요? (@RB를 사용했더니 원하는 결과를 얻을 수 있었습니다.)

> @RequestBody와 @ModelAttribute 어노테이션이  정확하게 어떠한 상황에서 사용을 해야 맞는것인지 궁금합니다. 

긴~ 질문 읽어주셔서 감사합니다.

PS - 추가 테스트

@ModelAttribute 붙였을 때

form-data 방식: 바인딩 정상

x-www-form-urlencoded 방식: 바인딩 정상

raw - json 방식: 바인딩 되지 않음

@RequestBody 붙였을 때

form-data 방식: 415 Unsupported Media Type

x-www-form-urlencoded 방식: 415 Unsupported Media Type

raw - json 방식: 바인딩 정상

0

조남철님의 프로필 이미지
조남철
질문자

답변 주신 내용을 제가 잘 이해하지 못 하고 있는 것 같습니다. 이해부탁 드립니다.

1. 요청 본문에 있는 값을 바인딩 받으려면 @RequestBody도 같이 사용해야 합니다.

> @ModelAttribute @RequestBody TestVo testVo 이렇게 써야 2개의 어노테이션을 함께 사용 한다는 말씀이신가요? 아니면 Post 요청을 받을 때는 @ModelAttribute 대신 @RequestBody 어노테이션을 사용해야 한다는 말씀이신가요?

2. @RequestBody가 없을 때는 request attribute에 들어 있는 값들로 바인딩을 하는데, 보통 화면에서 form 서브밋하거나, URL 뒤에 쿼리 매개변수로 넘겨주면 그쪽으로 들어가죠.

> Post 요청일 때만 Http Message 에서 Request Body (요청 본문) 이 생성되어 전달 되는데 @ModelAttribute로는 그 값을 읽을 수 없다는 말씀이신가요?

Http Message에 관련해서는 아래 링크 참고했습니다.

https://developer.mozilla.org/ko/docs/Web/HTTP/Messages

###

본문은 요청의 마지막 부분에 들어갑니다. 모든 요청에 본문이 들어가지는 않습니다. GETHEADDELETE , OPTIONS처럼 리소스를 가져오는 요청은 보통 본문이 필요가 없습니다. 일부 요청은 업데이트를 하기 위해 서버에 데이터를 전송합니다. 보통 (HTML 폼 데이터를 포함하는) POST 요청일 경우에 그렇습니다.

### 

method = RequestMethod.GET 과 (@ModelAttribute MyType myType)를 사용하면 myType에 값이 채워지는데.

method = RequestMethod.POST를 했을 때는 채워지지가 않습니다.

강좌에서 @Test postEvent() 테스트에서 post 방식으로도 받을 수 있는 것을 보여 주셨는데. 왜 저는 RequestMethod.POST 방식으로 @ModelAttribute 어노테이션을 사용했을 때 값이 바인딩 되지 못하는지 너무 궁금합니다.

ps

유투브 영상에서 테스트 코드 작성 관련 내용을 말씀 하신 것을 들었는데... 저는 작성을 하지 않아서 부끄럽습니다...

조남철님의 프로필 이미지
조남철

작성한 질문수

질문하기