[Spring] 기초 Spring 5주차-1

Yuri·2025년 1월 22일

Spring

목록 보기
6/21

🧑‍🏫 목표

  • HTTP 요청 및 응답 데이터의 처리 방식을 학습한다.
  • 실습을 통해 CRUD 기능을 포함한 프로젝트를 개발하며, 실제 애플리케이션에서 HTTP 통신과 데이터 처리를 적용하는 방법을 익힌다.

Client에서 Server로 데이터를 전달하는 방법 3가지

1. GET - Query Parameter

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);
        // 직접적으로 응답 데이터에 접근해서 응답 데이터를 만들어낼 수 있다. @Controller지만 데이터로 응답 
        response.getWriter().write("success");
    }
}

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

  • HTTP Request Body에 쿼리 파라미터 형태로 전달하는 방법
  • HTTP Request (HTML Form으로 전달한 값)
POST /form-data
content-type: application/x-www-form-urlencoded

key1=value1&key2=value2
  • HttpServletRequest 사용
@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");
    }

데이터의 생김새가 똑같기 때문에 getParameter로 값을 꺼내올 수 있다.

3. HTTP Request Body

  • 데이터(JSON, TEXT, XML 등)를 직접 HTTP Message Body에 담아서 전달한다.
  • 주로 @RestController에서 사용하며, 대부분 JSON 형식으로 데이터를 전달한다.
    • POST, PUT, PATCH Method에서 사용한다.
    • GET, DELETE Method는 Body에 데이터를 담는것을 권장하지 않는다.
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Board {

    private String title;
    private String content;
}
@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.class);

        log.info("board.getTitle()={}, board.getContent()={}", board.getTitle(), board.getContent());

        response.getWriter().write("success");

    }

}
  • Content-Type="application/json"으로 보내지만 사실 json 형태의 문자열로 보내는 것과 같다.
    request.getInputStream()을 통해서 문자열로 그 JSON 데이터를 꺼낸다.

💡 JSON을 Java 객체로 변환하려면 Jackson과 같은 라이브러리를 사용해야 한다. Spring Boot는 기본적으로 Jackson 라이브러리의 ObjectMapper를 제공하며, starter-web에 포함되어 있습니다.

Spring 요청 데이터

@annotation 기반의 Spring에서는 사실 HttpServletRequest 보다 편리하게 사용하는 방법이 있다.

@RequestParam

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

request.getParameter("key1");
request.getInputStream();

⬇️ 이렇게 요청 파라미터 값을 받아올 수 있다.

@Requestparam("key1") String key1;
  • 예시코드
@Slf4j
@Controller
public class RequestParamControllerV2 {

	@ResponseBody
	@GetMapping("/v1/request-param")
	public String requestParamV1 (
					@RequestParam("name") String userName,
					@RequestParam("age") int userAge													
	) {
		// logic
		log.info("name={}", userName);
    log.info("age={}", userAge);
		return "success";
	}

}
  • @RequestParam
    • 파라미터 이름으로 바인딩한다.
  • @RequestParam(”속성값”)
    • 속성값이 파라미터 이름으로 매핑된다.
  • “속성값”과 변수명이 같으면 생략이 가능하다.
  1. 어노테이션, 속성값 모두 생략
  • 생략하면 @RequestParam(required=false) 필수 여부 속성이 default로 설정된다.
    → param 값이 없어도 클라이언트 오류가 아니다.
  • 단, 요청 파라미터와 이름이 완전히 같아야 한다.
  • 단순 타입(int, String, Integer 등)이어야 한다.

어노테이션 생략 방식은 권장하지 않습니다. 명시적으로 표시되어있지 않으면 팀의 협의가 있지 않는 경우 다른 개발자들에게 혼동을 주게 됩니다. 최소 @RequestParam String name 속성 값 생략 형태를 쓰면 됩니다.

  1. 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이다.
    • requestParam에 required가 true 인 name 의 값이 없다면 400 BadRequest(클라이언트 에러)

🤨 requestParam에 required가 false인 값만 빼고 요청했을때 500 Internal Server Error(서버 에러)?

값을 빼고 요청한다는 것은 해당 키의 value가 null 인것과 같다.
→ 기본타입인 int는 null을 받아들일 수 없기 때문에
IllegalStateException이 발생

➕ param에 키값만 있고 value가 없는 경우
이름(name)에 value 없이 요청

속성으로 required=true 제한하였지만 빈문자열 ""은 null과 다르기 때문에 통과가 되어버린다.

  1. default 속성 적용
    파라미터의 기본 값을 설정한다.
	@ResponseBody
    @GetMapping("/v5/request-param")
    public String requestParamV5(
            @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";
    }

클라이언트 요청 파라미터에 값이 없다면 서버에서 미리 정의한 default 값이 들어간다.

  • 주의! defaultValue 속성을 설정하게 되면 “” 빈 문자열의 경우에도 기본 값이 설정된다.
  1. Map 사용
    Parameter를 Map형태로 조회가 가능하다.
  • Map 형태(key=value)로 조회가 가능하다
  • MultiValueMap 형태(key=[value1, value2])로 조회가 가능하다.
    → 값이 배열 형태로 전달된다.

✏️ MultiValueMap: 하나의 key에 여러개의 value가 존재

@ModelAttribute

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

  • HTML FORM Request
POST /v1/tutor
content-type: application/x-www-form-urlencoded

name=tutor&age=100
  • Tutor.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Tutor {

    private String name;
    private int age;

}

@Data 는 테스트 용도로만 사용한다.

  • ModelAttributeController.java
@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 는 해당 과정을 자동화 한다.

▶︎ ModelAttribute 적용

	@ResponseBody
    @PostMapping("/v2/tutor")
    public String modelAttributeV2(
            @ModelAttribute Tutor tutor
    ) {

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

        return "tutor name = " + name + " age = " + age;
    }
  • @ModelAttirubte 동작 순서

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

  • 파라미터의 타입이 다른 경우
    age의 값을 숫자가 아닌 문자열로 전달

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

@ModelAttirubte 생략
@ModelAttribute와 @RequestParam은 모두 생략이 가능하다.

  • Spring에서는 @RequestParam이나 @ModelAttribute가 생략되면
    • String, int, Integer 와 같은 기본 타입은 @RequestParam과 Mapping한다.
    • 나머지 경우들(객체)은 모두 @ModelAttribute 와 Mapping한다.

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 등을 데이터 형식으로 사용한다.

  • 요청하는 데이터는 Message Body 안에 담겨있다.
  • Server에서 Request로 전달받은 Data를 처리하기 위해서 바인딩 해야 한다.
    ex) JSON → Object

TEXT

HTTP Request Body에 Data가 전송되는 경우 HttpMessageConverter를 통해 바인딩된다.

💡 현대에는 Restful API를 주로 사용하고 있어서 대부분의 경우 JSON 형식으로 통신한다.

  1. HttpServletRequest
    기본적인 HttpServletRequest 예시
  • request.getInputStream();
@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);

    }
}    

inputStream으로 객체(Object)로 만든 텍스트는
StringUtils.copyToString(inputStream, StandardCharsets.UTF_8); 로 UTF_8로 인코딩된 형태의 문자열로 반환한다.

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

  • Spring의 HttpMessageConverter 덕분에 간편하게 Request Data에 접근할 수 있다.
    1. HttpEntity를 사용하면 HttpMessageConverter가 동작하여 자동으로 매핑된다.
    2. 요청 뿐만이 아닌 응답까지 HttpEntity 하나만으로 사용이 가능해진다.

💡 Converter는 어떤 뭔가를 다른 뭔가로 바꿔주는(Convert) 장치를 말한다.
🌱 Spring 에서 Converter의 역할은 json, text와 같은 데이터를 Object와 같은 객체로 바꿔주는 역할을 하게 된다.

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 상태 코드 설정이 가능하다.
	@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, 상태코드
    }

  • 상태코드가 201 CREATED로 반환되었다.
  • 상속받은 RequestEntity, ResponseEntity를 사용해도 Data를 httpEntity에서 꺼내어(entity.getData()) 사용해야 한다.

@RequestBody, @ResponseBody

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

	@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;
    }
  • @RequestBody

    • 요청 메세지 Body Data를 쉽게 조회할 수 있다.
  • @RequestHeader

    • 요청 헤더 정보 조회
  • @ResponseBody

    • 응답 메세지 바디에 값을 쉽게 담아서 전달할 수 있도록 해준다.
    • View가 아닌 데이터를 반환한다.
  • 요약

    1. 요청 파라미터, HTML Form Data에 접근하는 경우
      • @RequestParam, @ModelAttribute 를 사용한다.
    2. Http Message Body에 접근하는 경우
      • @RequestBody를 사용한다. (JSON, XML, TEXT)

JSON

Json은 @RestController 에서 가장 많이 사용되는 데이터 형식이다. 현재 대부분의 API는 Request, Response 모두 JSON 형태로 통신한다.

@RestController
public class JsonController {

	// jackson 라이브러리
    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);
    }
}    
  • HttpServletRequest를 사용하여 HTTP Message Body 데이터를 Read하여 문자로 변환한다.
  • 문자로 만들어진 JSON을 Jackson 라이브러리의 objectMapper를 사용하여 Object로 변환

@RequestBody 사용

요청받은 JSON 을 문자열로 받는다.

	@PostMapping("/v2/request-body-json")
    public String requesBodyJsonV2(@RequestBody String requestBody) throws IOException {

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

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

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

@ObjectMapper 제거

	@PostMapping("/v3/request-body-json")
    public String requestBodyJsonV3(@RequestBody Tutor tutor) {

        Tutor requestBodyTutor = tutor;

        return "tutor = " + requestBodyTutor;
    }

객체로 직접 json 데이터의 값을 매핑하여 사용
👉 위 Controller가 동작하는 이유는 무엇인가요?

  • @RequestBody
    • @RequestBody 어노테이션을 사용하면 Object를 Mapping할 수 있다.
    • HttpEntity<>, @RequestBody를 사용하면 HTTPMessageConverter가 Request Body의 Data를 개발자가 원하는 String이나 Object로 변환해준다.
    • JSON to Object의 Mapping 또한 가능하다.
      • MappingJackson2HttpMessageConverter 의 역할
      • 쉽게 설명하면 HTTP Message Converter가 ObjectMapper를 대신 실행한다.


MappingJackson2HttpMessageConverter

  • 다양한 타입의 요청·응답 데이터를 자바에서 사용할 수 있는 String이나 Object로 변환(Convert)해주는 역할을 하는 Converter들의 구현체 중 하나
  • jackson 라이브러리에 포함되어 있고 기본적으로 springframework에서 제공한다 (package 참고)

@RequestBody는 생략할 수 없다.

생략하게 되면 메서드는 해당 인자를 (기본 타입이 아니기 때문에) @ModelAttribute로 인식한다.

HttpEntity 사용

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

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

번거롭게 httpEntity.getBody();로 값을 꺼내서 사용해야 한다.

@ResponseBody

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

Tutor라는 객체를 반환했는데 Json 형태로 변환되어 클라이언트에 응답하였다.

  • View를 조회하지 않고 Response Body에 Data를 입력해서 직접 반환한다.
  • 요청 뿐만이 아니라 응답에도 HttpMessageConverter가 동작한다.
    • MappingJackson2HttpMessageConverter 적용
    • 응답 객체인 Tutor가 JSON으로 변환되어 반환된다.

요약

  1. 요청 데이터는 @RequestBody를 사용해서 바인딩 하면 된다.
  2. @RequestBody 는 생략이 불가능하다.
    • @ModelAttribute가 적용되기 때문
  3. HttpMessageConverter 가 요청·응답 데이터를 모두 변환할 수 있다.
    • JSON은 MappingJackson2HttpMessageConverter 를 사용한다.
    • Request Header의 Content-Type은 application/json 이어야 한다.
      • Header로 어떤 Converter가 동작할지 판별한다.

HttpMessageConverter 인터페이스

Spring Framework에서 HTTP 요청과 응답을 변환하는 인터페이스이다. 클라이언트와 서버 간에 데이터를 주고받을 때, 요청 데이터를 자바 객체로 변환하거나 자바 객체를 응답 본문으로 변환하는 역할을 수행한다.

  • 역할
    데이터를 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(허용할 데이터 형식)
profile
안녕하세요 :)

0개의 댓글