코딩/sparta TIL

TIL 24 : Spring 요청, 응답 처리 기초

americanoallday 2025. 3. 21. 11:45

요약

더보기

클라이언트에서 서버로 데이터를 전달하는 방법

1. Get : @RequestParam, @ModelAttribute

2. Post (HTML Form) : @RequestParam, @ModelAttribute

3. HTTP Request Body : @RequestBody

✅ 정리: 클라이언트 ➡ 서버로 보내는 방식

방식 설명 애노테이션
GET URL에 붙여서 보냄 @RequestParam, @ModelAttribute
POST (Form) HTML 폼에서 전송 @RequestParam, @ModelAttribute
HTTP Body
JSON 등 raw 데이터 전송 @RequestBody

 

서버에서 HTTP응답을 클라이언트에 전달하는 세가지 방법

1. 정적리소스

2. View Template : @controller

3. HTTP Message Body : @ResponseBody, @ ResponseEntity<Object>

✅ 정리: 서버 ➡ 클라이언트로 응답 주는 방식

방식 설명 애노테이션
정적 리소스 파일 그대로 전달 없음 (Spring이 자동 처리)
View Template HTML을 만들어서 전달 @Controller + return "뷰 이름"
HTTP Body JSON/문자열 직접 전송 @ResponseBody, @RestController, ResponseEntity

 

💌 1. 클라이언트 ➡ 서버 : 데이터를 보내는 방법

1️⃣ GET 방식으로 보내기

📦 URL 뒤에 ?key=value 형식으로 보내는 방식

 

🧠 대표 예시
GET /search?keyword=cat

 

📌 Spring에서는 이걸 이렇게 받음:

@GetMapping("/search")
public String search(@RequestParam String keyword) {
    // keyword = "cat"
}

 

✅ 사용되는 애노테이션:

Annotation 설명
@RequestParam URL에 붙은 ?key=value 받기
@ModelAttribute 폼 데이터를 객체에 바인딩해서 받기 (ex: User 객체 등)

2️⃣ POST 방식 (HTML Form으로 보내기)

🧠 예: 회원가입할 때 이름/나이 입력하고 “제출” 누르는 것!

<form action="/signup" method="post">
  <input name="name" />
  <input name="age" />
</form>

📌 Spring에서는 이렇게 받음:

@PostMapping("/signup")
public String signup(@ModelAttribute User user) {
    // user.name, user.age 자동으로 채워짐
}

✔️ @RequestParam, @ModelAttribute 둘 다 가능!

 

3️⃣ HTTP Body에 담아서 보내기 (ex: JSON 전송)

🧠 예: 프론트엔드가 JSON으로 보낼 때

POST /api/user
Content-Type: application/json

{
  "name": "sunyoung",
  "age": 30
}

 

📌 Spring에서는 이렇게 받음:

@PostMapping("/api/user")
public String save(@RequestBody User user) {
    // JSON → user 객체로 자동 바인딩
}

 

🎁 2. 서버 ➡ 클라이언트 : 응답을 주는 방법

1️⃣ 정적 리소스

📁 서버에 있는 파일을 그대로 줌 (HTML, 이미지, JS, CSS 등)

 

🧠 예: /static/hello.html 이 있으면

GET /hello.html 요청 시, 이 파일이 그대로 브라우저에 뜸

 

2️⃣ View Template (JSP, Thymeleaf 등)

📄 서버에서 동적으로 HTML을 만들어서 클라이언트에 줌

(= View 이름을 반환하고, ViewResolver가 찾아서 HTML로 응답)

@Controller
public class PageController {
    @GetMapping("/home")
    public String home(Model model) {
        model.addAttribute("name", "sunyoung");
        return "home"; // → /WEB-INF/views/home.jsp 로 응답
    }
}

 

3️⃣ HTTP Body로 직접 응답 (API 방식)

💡 JSON 응답 또는 문자열 그대로 보냄

@RestController
public class ApiController {

    @GetMapping("/api/user")
    public User getUser() {
        return new User("sunyoung", 30); // JSON으로 응답
    }
}

📌 또는

@GetMapping("/text")
@ResponseBody
public String text() {
    return "Hello!";
}

📌 또는

@GetMapping("/custom")
public ResponseEntity<String> custom() {
    return ResponseEntity.ok("커스텀 응답");
}

🧩 1. @RequestParam vs @ModelAttribute

✅ @RequestParam

👉 요청 파라미터 하나하나를 직접 변수로 바인딩할 때 사용

👉 ?name=sun&age=30 같이 URL 뒤에 붙는 값 (쿼리 파라미터)을 받음

보통 String, int, boolean 같은 기본 타입 받을 때 좋음

@GetMapping("/hello")
public String hello(@RequestParam String name) {
    return "Hello, " + name;
}

📌 URL 요청: /hello?name=sun

📌 결과: "Hello, sun"

 

✅ @ModelAttribute

👉 폼(form)이나 URL 파라미터 값을 객체로 바인딩하고 싶을 때 사용

name, age 같은 값들을 User 클래스 객체에 자동으로 채워줌

폼 데이터나 여러 값을 묶어서 넘길 때 아주 유용!

public class User {
    private String name;
    private int age;
    // getters & setters
}
@PostMapping("/join")
public String join(@ModelAttribute User user) {
    return "회원 이름: " + user.getName();
}

📌 form이나 /join?name=sun&age=30 같이 보내면

➡ Spring이 알아서 user.setName("sun"), user.setAge(30) 해줌!

 

✅ 차이점 정리

비교 항목 @RequestParam @ModelAttribute
바인딩 대상 단일 파라미터 (문자열 등) 객체 전체
사용 시점 쿼리 스트링 또는 폼 데이터 주로 폼 데이터
예시 @RequestParam String name @ModelAttribute User user
장점 간단한 값 받을 때 편함 여러 값을 깔끔하게 묶을 수 있음

🟡 언제 쓰면 좋을까?

✔️ 이름, 나이 하나하나 받을 땐 → @RequestParam

✔️ 회원가입/게시글 작성 등 폼 전체 받을 땐 → @ModelAttribute


🧩 2. @ResponseBody vs @RestController vs ResponseEntity

✅ @ResponseBody

👉 리턴값을 View로 처리하지 않고, HTTP 응답 Body에 그대로 보냄

문자열, JSON, XML 등 그대로 응답

API 개발에 필수!

@GetMapping("/text")
@ResponseBody
public String hello() {
    return "Hello, world!"; // 텍스트 그대로 응답
}


✅ @RestController

👉 @Controller + @ResponseBody의 조합

클래스 전체가 API 응답 전용일 때 사용

안에 있는 모든 메서드가 자동으로 @ResponseBody 적용됨

@RestController
public class UserApiController {

    @GetMapping("/api/user")
    public User getUser() {
        return new User("sunyoung", 30); // 자동 JSON 응답
    }
}

 

✅ ResponseEntity<T>

👉 응답 바디 + 응답 헤더 + 상태코드를 모두 조작할 수 있는 객체

성공/실패 상태나 커스텀 헤더 넣고 싶을 때 아주 유용함

@GetMapping("/custom")
public ResponseEntity<String> custom() {
    return ResponseEntity
        .status(201) // 상태코드 변경
        .header("X-Special", "hello")
        .body("Created!");
}

 

✅ 차이점 정리

비교 항목 @ResponseBody @RestController ResponseEntity
위치 메서드 위 클래스 위 리턴 타입
역할 View 대신 직접 응답 Body에 출력 모든 메서드가 @ResponseBody 처리됨 응답 + 헤더 + 상태코드 모두 조작
언제 사용 JSON, 텍스트 직접 반환 API 컨트롤러 전체 만들 때 응답 상세 설정이 필요할 때

🟡 언제 쓰면 좋을까?

✔️ 간단하게 응답 문자열, JSON 보낼 때 → @ResponseBody

✔️ API 전용 컨트롤러 만들 때 → @RestController

✔️ HTTP 상태코드나 헤더 직접 설정하고 싶을 때 → ResponseEntity


“API 전용 컨트롤러를 만든다” 의미 

HTML 페이지(JSP, Thymeleaf 같은 View)를 리턴하지 않고,

오직 데이터(JSON, 문자열 등)를 클라이언트에게 응답하는 컨트롤러를 만든다는 뜻!

 

 HTTP 상태코드나 헤더 직접 설정은 ResponseEntity만 가능한가요? 🙆‍♀️

Client에서 Server로 Data를 전달하는 방법

📚 Client에서 Server로 Data를 전달하는 방법은 Query Parameter, HTTP Form Data, HTTP Request Body 크게 세가지가 있다.

 

1. GET + Query Parameter(=Query String)

URL의 쿼리 파라미터를 사용하여 데이터 전달하는 방법

http://localhost:8080/request-params?key1=value1&key2=value2

 

HttpServletRequest 사용

@Slf4j
@Controller
public class RequestParamController {

    @GetMapping("/request-params")
    public void params(
    HttpServletRequest request,
    HttpServletResponse response
    ) throws IOException {
        String key1Value = request.getParameter("key1");
        String key2Value = request.getParameter("key2");
        
        log.info("key1Value={}, key2Value={}", key1Value, key2Value);
        response.getWriter().write("success");
    }
}

 

response.getWriter().write()

  • HttpServletResponse를 사용해서 응답값을 직접 다룰 수 있다.
  • @Controller 지만 @ResponseBody를 함께 사용한 것과 같다.

Postman 요청

 

log 출력결과

 

2. POST + HTML Form(x-www-form-urlencoded)

HTTP Request Body에 쿼리 파라미터 형태로 전달하는 방법

 

HTTP Request

POST /form-data
content-type: application/x-www-form-urlencoded

key1=value1&key2=value2

 

HttpServletRequest 사용

@Slf4j
@Controller
public class RequestBodyController {

    @PostMapping("/form-data")
    public void requestBody(
    HttpServletRequest request,
    HttpServletResponse response
    ) throws IOException {
    
        String key1Value = request.getParameter("key1");
        String key2Value = request.getParameter("key2");
        
        log.info("key1Value={}, key2Value={}", key1Value, key2Value);
        response.getWriter().write("success");
    }
}

 

Postman

 

Log 출력

💡 HttpServletRequest.getParameter(”key”);
를 사용하면 Query Parameter, HTML Form Data 두가지 경우 모두 데이터 형식(key=value)이 같기 때문에 해당값에 접근할 수 있다

 

1. HTTP Request Body

  • 데이터(JSON, TEXT, XML 등)를 직접 HTTP Message Body에 담아서 전달한다.
  • 주로 @RestController에서 사용하며, 대부분 JSON 형식으로 데이터를 전달한다.
    • POST, PUT, PATCH Method에서 사용한다.
    • GET, DELETE Method는 Body에 데이터를 담는것을 권장하지 않는다.
  • HttpServletRequest 사용
    @Getter
    @Setter
    public class Board {
    
    	private String title;
    	private String content;
    
    }
    package com.example.springbasicannotation.controller;
    
    import com.example.springbasicannotation.entity.Board;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import jakarta.servlet.ServletInputStream;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Controller;
    import org.springframework.util.StreamUtils;
    import org.springframework.web.bind.annotation.PostMapping;
    
    import java.io.IOException;
    import java.nio.charset.StandardCharsets;
    
    @Slf4j
    @Controller
    public class RequestBodyController {
    
        // JSON을 객체로 변환해주는 Jackson 라이브러리
        private ObjectMapper objectMapper = new ObjectMapper();
    
        @PostMapping("/request-body")
        public void requestBody(
                HttpServletRequest request,
                HttpServletResponse response
        ) throws IOException {
    
            ServletInputStream inputStream = request.getInputStream();
            String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
    
            log.info("messageBody={}", messageBody);
    
            Board board = objectMapper.readValue(messageBody, Board.clss);
    
            log.info("board.getTitle()={}, board.getContent()={}", board.getTitle(), board.getContent());
    
            response.getWriter().write("success");
    
        }
        
    }
  • Postman
  • 출력결과
⭐ JSON을 Java 객체로 변환하려면 Jackson과 같은 라이브러리를 사용해야 한다. Spring Boot는 기본적으로 Jackson 라이브러리의 ObjectMapper를 제공하며, starter-web에 포함되어 있습니다.

@RequestParam

📚 URL에서 파라미터 값과 이름을 함께 전달하는 방식으로 주로 HTTP 통신 Method 중 GET 방식의 통신을 할 때 많이 사용한다. @Requestparam을 사용하면 요청 파라미터 값에 아주 쉽고 간편하게 접근(Parameter Binding)할 수 있다.
💡?뒤에 오는 URL을 Query String, Query Parameter, Request Param이라 한다.

 

예시코드

@Slf4j //로그 사용
@Controller // 응답할 뷰가 있을 때
public class RequestParamControllerV2 {

	@ResponseBody // @controller + ResponseBody : RestController
	@GetMapping("/v1/request-param")
	public String requestParamV1 (
    //@RequestParam : 쿼리 키값이 네임 및 AGE일경우 바인딩된다.
    @RequestParam("name") String userName,
    @RequestParam("age") int userAge													
	) {
		// logic
        log.info("name={}", userName);
        log.info("age={}", userAge);
        return "success";
	}

}

 

1. @Controller + @ResponseBody

  • View를 찾는 것이 아니라 ResponseBody에 응답을 작성한다(=@RestController)

2. @RequestParam

  • 파라미터 이름으로 바인딩한다.
  • 바인딩 :“HTTP 요청에서 들어온 값들을 → 자바 메서드의 매개변수에 자동으로 넣어주는 것”

3. @RequestParam(”속성값”) 

  • 속성값이 파라미터 이름으로 매핑된다.

Postman

 

출력결과

 

“속성값”과 변수명이 같으면 생략이 가능하다.

ex) @RequestParam("name") String name

-> @RequestParam String name

// GET http://localhost:8080/v2/request-param?name=sparta&age=100
@ResponseBody
@GetMapping("/v2/request-param")
public String requestParamV2 (
@RequestParam String name,
@RequestParam int age													
) {
// logic
log.info("name={}", name);
log.info("age={}", age);
return "success";
}

 

@RequestParam 사용법

1. 어노테이션, 속성값 모두 생략

  • @RequestParam은 생략이 가능하다.
    // GET http://localhost:8080/v3/request-param?name=sparta&age=100
    @ResponseBody
    @GetMapping("/v3/request-param")
    public String requestParamV3 (
    String name, //@RequestParam은 생략이 가능하다.
    int age //@RequestParam은 생략이 가능하다.			
    ) {
    // logic
    log.info("name={}", name);
    log.info("age={}", age);
    return "success";
    }
  • 생략하면 @RequestParam(required=false) 필수 여부 속성이 default로 설정된다.
  • 단, 요청 파라미터와 이름이 완전히 같아야 한다.
  • 단순 타입(int, String, Integer 등)이어야 한다.
  • Postman
  • 출력결과
⭐ 위의 생략하는 방식은 권장하지 않습니다. 명시적으로 표시되어있지 않으면 팀의 협의가 있지 않는 경우 다른 개발자들에게 혼동을 주게 됩니다. 최소 @RequestParam String name 속성 값 생략 형태를 쓰면 됩니다.

 

2. required 속성 설정

  • 파라미터의 필수 값을 설정한다.
  • API 스펙을 규정할 때 사용한다.
    @ResponseBody
    @GetMapping("/v4/request-param")
    public String requestParam (
    @RequestParam(required = true) String name, // 필수
    @RequestParam(required = false) int age	// 필수가 아님										
    ) {
    // logic
    log.info("name={}", name);
    log.info("age={}", age);
    return "success";
    }
  • @RequestParam을 사용하면 기본 Default값은 True이다.
    • True로 설정된 파라미터 값이 요청에 존재하지 않으면 400 BadRequest(클라이언트 측 에러)
  • Exception이 발생하지 않는 경우
    ex) http://localhost:8080/v4/request-param?name=sparta&age=100
  • Exception이 발생하는 경우
    ex) http://localhost:8080/v4/request-param?age=100
    ex) http://localhost:8080/v4/request-param
    • required = false 설정이 되어있으면 해당 파라미터는 없어도 된다.
      • 주의! http://localhost:8080/v4/request-param?name=sparta 요청한다면?
        • 500 Error가 발생한다.
        • int Type에는 null을 넣을 수 없다. 0이라도 들어가야 한다.
      • 따라서 보통 null을 허용하는 Integer로 사용하거나 default 옵션을 사용한다.
        @ResponseBody
        @GetMapping("/v4/request-param")
        public String requestParam (
        @RequestParam(required = true) String name, // 필수
        @RequestParam(required = false) Integer age										
        ) {
        // logic
        log.info("name={}", name);
        log.info("age={}", age);
        return "success";
        }
    • 파라미터 Key값만 있고 Value가 없는 경우
      http://localhost:8080/request-param?name=
      • null과 빈 문자열 “”은 다르다!
      • 위 형태는 빈 문자열 “” 로 인식한다, True지만 통과가 되어버린다. 주의해야 한다.

3. default 속성 적용

파라미터의 기본 값을 설정한다.

@RequestParam(required = true, defaultValue = "sparta") String name,
@ResponseBody
@GetMapping("/v5/request-param")
public String requestParam (
@RequestParam(required = true, defaultValue = "sparta") String name,
@RequestParam(required = false, defaultValue = "1") int age											
) {
// logic
log.info("name={}", name);
log.info("age={}", age);
return "success"	
}

 

name Parameter 의 값이 없으면 기본적으로 “sparta”으로 설정한다

ex) http://localhost:8080/v5/request-param?age=100


age Parameter의 값이 없으면 기본적으로 1 으로 설정한다.

ex) http://localhost:8080/v5/request-param?name=wonuk

 

ex) http://localhost:8080/v5/request-param

 

주의! defaultValue 속성을 설정하게 되면 “” 빈 문자열의 경우에도 기본 값이 설정된다.

ex) http://localhost:8080/v5/request-param?name&age

 

4. Map 사용

Parameter를 Map형태로 조회가 가능하다.

@RequestParam Map<String, String> map
@ResponseBody
@GetMapping("/v6/request-param")
public String requestParamV6(
@RequestParam Map<String, String> map
) {
// logic
log.info("name={}", map.get("name"));
log.info("age={}", map.get("age"));

return "success";
}

Map 형태(key=value)로 조회가 가능하다

 

ex) http://localhost:8080/v6/request-param?name=sparta&age=100

 

MultiValueMap 형태(key=[value1, value2])로 조회가 가능하다.

@RequestParam MultiValueMap<String, String> map
@ResponseBody
@GetMapping("/v6/request-param")
public String requestParamV6(
@RequestParam MultiValueMap<String, String> map
) {
// logic
log.info("name={}", map.get("name"));
log.info("age={}", map.get("age"));

return "success";
}

 

ex) http://localhost:8080/v6/request-param?name=sparta&name=wonuk&name=tutor&age=100

 

💡 파라미터 Map의 Value가 1개인 경우에는 Map, 여러 개인 경우 MultiValueMap을 사용한다. 하지만 대부분의 파라미터 값은 한 개만 존재한다.

@ModelAttribute

📚 요청 파라미터를 받아 필요한 Object로 바인딩 해준다. 주로 HTML 폼에서 전송된 데이터를 바인딩하고 HTTP Method POST인 경우 사용된다.

 

1. 기존 코드 

💡@Data는 @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor를 자동으로 설정해주는 역할을 한다. 테스트 용도로만 사용하고 실무에서는 잘 사용하지 않는다.

 

ex) http://localhost:8080/v1/tutor + x-www-form-urlencoded

@Data
public class Tutor {

	private String name;
	private int age;

}

@Controller
public class ModelAttributeController {

  @ResponseBody
  @PostMapping("/v1/tutor")
  public String requestParamV1(
          @RequestParam String name,
          @RequestParam int age
  ) {
      Tutor tutor = new Tutor();
      tutor.setName(name);
      tutor.setAge(age);

      return "tutor name = " + name + " age = " + age;
  }

}

 

@RequestParam의 Mapping을 사용하게 되면 위와 같은 객체를 생성하는 코드가 포함된다.

  • @ModelAttribute 는 해당 과정을 자동화 한다.

Postman

POST /v1/tutor
content-type: application/x-www-form-urlencoded

name=wonuk&age=100

 

2. @ModelAttribute 적용

ex) http://localhost:8080/v2/tutor+ x-www-form-urlencoded

@ModelAttribute Tutor tutor
@ResponseBody
@PostMapping("/v2/tutor")
public String modelAttributeV2(
@ModelAttribute Tutor tutor													
) {
	String name = tutor.getName();
	int age = tutor.getAge();
	return "tutor name = " + name + " age = " + age;
}

 

Postman

POST /v2/tutor
content-type: application/x-www-form-urlencoded

name=wonuk&age=100

 

@ModelAttirubte 동작 순서

  1. 파라미터에 @ModelAttribute가 있으면 파라미터인 Tutor 객체를 생성한다.
  2. 요청 파라미터 이름으로 객체 필드의 Setter를 호출해서 바인딩한다.
    1. 파라미터 이름이 name 이면 setName(value); 메서드를 호출한다.
    2. 파라미터 이름과 필드 이름이 반드시 같아야 한다!

@Data의 Setter가 없다면?

@Getter
public class Tutor {

	private String name;
	private int age;

}

객체 필드에 값이 set되지 않는다.

 

파라미터의 타입이 다른 경우

  • 만약 요청 파라미터 age 에 int가 아닌 String 이 전달된다면?

ex) http://localhost:8080/v2/tutor+ x-www-form-urlencoded

POST /v2/tutor
content-type: application/x-www-form-urlencoded

name=wonuk&age=nbcamp

  • BindException 발생
  • 이런 경우 때문에 Validation(검증)이 필요하다.

 

3. @ModelAttirubte 생략

@ModelAttribute와 지난 시간에 배운 @RequestParam은 모두 생략이 가능하다.

ex) http://localhost:8080/v3/tutor+ x-www-form-urlencoded

POST /v3/tutor
content-type: application/x-www-form-urlencoded

name=wonuk&age=100
@ResponseBody
@PostMapping("/v3/tutor")
public String modelAttributeV3(Tutor tutor) {

	String name = tutor.getName();
	int age = tutor.getAge();

	return "tutor name = " + name + " age = " + age;
}

Spring에서는 @RequestParam이나 @ModelAttribute가 생략되면

  • String, int, Integer 와 같은 기본 타입은 @RequestParam과 Mapping한다. V4
@ResponseBody
@PostMapping("/v4/tutor")
public String requestParamV2(
        String name,
        int age
) {
    return "tutor name = " + name + " age = " + age;
}

나머지 경우들(객체)은 모두 @ModelAttribute 와 Mapping한다. V3


HTTP Message Body(요청)

💡@RequestParam, @ModelAttribute는 GET + Query Parameter와, POST HTML Form Data를 바인딩하는 방법이다.
  • 이제부터 배울 내용은 HTTP Message Body에 직접적으로 Data가 전달되는 경우이다.
    • Request Body의 Data를 바인딩하는 방법이다.
  • REST API에서 주로 사용하는 방식이다.
  • HTTP Method POST, PUT, PATCH에서 주로 사용한다.
    • GET은 Request Body가 존재할 수는 있지만 권장하지 않는다.
  • JSON, XML, TEXT 등을 데이터 형식으로 사용한다.

HTTP Message 구조

 

HTTP Request, Response 예시

Server에서 Request로 전달받은 Data를 처리하기 위해서 바인딩 해야 한다.

ex) JSON → Object

 

TEXT

📚 HTTP Request Body에 Data가 전송되는 경우 HttpMessageConverter를 통해 바인딩된다.
💡현대에는 Restful API를 주로 사용하고 있어서 대부분의 경우 JSON 형식으로 통신한다.

 

1. HttpServletRequest 예시

request.getInputStream();

@Slf4j
@Controller
public class RequestBodyStringController {
	@PostMapping("/v1/request-body-text")
  public void requestBodyTextV1(
          HttpServletRequest request,
          HttpServletResponse response
  ) throws IOException {

      ServletInputStream inputStream = request.getInputStream();
      String bodyText = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
      
      response.getWriter().write("response = " + bodyText);
  }
}

 

Postman

Request → Body → raw → Text

 

Request Header Content-Type : text/plain

 

2. I/O 예시

  • InputStream(읽기) 파라미터 지원
    • HTTP Request Body Data 직접 조회
  •  OutputStream(쓰기) 파라미터 지원
    • HTTP Response Body 직접 결과 출력
@PostMapping("/v2/request-body-text")
public void requestBodyTextV2(
InputStream inputStream, //inputstream 
Writer responseWriter //outputstream
) throws IOException {
  String body = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
  responseWriter.write("response = " + bodyText);
}

 

Postman

 

3. HttpEntity 예시

  • HttpMessageConverter 사용 → 추후 설명
  • HttpEntity를 사용하면 HttpMessageConverter를 사용한다.
@PostMapping("/v3/request-body-text")
public HttpEntity<String> requestBodyTextV3(HttpEntity<String> httpEntity) { 
	// HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
	String body = httpEntity.getBody();
		
	return new HttpEntity<>("response = " + body); // 매개변수 = Body Message
}

 

Postman

  • Spring의 HttpMessageConverter 덕분에 간편하게 Request Data에 접근할 수 있다.
    1. HttpEntity를 사용하면 HttpMessageConverter가 동작하여 자동으로 매핑된다.
    2. 요청 뿐만이 아닌 응답까지 HttpEntity 하나만으로 사용이 가능해진다.
💡Converter는 어떤 뭔가를 다른 뭔가로 바꿔주는(Convert) 장치를 말한다. 예를 들면 태양빛을 전기로 바꿔주는 장치 또한 컨버터이다.

HttpEntity

📚 HttpEntity는 HTTP Header, Body 정보를 편리하게 조회할 수 있도록 만들어준다.
  • HttpEntity 역할
    1. Http Request Body Message를 직접 조회한다
    2. Request 뿐만 아니라 Response도 사용할 수 있도록 만들어준다.
    3. Response Header 또한 사용할 수 있다.
    4. Request Parameter를 조회하는 기능들과는 아무 관계가 없다.
    5. View를 반환하지 않는다.
  • HttpEntity를 상속받은 객체
    • RequestEntity<>
      • HTTP Request Method, URL 정보가 추가 되어있다.
    • ResponseEntity<>
      • HTTP Response 상태 코드 설정이 가능하다.

코드 예시

@Controller
public class RequestBodyStringController {
@PostMapping("/v4/request-body-text")
public HttpEntity<String> requestBodyTextV4(RequestEntity<String> httpEntity) {

      // HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
      String body = httpEntity.getBody();
      // url, method 사용 가능

      return new ResponseEntity<>("response = " + body, HttpStatus.CREATED); // Body Data, 상태코드
  }

}

 

Postman

  • 응답은 추후 응답 데이터 강의에서 다룹니다.
  • 위 방법을 적용해도 불편하다.
    • Data를 httpEntity에서 꺼내어 사용해야 한다.
💡Spring은 Http RequestBody Message를 읽어서 String이나 Object로 자동으로 변환해준다. 이때 HttpMessageConverter가 사용된다.

@RequestBody, @ResponseBody

📚 Spring에서 @RequestBody, @ResponseBody 어노테이션을 사용하면 각각 Request, Response 객체의 Body에 편하게 접근하여 사용할 수 있다.

 

코드 예시

@Controller // @RestController = @Controller + @ResponseBody
public class RequestBodyStringController {
	
  @ResponseBody
  @PostMapping("/v5/request-body-text")
  public String requestBodyTextV5(
          @RequestBody String body,
          @RequestHeader HttpHeaders headers
  ) {
      // HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
      String bodyMessage = body;

      return "request header = " + headers + " response body = " + bodyMessage;
  }
}

 

Postman

  • @RequestBody : 메서드의 매개변수 위치에 사용
    • 요청 메세지 Body Data를 쉽게 조회할 수 있다.
    • HTTP 요청의 “Body”에 있는 데이터를 자바 객체(또는 String 등)에 바인딩해주는 애노테이션
    • 보통 POST, PUT 요청에서 사용
  • @RequestHeader : 메서드의 매개변수 위치에 사용
    • 요청 헤더 정보 조회
  • @ResponseBody : 클래스, 메서드 위에 선언해서 사용
    • 응답 메세지 바디에 값을 쉽게 담아서 전달할 수 있도록 해준다.
    • 자바 메서드의 반환값을 View(JSP)가 아니라, 그대로 HTTP 응답 Body에 넣어주는 애노테이션
    • 즉, “문자열이면 문자열 그대로”, “JSON이면 JSON 그대로” 클라이언트에게 응답됨
❓ @RequestBody, @RequestHeader 쓰려면 @ResponseBody도 써야 할까?

👉 정답은 “아니다!”

@RequestBody, @RequestHeader는 요청(Request)을 다루는 애노테이션

@ResponseBody는 응답(Response)을 다루는 애노테이션

✔️ 그래서 둘은 별개로 동작

✔️ 하지만 실무에선 같이 쓰는 경우가 많음! (그래서 같이 등장하는 것처럼 보임)



💡 만약 @ResponseBody를 빼면 어떻게 될까?
@PostMapping("/v5/request-body-text")
public String requestBodyTextV5(@RequestBody String body) {
    return "hello";
}​

 

✔ 이렇게 되면 "hello"라는 문자열을 반환하지만,
Spring은 ViewResolver를 이용해 "hello.jsp" 파일을 찾으려고 함!
➡ 즉, “hello.jsp라는 화면을 보여주려는 동작”이 됨 😅


@RequestBody와 @RequestHeader는 오직 “매개변수(Parameter)“에만 쓸 수 있음!
❌ 메서드 위나 클래스 위에는 사용할 수 없음!


✅ 왜 매개변수에만 쓸 수 있을까?
Spring에서
• @RequestBody는 👉 요청 바디(JSON, text 등)의 값을
• @RequestHeader는 👉 요청 헤더의 값을
➡ “어떤 변수에 넣어줘야 할지”가 반드시 필요


📌 즉, 이 애노테이션들은 “값을 어디에 바인딩할지”를 알아야 하니까, 변수(매개변수) 앞에만 쓸 수 있음!
메서드나 클래스에는 바인딩할 대상이 없기 때문에 사용할 수 없음.
  • 요약
    1. 요청 파라미터, HTML Form Data에 접근하는 경우
      • @RequestParam, @ModelAttribute 를 사용한다.
    2. Http Message Body에 접근하는 경우
      • @RequestBody를 사용한다. (JSON, XML, TEXT)

JSON

💡Json은 @RestController 에서 가장 많이 사용되는 데이터 형식이다. 현재 대부분의 API는 Request, Response 모두 JSON 형태로 통신한다.
⛔ Json 형태로 Data를 전송할 때는 Request Header의 content-type이 꼭 application/json 이여야 한다,

 

1. HttpServletRequest 사용 예시

@Data
public class Tutor {
	private String name;
	private int age;
}

@RestController
public class JsonController {
	
	private ObjectMapper objectMapper = new ObjectMapper();

	@PostMapping("/v1/request-body-json")
	public void requestBodyJsonV1(
				HttpServletRequest request, 
				HttpServletResponse response
	) throws IOException {

		// request body message를 Read
		ServletInputStream inputStream = request.getInputStream();
		// UTF-8 형식의 String으로 변환한다.
		String requestBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

		// String requestBody를 ObjectMapper를 사용하여 변환 "{\"name\":\"wonuk\", \"age\":10}"
		Tutor tutor = objectMapper.readValue(requestBody, Tutor.class);
		
		// 응답
		response.getWriter().write("tutor" + tutor);
	}
}

 

Postman

Content-Type 헤더 확인 : application/json

  • HttpServletRequest를 사용하여 HTTP Message Body 데이터를 Read하여 문자로 변환한다.
  • 문자로 만들어진 JSON을 Jackson 라이브러리의 objectMapper를 사용하여 Object로 변환

 

2. @RequestBody 사용 예시

@RestController
public class JsonController {
    private ObjectMapper objectMapper = new ObjectMapper();
	
    @PostMapping("/v2/request-body-json")
    public String requesBodytJsonV2(@RequestBody String requestBody) throws IOException {

      Tutor tutor = objectMapper.readValue(requestBody, Tutor.class);

      return "tutor.getName() = " + tutor.getName() + "tutor.getAge() = " + tutor.getAge();
  }
    
}

 

Postman

@RequestBody를 사용하여 HTTP Request Body의 Data에 접근한다.

 

ObjectMapper가 계속 반복되는데 @ModelAttribute처럼 객체로 바로 반환은 안되나요?
 🧑‍🎓 Spring은 개발에 필요한 대부분의 기능이 구현되어 있습니다.

 

3. ObjectMapper 제거

@RestController
public class JsonController {
	
	@PostMapping("/v3/request-body-json")
	public String requestBodyJsonV3(@RequestBody Tutor tutor) {
		
		Tutor requestBodyTutor = tutor;

		return "tutor = " + requestBodyTutor;
	}
}

 

Postman

❓ 위 Controller가 동작하는 이유는 무엇인가요?
  • @RequestBody
    • @RequestBody 어노테이션을 사용하면 Object를 Mapping할 수 있다.
    • HttpEntity<>, @RequestBody를 사용하면 HTTPMessageConverter가 Request Body의 Data를 개발자가 원하는 String이나 Object로 변환해준다.
    • JSON to Object의 Mapping 또한 가능하다.
      • MappingJackson2HttpMessageConverter 의 역할

쉽게 설명하면 HTTP Message Converter가 ObjectMapper를 대신 실행한다.

 

4. @RequestBody는 생략할 수 없다.

💡@RequstParam, @ModelAttribute는 생략이 가능하다.
@Slf4j
@RestController
public class JsonController {
	
	@PostMapping("/v4/request-body-json")
  public String requestBodyJsonV4(Tutor tutor) { // @RequestBody 생략시 @ModelAttribute가 된다.

      Tutor requestBodyTutor = tutor;

      return "tutor.getName() = " + requestBodyTutor.getName() + " tutor.getAge() = " + requestBodyTutor.getAge();
  }
	
}

 

 

Postman

  • 생략하면 @ModelAttribute가 된다.
    • 요청 파라미터를 처리하도록 설정된다.
  • Request Header의 contentType은 꼭 application/json 이여야 한다.
    • 위 설정 정보를 기반으로 MessageConverter가 실행된다.

5. HttpEntity 사용

@RestController
public class JsonController {

  @PostMapping("/v5/request-body-json")
  public String requestBodyJsonV5(
  HttpEntity<Tutor> httpEntity
  ) {
      // 값을 꺼내서 사용해야한다!
      Tutor tutor = httpEntity.getBody();

      return "tutor.getName() = " + tutor.getName() + " tutor.getAge() = " + tutor.getAge();
  }
	
}

 

Postman

  • HttpEntity<Tutor>
    • Generic Type으로 Tutor가 지정되어 있기 때문에 해당 Class로 반환된다.

6. @ResponseBody

@Controller
public class JsonController {

  @ResponseBody // @RestController = @Controller + @ResponseBody
  @PostMapping("/v6/request-body-json")
  public Tutor requestJson(@RequestBody Tutor tutor) {
    return tutor;
  }
}

 

Postman

  • View를 조회하지 않고 Response Body에 Data를 입력해서 직접 반환한다.
  • 요청 뿐만이 아니라 응답에도 HttpMessageConverter가 동작한다.
    • MappingJackson2HttpMessageConverter 적용
    • 응답 객체인 Tutor가 JSON으로 변환되어 반환된다.
  • HttpEntity를 사용해도 된다.
  • 요약
    1. 요청 데이터는 @RequestBody를 사용해서 바인딩 하면 된다.
    2. @RequestBody 는 생략이 불가능하다.
      • @ModelAttribute가 적용되기 때문
    3. HttpMessageConverter 가 요청 응답 데이터를 모두 변환할 수 있다.
      • JSON은 MappingJackson2HttpMessageConverter 를 사용한다.
      • Request Header의 Content-Type은 application/json 이어야 한다.
        • Header로 어떤 Converter가 동작할지 판별한다.

HTTPMessageConverter

📚 Spring Framework에서 HTTP 요청과 응답을 변환하는 인터페이스이다. 클라이언트와 서버 간에 데이터를 주고받을 때, 요청 데이터를 자바 객체로 변환하거나 자바 객체를 응답 본문으로 변환하는 역할을 수행한다.
💡 MappingJackson2HttpMessageConverter는 JSON을 처리하는 대표적인 HTTPMessageConverter의 구현체이다.

 

  • HttpMessageConverter의 역할
    • 데이터를 Obejct로 변환한다. 대표적으로 JSON을 변환한다.

  • @RequestBody
    • 요청 데이터 + Request Header를 참고하여 Object로 변환한다.
      • HTTP Request Body(JSON Data) → Converter(Jackson) → Object
      • Reqeust Header → Content-Type : application/json(전달할 데이터 형식)
  • @ResponseBody
    • 응답 데이터 + Accept Header를 참고하여 원하는 데이터 형식으로 변환한다.
      • Object → Converter(Jackson) → HTTP Response Body(JSON Data)
      • Request Header → Accept : application/json(허용할 데이터 형식)

Server에서 Client로 Data를 전달하는 방법

📚 Server에서 Client로 Data를 전달하는 방법은 정적 리소스, View Template, HTTP Message Body 세가지 방법이 있다.

 

1. 정적 리소스

  • 정적인 HTML, CSS, JS, Image 등을 변경 없이 그대로 반환한다.

 

2. View Template

  • SSR(Server Side Rendering)을 사용할 때 View가 반환된다. (동적 뷰 반환)

 

3. HTTP Message Body

  • 응답 데이터를 직접 Message Body에 담아 반환한다.

정적 리소스

📚 웹 애플리케이션에서 변하지 않는 파일들을 의미한다. 예를 들어, HTML, CSS, JavaScript, 이미지 파일들(JPG, PNG, GIF) 등이 정적 리소스에 해당한다.

 

Spring Boot의 정적 리소스 경로

아래 경로들에 정적 리소스가 존재하면 서버에서 별도의 처리 없이 파일 그대로 반환된다.

  1. /static
  2. /public
  3. /META-INF/resources
  4. src/main/resources (스프링부트 기본 경로)
    1. /static

Spring Boot Directory 구조

 

src/main/resources/static/hello/world.html 디렉토리 구조라면

  • http://localhost:8080/hello/world.html URL로 리소스에 접근이 가능하다.

  • /static 대신 /public 혹은 /META-INF/resources 도 사용 가능하다.

View Template

📚 Spring에서는 Thymeleaf, JSP와 같은 템플릿 엔진을 사용해 View Template을 작성할 수 있고, View Template은 서버에서 데이터를 받아 이를 HTML 구조에 맞게 삽입한 후, 최종적으로 클라이언트에게 전송되는 HTML 문서로 변환하여 사용자에게 동적으로 생성된 웹 페이지를 제공한다.
📌 View Template
- View Template은 Model을 참고하여 HTML 등이 동적으로 만들어지고 Client에 응답된다.
- Spring Boot는 기본적으로 View Template 경로(src/main/resources/templates)를 설정한다.
- build.gradle에 Thymeleaf 의존성을 추가하면 ThymeleafViewResolver와 필요한 Spring Bean들이 자동으로 등록된다. 

 

기본 설정 예시(아래 내용을 자동으로 Spring Boot가 등록해준다.)

spring.thymeleaf.prefix=classpath:/templates/ 
spring.thymeleaf.suffix=.html // or jsp

 

1. @Controller의 응답으로 String을 반환하는 경우

@ResponseBody 가 없으면 View Resolver가 실행되며 View를 찾고 Rendering한다.

@Controller
public class ViewTemplateController {

    @RequestMapping("/response-view")
    public String responseView(Model model) {
      // key, value
      model.addAttribute("data", "sparta"); //키값은 data, value는 sparta 입력

      return "thymeleaf-view"; //논리적 뷰네임
  }
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Hello</title>
</head>
<body>
    <h1>Thymeleaf</h1>
    <h2 th:text="${data}"></h2>
</body>
</html>

 

ex) http://localhost:8080/response-view

 

@ResponseBody 가 있으면 HTTP Message Body에 return 문자열 값이 입력된다.

@Controller
public class ViewController {
	
	@ResponseBody // @RestController = @Controller + @ResponseBody
	@RequestMapping("/response-body")
	public String responseBody() {
		return "thymeleaf-view"; //뷰를 반환하지 않고 문자열을 반환함
	}
}

 

Postman

 

2. 반환 타입이 void인 경우

  • 잘 사용하지 않는다.
  • @Controller + (@ResponseBody, HttpServletResponse, OutputStream)과 같은 HTTP Message Body를 처리하는 파라미터가 없으면 RequestMapping URL을 참고하여 View Name으로 사용한다.
@Controller
public class ViewTemplateController {
	
	// thymeleaf-view.html 과 Mapping된다.
  @RequestMapping("/thymeleaf-view")
  public void responseViewV2(Model model) {
      model.addAttribute("data", "sparta");
  }
	
}

예시와 같은 경우에는 viewTemplate(viewName)을 RequestMapping URL 주소로 찾는다.


HTTP Message Body(응답)

📚 REST API를 만드는 경우 Server에서 Client로 HTML을 전달하는 방식이 아닌 HTTP Message Body에 직접 Data를 JSON 형식으로 담아 전달한다.
⛔ 정적 HTML, View Template 또한 HTTP Message Body에 담겨서 전달된다. 현재 설명하는 Response의 경우는 정적 HTML, View Template을 거치지 않고 직접 HTTP Response Message를 만들어 전달하는 경우를 말하는것.

 

HTTP Message Body

1. HttpServletResponse 사용 예시

@Controller
public class ResponseBodyController {
    
    @GetMapping("/v1/response-body")
    public void responseBodyV1(HttpServletResponse response) throws IOException {
		response.getWriter().write("data");
	}
}

 

Postman

  • Response Body에 data 라는 문자열이 입력되어 응답된다.
  • 기존 Servlet을 다룰 때 코드와 형태가 같다.

2. ResponseEntity<> 사용

@GetMapping("/v2/response-body")
public ResponseEntity<String> responseBodyV2() {
	return new ResponseEntity<>("data", HttpStatus.OK);
}

 

Postman

  • Response Body에 data라는 문자열과 HttpStatus.OK에 해당하는 상태 코드를 반환한다.
  • ResponseEntity는 HttpEntity 를 상속받았다.
    • HttpEntity는 HTTP Message의 Header, Body 모두 가지고 있다.

3. @ResponseBody(TEXT, JSON) 사용

@Data
@NoArgsConstructor // 기본 생성자
@AllArgsConstructor // 전체 필드를 인자로 가진 생성자
public class Tutor {

    private String name;
    private int age;

}
// TEXT 데이터 통신
@ResponseBody
@GetMapping("/v3/response-body-text")
public String responseBodyText() {
		
	return "data"; // HTTP Message Body에 "data"
}

 

Postman

// JSON 데이터 통신
@ResponseBody
@GetMapping("/v3/response-body-json")
public Tutor responseBodyJson() {
		
	Tutor tutor = new Tutor("wonuk", 100);
		
	return tutor; // HTTP Message Body에 Tutor Object -> JSON
}

Postman

  • View를 사용하는 것이 아닌 HTTP Message Converter를 통해 HTTP Message Body를 직접 입력할 수 있다. → ResponseEntity와 같음
  • @ResponseStatus 를 사용하여 상태 코드를 지정할 수 있다.
@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/v4/response-body")
public Tutor responseBodyV4() {
		
	Tutor tutor = new Tutor("wonuk", 100);
		
	return tutor; // OK 상태메세지 포함
}

Postman

  • 단, 응답 코드를 조건에 따라서 동적으로 변경할 수는 없다.
    ex) 1번의 경우 OK, 2번의 경우 Created 불가

4. ResponseEntity<Object>(JSON)

@ResponseBody
@GetMapping("/v5/response-body")
public ResponseEntity<Tutor> responseBody() {
		
	Tutor tutor = new Tutor("wonuk", 100);
	
	return new ResponseEntity<>(tutor, HttpStatus.OK);
}

Postman

  • ResponseEntity<>두 번째 파라미터에 Enum을 사용하여 상태 코드를 바꿀 수 있다.
  • HTTP Message Converter를 통하여 JSON 형태로 변환되어 반환된다.
  • 동적으로 응답 코드를 변경할 수 있다.
@ResponseBody
@GetMapping("/v5/response-body")
public ResponseEntity<Tutor> responseBody() {
		
	Tutor tutor = new Tutor("wonuk", 100);
	//상태 코드를 바꿀 수 있다.
	if (조건) {
		return new ResponseEntity<>(tutor, HttpStatus.OK);
	} else {
		return new ResponseEntity<>(tutor, HttpStatus.BAD_REQUEST);
	}
	
}
  • HttpEntity를 상속받았다.

정리

Client에서 Server로 Data를 전달하는 세가지 방법

 

1. GET - Query Param, Query String

ex) http://localhost:8080/tutor?name=wonuk&age=100

  • 사용하는 어노테이션
    • @RequestParam, @ModelAttribute

2. POST - HTML Form(x-www-form-urlencoded)

POST /form-data
    content-type: application/x-www-form-urlencoded
    
    key1=value1&key2=value2
  • 사용하는 어노테이션
    • @RequestParam, @ModelAttribute

 

3. HTTP Request Body

ex) 데이터(JSON, TEXT, XML 등)를 직접 HTTP Message Body에 담아서 사용한다.

  • 사용하는 어노테이션
    • @RequestBody

Server(Spring)에서 HTTP 응답을 Client에 전달하는 세가지 방법

    1. 정적 리소스
      • 정적인 HTML, CSS, JS, Image 등을 변경없이 그대로 반환한다.
    2. View Template
      • SSR(Server Side Rendering)을 사용할 때 View가 반환된다.
      • 사용하는 어노테이션
        • @Controller
    3. HTTP Message Body
      • 응답 데이터를 직접 Message Body에 담아 반환한다.
      • 사용하는 어노테이션
        • @ResponseBody, ResponseEntity<Object>

 

  • 요청
    • @RequestParam, @ModelAttribute, @RequestBody
  • 응답
    • 정적 리소스, View Template(@Controller), @ResponseBody, ResponseEntity<Object>

CRUD 실습


메모장 프로젝트