suyeonme

[Spring] Controller의 요청/응답 관련 애노테이션 본문

프로그래밍👩🏻‍💻/Spring

[Spring] Controller의 요청/응답 관련 애노테이션

suyeonme 2022. 6. 5. 15:21
Controller와 관련있는 자주 사용할 것 같은 애노테이션을 정리했다.

@RequestMapping

Controller에서 들어온 http request를 method와 매핑(mapping)하게 위해 사용한다.

  • @RequestMapping(value = "URI", method = RequestMethod.GET)
  • @RequestMapping({ "/members", "member_list" })와 같이 복수개의 URL을 등록할 수 있다. 
  • method를 지정하지 않으면 모든 요청에 대해서 호출된다.
@RestController
public class MemberController {

    @RequestMapping(value = "/member", method = RequestMethod.GET)
    public String getMember(...) {}

    @RequestMapping(value = "/member", method = RequestMethod.POST)
    public String postMember(...) {}

    @RequestMapping(value = "/member", method = RequestMethod.PUT)
    public String putMember(...) {}

    @RequestMapping(value = "/member", method = RequestMethod.DELETE)
    public String deleteMember(...) {}
}

@GetMapping, @PostMapping, @PutMapping, @DeleteMapping

위처럼 작성해도 문제 없지만, 아래와 같이 더 간결하게 작성할 수 있다.

공통된 URI를 RequestMapping의 value로 작성하고, 각 method별로 @GetMapping과 같이 작성한다.

  • @GetMapping의 경우 내부적으로는 @RequestMapping(method = RequestMethod.GET)으로, 즉 @RequestMapping의 method별로 작성이 되어있다. 
@RestController
@RequestMapping(value = "/member")
public class MemberController {

    @GetMapping()
    public String getMember(...) {}

    @PostMapping()
    public String postMember(...) {}

    @PutMapping()
    public String putMember(...) {}

    @DeleteMapping()
    public String deleteMember(...) {}
}

Params, Headers

params나 headers를 사용하여 쿼리 파라미터 및 특정 헤더를 포함한 URL에 있는 경우에만 API를 호출할 수 있다.

  • params="mode"
  • params="!mode"
  • params="mode=debug"
  • params="mode!=debug"
  • params={"mode=debug","data=good"}
// /members?mode=debug 정상호출
// /members 호출되지 않음
@GetMapping(value = "/members", params = "mode=debug")
public String getMember(...) {}

@GetMapping(value = "/members", headers = "mode=debug")
public String getMember(...) {}

Consumes, Produces

consumes는 요청 Header의 Content-type을 매핑이 된다.

  • consumes="text/plain"
  • consumes={"text/plain", "application/*"}
  • consumes=MediaType.TEXT_PLAIN_VALUE

produces는 요청 Header의 Accept의 Content-Type과 매핑이 된다. 맞지 않으면 406 상태코드를 반환한다.

@PostMapping(value = "/members/create", consumes = "application/json")
public String createMember(...) {}

// Header의 Accept가 "text/html"인 경우 정상적으로 동작.
@PostMapping(value = "/members/create", produces = "text/html")
public String createMember(...) {}

@RestController

@Controller와 @ResponseBody를 함께 적용하는 어노테이션이다. 반환값으로 뷰 템플릿을 사용하지 않고 HTTP 메세지 바디에 값을 바로 입력한다. 즉 Rest API르 만들 때 사용하는 컨트롤러이다.

  • @Controller의 경우, 반환값이 String 타입이면 View의 이름으로 인식되어 View를 찾은 뒤 View를 렌더링한다.
  • 클래스 레벨이 @ResponseBody를 적용하면 클래스의 모든 메소드에 적용된다.
@RestController
public class MemberController {
  @GetMapping("/members")
  public String getMember() {
    return "ok";  
  }
}

// 위와 동일
@Controller
@ResponseBody
public class MemberController {
  @GetMapping("/members")
  public String getMember() {
    return "ok";  
  }
}

@JsonProperty, @JsonNaming

보편적으로 클라이언트에서는 snake case(e.g. phone_number)를 사용하고 서버에서는 camel case(e.g phoneNumber)를 사용한다. 이 때 클라이언트에서 보낸 JSON의 key와 서버의 entity key와 일치하지 않을 수 있다. 이 때 @JsonProperty를 사용하여 key를 매핑(mapping)할 수 있다.

해당 어노테이션을 사용하기 위해서는 com.fasterxml.jackson.core 패키지(Jackson)를 dependency에 추가해야한다.

  • @JsonProperty: 객체를 JSON 형식으로 변환할 때 Key의 이름을 매핑(mapping)한다.
  • @JsonNaming: 클래스의 전체 field에 대해서 key의 이름을 매핑(mapping)한다.
// JSON 데이터
{
  phone_number: "010-000-0000",
  name: "Suyeon"
}

// Entity
@Data
public class Member {
  @JsonProperty("phone_number")
  private string phoneNumber;
  private String name;
}

// Controller
@RestController
@RequestMapping("/member")
public class MemberController {
  public void post postMember(@RequestBody Member member) {
    System.out.println(member);
  }
}

위와 같이 @JsonProperty를 사용하여 field를 매핑할 수 있지만, 매핑이 필요한 field가 많은 경우에는 일일히 설정하는 것은 매우 번거롭다. 이러한 경우 @JsonNaming를 클래스에 붙여서 전체 필드에 적용할 수 있다.

// Entity
@Data
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
public class Member {
  private string phoneNumber;
  private String name;
}

@RequestBody, @ResponseBody

클라이언트에서 POST 형식으로 데이터를 메세지 Body 에 넣어서 보내거나(content-type: application/json) JSON데이터 또는 스트링등을 Response Body에 담아서 클라이언트로 보낼 때 사용한다.

  • @RequestBody: Request의 body를 자바객체로 변환한다.
  • @ResponseBody: 자바 객체를 Response의 body로 변환한다. @RestController와 동일한 기능이다.
  • 헤더 정보 조회는 HttpEntity 또는 @RequestHeader를 사용한다.
  • @RequestBody는 생략할 수 없다. (default값인 @ModelAttribute가 적용됨)

http 메세지 컨버터

내부적으로 http 메세지 컨버터가 메세지 바디를 변환한다.

  • @RequestBody: JSON요청 -> HTTP 메세지 컨버터 -> 객체
  • @ResponseBody: 객체 -> HTTP 메세지 컨버터 -> JSON응답
@Controller
public class MemberController {
  @PostMapping("/member")
  @ResponseBody
  public Member createMember(@RequestBody Member member) {
    return member;
  }
}

HttpEntity

Http header, body 정보를 조회한다. 응답에도 사용할 수 있다. @RequestBody, @ResponseBody로 대체할 수 있다.

  • POST 형식의 메세지 바디의 정보를 조회하는 경우 사용한다.
  • RequestEntity: http method, url 정보가 추가되며 요청에 사용한다. (httpEntity 상속)
  • ResponseEntity: http 상태 코드를 설정할 수 있으며 응답에 사용한다. (httpEntity 상속) 
@PostMapping("/request-body-string")
public HttpEntity<String> requestBodyString(HttpEntity<String> httpEntity) throws IOException {
  String messageBody = httpEntity.getBody();
  log.info("messageBody={}", messageBody);
  return new HttpEntity<>("ok");
}

 

@PathVariable

Rest API에서 URI의 변수를 핸들링할 수 있다. @PathVariable을 사용하여 매칭되는 값을 편하게 조회할 수 있다.

예를 들어, URI가 http://localhost:8080/api/user/1인 경우 변수는 1에 해당된다.

@RestController
public class MemberController { 
    @GetMapping("/member/{name}")
    public String findByName(@PathVariable("name") String name) {
    	log.info("name={}", name);
        return "Name: " + name;
    }
    
    @GetMapping("/member/{id}/{name}")
	public String findByNameAndId(@PathVariable("id") String id, @PathVariable("name") String name) {
    	return "ID: " + id + ", name: " + name;
    }
    
}

위 코드처럼 path variable과 변수명이 동일한 경우 아래와 같이 간단하게 작성할 수 있다.

@RestController
public class MemberController { 
    @GetMapping("/member/{name}")
    public String findByName(@PathVariable String name) {
    	log.info("name={}", name);
        return "Name: " + name;
    }
}

@RequestParam

Request Parameter 조회 및 핸들링하는 경우에 사용한다. 

  • GET 방식의 쿼리 파라미터
  • POST 방식의 HTML Form

HTTP 쿼리 파라미터 이름이 변수명과 동일한 경우 @RequestParam String memberName와 같이 간략하게 사용할 수 있다.

Required

default값이 true이며 해당 파라미터가 포함되지 않은 경우 상태코드 400을 반환한다. false로 설정시 해당 파라미터를 필수로 포함하지 않아도 된다.

@Controller
public class RequestParamController {
  @ResponseBody
  @RequestMapping("/request-param")
  public String requestParam(
           @RequestParam(required = false) String memberName // @RequestParam("memberName") String memberName
           @RequestParam int memberAge) {  
    return "ok";
  }
}

// @RequestParam("username") String memberName
// @RequestParam("age") int memberAge

DefaultValue

쿼리 파라미터가 포함되지 않은 경우 기본값을 지정한다. 쿼리 파라미터가 빈문자인 경우(e.g. /members?username=)에도 적용된다.

@Controller
public class RequestParamController {
  @ResponseBody
  @RequestMapping("/request-param")
  public String requestParam(@RequestParam(required = true, defaultValue = "suyeon") String memberName) {  
    return "ok";
  }
}

Map, MultiValueMap

모든 쿼리 파라미터를 조회하고 싶은 경우, Map을 사용하면 된다.

@Controller
public class RequestParamController {
  @ResponseBody
  @RequestMapping("/request-param")
  public String requestParam(@RequestParam Map<String, Object> paramMap) {  
    log.info("username={}, age={}", paramMap.get("username"), paramMap.get("age"));
    return "ok";
  }
}

@ModelAttribute

객체를 생성하고 요청 파라미터의 이름으로 해당 객체의 프로퍼티를 찾는다. 그리고 프로퍼티의 setter를 호출하여 파라미터의 값을 객체에 추가한다. 쿼리 파라미터의 값과 객체 프로퍼티의 타입이 다르면 BindException이 발생한다.

Request Parameter 조회 및 핸들링하는 경우에 사용한다.

  • GET 방식의 쿼리 파라미터
  • POST 방식의 HTML Form
@Data // 롬복의 어노테이션
public class HelloData {
  private String username;
  private int age;
}

@Controller
public class RequestParamController {
  @ResponseBody
  @RequestMapping("/model-attribute")
  public String requestParam(@ModelAttribute HelloData helloData) {  
    return "ok";
  }
}

@ModalAttibute를 사용하지 않는다면 아래와 같이 직접 객체를 생성한 뒤 값을 추가해야한다.

@Controller
public class RequestParamController {
  @ResponseBody
  @RequestMapping("/model-attribute")
  public String requestParam(@RequestParam String username, @RequestParam int age) {  
  	HelloData helloData = new HelloData();
    helloData.setUsername(username);
    helloData.setAge(age);
    return "ok";
  }
}

@ResponseEntity

응답 상태코드를 반환한다.

  • HttpStatus
@GetMapping
public ResponseEntity<User> getUsers() {
  //...
  return new ResponseEntity<User>(user, HttpStatus.OK);
}
Comments