[Spring Boot] 맨날 헷갈리는 @ModelAttibute, @RequestParam , @RequestBody, @ResponseBody +@ 정리

황성현·2024년 2월 28일

Spring Boot

목록 보기
2/3

1. @RequestParam

  • 쿼리 파라미터로 날아오는 데이터를 받을 수 있게 해줌
  • 파라미터 이름으로 바인딩 되는 형식
  • EX) @RequestParam("username") String memberName에서 username으로 넘어오는 쿼리 파라미터를 String memberName에 바인딩 됨
  • 만약 괄호 안의 값이 없다면 변수명으로 바인딩 됨
  • EX) @RequestParam String memberName이라면 memberName으로 넘어오는 쿼리 파라미터를 바인딩.
  • Primitive타입, Map과 같은 컬렉션 등에 사용 가능

2. @ModelAttribute

  • @RequestParam과 동일하게 쿼리 파라미터로 날아오는 데이터를 받을 수 있음
  • 차이점은 필요한 객체를 생성하고 그 객체에 값을 넣어주는 것까지 가능함
  • 실행 순서 => 객체 생성 => 넘어온 쿼리 파라미터 이름으로 객체에 프로퍼티를 찾음 => 해당 객체의 프로퍼티의 setter를 호출해 파라미터 값을 바인딩함
  • ex) 파라미터 이름이 username이면 setUsername()메서드를 찾아 호출해 값을 바인딩
  • @RequestParam으로 쿼리 파라미터를 줄줄이 받는 것 보다, 적절한 객체를 생성해서 @ModelAttribute로 받으면 훨씬 깔끔한 코드를 작성할 수 있다.

3. @RequestBody

  • 위의 두 가지 어노테이션과 가장 큰 차이점은 어떤 대상을 타겟으로 하냐에 있다.
  • 위의 어노테이션들은 쿼리 파라미터를 대상으로 하고, @RequestBody의 경우 HTTP 메세지 바디에서 오는 JSON 형태의 데이터를 Java 객체에 매핑할 때 사용함.
  • 넘어오는 JSON형태의 데이터에 맞게 객체를 만들고 어노테이션을 붙여주면 객체에 값들이 맵핑되어 사용할 수 있음

4. @ResponseBody

  • Spring에서 Controller의 반환 타입이 String일때 기본적으로 View를 내려주는데, @ResponseBody 어노테이션을 사용하면 HTTP 메세지의 바디에 return 값을 넣어준다.
  • 객체가 반환타입일 때 @ResponseBody가 있으면 객체를 Http 메세지 바디에 넣어 알맞게 JSON형식으로 변환됨.

5. +@

  1. HttpEntity: HTTP header, body 정보를 편리하게 조회할 수 있으며, 응답에서도 메세지 바디 정보를 직접 반환하고 헤더 정보 포함 가능함(view 조회x)
  2. RequestEntity: HttpEntity를 상속받고 HttpMethod ,url 정보가 추가됨
  3. ResponseEntity: HttpEntity를 상속받고 HTTP 상태 코드 설정 가능
  4. @RequestBody를 사용하여 메세지 바디 내용(JSON)을 객체에 바로 맵핑하듯이, 위의 1,2들도 <>안에 객체 타입을 명시해주면 바로 객체에 바인딩됨.
  5. @ResponseBody를 사용하여 객체를 바로 Http 메세지 바디에 넣어주어 JSON으로 변환해주듯이 1,3도 가능함.

6.예시

@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {
String messageBody = httpEntity.getBody();
log.info("messageBody={}", messageBody);
return new HttpEntity<>("ok");
}
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
log.info("username={}, age={}", data.getUsername(), data.getAge());
return data;
}
@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) {
HelloData data = httpEntity.getBody();
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) {
HelloData data = httpEntity.getBody();
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
@GetMapping("/response-body-string-v2")
public ResponseEntity<String> responseBodyV2() {
return new ResponseEntity<>("ok", HttpStatus.OK);
}
@GetMapping("/response-body-json-v1")
public ResponseEntity<HelloData> responseBodyJsonV1() {
HelloData helloData = new HelloData();
helloData.setUsername("userA");
helloData.setAge(20);
return new ResponseEntity<>(helloData, HttpStatus.OK);
}

7. 원리

  • 모든 것은 Spring이 Http Message Converter를 사용하여 가능하다.
  • HTTP 메시지 컨버터는 HTTP 요청, HTTP 응답 둘 다 사용된다.
    canRead() , canWrite() : 메시지 컨버터가 해당 클래스, 미디어타입을 지원하는지 체크
    read() , write() : 메시지 컨버터를 통해서 메시지를 읽고 쓰는 기능
  • ByteArrayHttpMessageConverter : byte[] 데이터를 처리한다.
    클래스 타입: byte[] , 미디어타입: / ,
    요청 예) @RequestBody byte[] data
    응답 예) @ResponseBody return byte[] 쓰기 미디어타입 application/octet-stream
    StringHttpMessageConverter : String 문자로 데이터를 처리한다.
  • 클래스 타입: String , 미디어타입: /
    요청 예) @RequestBody String data
    응답 예) @ResponseBody return "ok" 쓰기 미디어타입 text/plain
  • MappingJackson2HttpMessageConverter : application/json
    클래스 타입: 객체 또는 HashMap , 미디어타입 application/json 관련
    요청 예) @RequestBody HelloData data
    응답 예) @ResponseBody return helloData 쓰기 미디어타입 application/json 관련

실제 요청이 왔을때

  • HTTP 요청 데이터 읽기
    HTTP 요청이 오고, 컨트롤러에서 @RequestBody , HttpEntity 파라미터를 사용한다.
    메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 canRead() 를 호출한다.
    대상 클래스 타입을 지원하는가?
    예) @RequestBody 의 대상 클래스 ( byte[] , String , HelloData )
    HTTP 요청의 Content-Type 미디어 타입을 지원하는가?
    예) text/plain , application/json , /
    canRead() 조건을 만족하면 read() 를 호출해서 객체 생성하고, 반환한다.
  • HTTP 응답 데이터 생성
    컨트롤러에서 @ResponseBody , HttpEntity 로 값이 반환된다.
    메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해 canWrite() 를 호출한다.
    대상 클래스 타입을 지원하는가.
    예) return의 대상 클래스 ( byte[] , String , HelloData )
    HTTP 요청의 Accept 미디어 타입을 지원하는가.(더 정확히는 @RequestMapping 의 produces )
    예) text/plain , application/json , /
    canWrite() 조건을 만족하면 write() 를 호출해서 HTTP 응답 메시지 바디에 데이터를 생성한다.

8. 곁다리 Pageable

    @GetMapping("/v2/members")
    public Page<MemberTeamDto> searchMemberV2(@ModelAttribute MemberSearchCond cond, Pageable pageable){
        return memberRepository.searchPageComplex(cond,pageable);
    }

해당 코드를 보면 MemberSearchCond cond는 쿼리 파라미터 형식으로 넘어오는 데이터들
(?memberName="memberA"&teamName="teamA")에 대해 @ModelAttribute를 사용하여 객체를 생성하고 바인딩해준 후 사용하는데, Pageable은 인터페이스 형식으로 Spring Data JPA에서 PageRequest(Pageable를 구현한)객체를 만들어서 size나 page등 쿼리 파라미터로 넘어오는 것들을 넣어준다. 그렇다면 @ModelAttribute를 생략해준 것인가? 싶어 붙여보았으나 에러가 났고, 그 이유는 Spring이 자동으로 설정해주니 어노테이션이 불필요하다는 것이였다,,, 나는 @ModelAttribute를 생략해준 줄 알았는데 그게 아니였다..

0개의 댓글