※ 먼저 이전글을 반드시 읽고와야한다.
[Spring] 스프링 요청 처리 : DispatcherServlet부터 컨트롤러 메서드 실행까지
이전 글에서 DispatcherServlet이 URL + HTTP 메서드를 분석해서 알맞은 컨트롤러 메서드를 찾는 과정까지 정리했다.
이번 글에서는 그 다음 단계인 “요청에 들어있는 값(파라미터/바디)을 자바 메서드 파라미터로 변환해서 꽂아주는 과정(파라미터 바인딩)”과, 컨트롤러 반환값이 어떤 방식으로 text/json/html 응답으로 나뉘는지 실습 코드 기준으로 정리한다.
스프링 MVC에서 응답이 갈리는 가장 큰 기준은 @ResponseBody의 유무다.
@ResponseBody가 있으면 반환값을 “뷰 이름”이 아니라 “HTTP 응답 바디 데이터”로 처리하고, 없으면 반환값을 “뷰 이름”으로 해석해 템플릿을 렌더링한다.
@ResponseBody + String아래 코드는 문자열을 그대로 HTTP body에 내려준다
@GetMapping("")
@ResponseBody
public String textDataReturn() {
return "홍길동";
}
GET /member"홍길동"이 text 응답으로 내려감
@ResponseBody + Object객체를 반환하면 스프링이 JSON으로 직렬화해서 내려준다(보통 REST API에서 가장 흔한 형태).
@GetMapping("/json")
@ResponseBody
public Member jsonDataReturn() {
Member m1 = new Member("이지연", "jiyean@naver.com");
return m1;
}
GET /member/jsonMember 객체가 JSON으로 직렬화되어 내려감
보충: ObjectMapper로 JSON 문자열을 직접 만들어
String으로 반환하면, 구조는 JSON처럼 보여도 “응답 타입 관점에서는 문자열(text)”로 다뤄질 수 있으니 의도를 명확히 하는 게 좋다
@ResponseBody 없음 + String@ResponseBody가 없고 String을 반환하면, 해당 문자열은 “데이터”가 아니라 “뷰 이름”으로 해석된다.
@GetMapping("/html")
public String htmlReturn() {
return "simple_html";
}
GET /member/htmlsimple_html을 찾아 렌더링한 HTML이 내려감
Model로 값 전달하기/member/html/dynamic은 서버가 화면과 데이터를 함께 내려주는 SSR(Server Side Rendering) 방식이다.
컨트롤러에서 Model에 값을 담아두면, 템플릿 엔진(Thymeleaf)이 ${name} 같은 표현식 자리에 값을 주입해 최종 HTML을 만들어 내려준다.
@GetMapping("/html/dynamic")
public String htmlForDynamicReturn(Model model) {
model.addAttribute("name", "김아무개");
model.addAttribute("email", "kim@naver.com");
return "dynamic_html";
}
GET /member/html/dynamicdynamic_html을 찾아 렌더링한 HTML이 내려감
GET 요청에서 서버로 값을 보내는 대표 방법은 2가지다.
/member/path/1 형태/member/param1?name=홍길동 형태@PathVariableURL 경로에 포함된 값을 꺼내서 파라미터에 바인딩한다
@GetMapping("/path/{id}")
@ResponseBody
public String path(@PathVariable Long id) {
System.out.println(id);
return "OK";
}
GET /member/path/1
id가 Long으로 바인딩되어 들어옴
@RequestParam쿼리 스트링의 key=value 값을 꺼내서 바인딩한다.
@GetMapping("/param1")
@ResponseBody
public String param1(@RequestParam(value = "name") String nameValue) {
System.out.println("Member name === " + nameValue);
return "OK";
}
GET /member/param1?name=홍길동name 값이 nameValue로 들어옴파라미터가 늘어나면 아래처럼 여러 개를 받을 수도 있다
@GetMapping("/param2")
@ResponseBody
public String param2(@RequestParam(value = "name") String nameValue,
@RequestParam(value = "email") String emailValue) {
System.out.println("Member name === " + nameValue);
System.out.println("Member email === " + emailValue);
return "OK";
}
요청: GET /member/param1?name=홍길동&email=hong@naver.com
결과: name 값은 nameValue로, email 값은 emailValue로 들어옴
@ModelAttribute로 객체 바인딩@ModelAttribute는 요청 파라미터 형태로 들어온 데이터들을 모아서 객체로 자동 매핑(데이터 바인딩)해준다
즉 name=...&email=...처럼 들어온 값들을 Member 객체 필드에 맞춰 채워서 메서드 파라미터로 넣어준다.
@GetMapping("/param3")
@ResponseBody
public String param3(@ModelAttribute Member member) {
System.out.println("Member === " + member);
return "OK";
}
요청: GET /member/param3?name=홍길동&email=hong@naver.com
결과: member.getName(), member.getEmail() 형태로 바로 사용 가능
보충:
@ModelAttribute는 생략 가능한 경우도 많아서, 코드를 간단히 쓰고 싶을 때는 생략 문법을 쓰기도 한다(팀 컨벤션에 맞추는 게 중요).
POST는 보통 데이터가 body로 들어오며, 대표적으로 아래 3가지 형태를 자주 다룬다.
application/x-www-form-urlencoded multipart/form-data application/json@ModelAttribute로 바인딩url-encoded는 구조가 쿼리 파라미터와 유사해서 @ModelAttribute로도 자연스럽게 객체 바인딩이 가능하다
@PostMapping("/url-encoded")
@ResponseBody
public String urlEncoded(@ModelAttribute Member member) {
System.out.println(member);
return "OK";
}
name=홍길동&email=hong@naver.com
- POSTMAN 요청 완료 화면:
- 콘솔 출력:
MultipartFile 받기파일 업로드가 포함되면 보통 multipart/form-data를 사용하고, 스프링에서는 MultipartFile로 파일을 받을 수 있다.
@PostMapping("/multipart-formdata")
@ResponseBody
public String multipartFormData(@ModelAttribute Member member,
@RequestParam(value = "profileImage") MultipartFile profileImg) {
System.out.println("Member === " + member);
System.out.println("File name === " + profileImg.getOriginalFilename());
return "OK";
}
namename=홍길동&email=hong@naver.com&profileImage=(binary file)
- POSTMAN 요청 완료 화면:
- 콘솔 출력:
여러 파일도 리스트로 받을 수 있다.
@PostMapping("/multipart-formdata-plura")
@ResponseBody
public String pluraMultipartFormData(@ModelAttribute Member member,
@RequestParam(value = "profileImages") List<MultipartFile> profileImgList) {
System.out.println("Member === " + member);
System.out.println("List size === " + profileImgList.size());
return "OK";
}
name=홍길동&email=hong@naver.com&profileImages=(binary file1)&profileImages=(binary file2)&profileImages=(binary file3)
- POSTMAN 요청 완료 화면:
- 콘솔 출력:
@RequestBody로 바인딩JSON 요청은 @RequestBody로 받는 것이 기본 패턴이다.
@PostMapping("/json")
@ResponseBody
public String json(@RequestBody Member member) {
System.out.println("Member === " + member);
return "OK";
}
{"name": "홍길동", "email": "hong@naver.com"}
- POSTMAN 요청 완료 화면:
- 콘솔 출력:
JSON 배열도 리스트로 그대로 받을 수 있다.
@PostMapping("/json-list")
@ResponseBody
public String jsonList(@RequestBody List<Member> memberList) {
System.out.println("Member === " + memberList);
return "OK";
}
[{"name": "홍길동1", "email": "hong1@naver.com"}, {"name": "홍길동2", "email": "hong2@naver.com"}, {"name": "홍길동3", "email": "hong3@naver.com"}]
- POSTMAN 요청 완료 화면:
- 콘솔 출력:
중첩 JSON도 객체 구조만 맞으면 그대로 바인딩된다(예: Student 안에 scores 리스트).
@PostMapping("/json-nested")
@ResponseBody
public String jsonNested(@RequestBody Student student) {
System.out.println("Member === " + student);
return "OK";
}
{"name":"홍길동", "email":"hong1@naver.com", "scores":[{"subject":"math", "point":100}, {"subject":"english", "point":90}, {"subject":"korean", "point":100}]}
- POSTMAN 요청 완료 화면:
- 콘솔 출력:
@RequestPart“JSON 데이터 + 파일”을 한 번에 보내야 하면 보통 요청은 multipart/form-data가 되고, 이때 JSON 파트와 파일 파트를 분리해서 받기 위해 @RequestPart를 사용한다.
즉 multipart 내부의 각 part를 (JSON은 객체로 역직렬화하고, 파일은 MultipartFile로) 함께 처리하는 방식이다.
@PostMapping("/json-file")
@ResponseBody
public String jsonWithFile(@RequestPart("member") Member member,
@RequestPart("profileImg") MultipartFile profileImg) {
System.out.println(member);
System.out.println(profileImg.getOriginalFilename());
return "OK";
}
member={...JSON...}`&`profileImg=(바이너리)
- POSTMAN 요청시 주의 사항 및 요청 완료 화면:
- 콘솔 출력: