오늘 소개할 내용은 Spring의 Controller에서 사용할 수 있는 파라미터 타입들을 알아보도록 하자. .
Spring에서 Controller로 받을 수 있는 파라미터들이 다양한 타입들이 존재하는데 이는 Object 혹은 어노테이션등으로 선언하면 자동으로 Spring이 그에 맞게 변환해주고 있다. 다 알아볼 수 는 없겠지만 자주 사용하는 부분 위주로 최대한 많이 살펴보도록 하자.

필자의 경우에는 Controller에서 servlet API를 거의 사용하지 않는다. 예를들어 HttpServletRequest, HttpServletResponse 이와 같은 servlet과 관련있는 API는 되도록이면 작성하지 않으려고 노력한다. 그 이유는 몇가지 이유가 있지만 나중에 혹시 기회가 되면 다뤄보기로 하고 그렇다고 해서 사용하지 말라는 것은 아니다. 필요하다면 사용해도 되겠지만 특별한 이유가 없다면 Spring에서 제공해주는 파라미터 타입으로도 충분히 개발할 수 있다고 생각한다. 실제로 파라미터 타입 마저 커스텀하게 만들 수 있으니 servlet API를 직접적으로 사용할 필요는 거의 없을 듯 하다.

Servlet

일단 Servlet과 관련된 파라미터 타입들을 알아보도록 하자.

HttpServletRequest, HttpServletResponse

아까 위에서 말했다 시피 필자는 거의 사용하지 않지만 그래도 원한다면 위와 같은 Servlet API를 사용해도 된다.

@RestController
public class SampleController {

  @GetMapping("/sample")
  public String hello(HttpServletRequest request, HttpServletResponse response){
    return "hello";
  }
}

위의 HttpServletRequest, HttpServletResponse 상위 타입인 ServletRequest, ServletResponse을 사용해도 된다.

WebRequest

Spring에서 제공해주는 WebRequest은 HttpServletRequest의 요청정보를 거의 대부분 그대로 갖고 있는 API로 Servlet API의 종속적이지 않은 타입이다. 원래는 서블릿과 포틀릿 환경 양쪽 모두 사용할 수 있도록 만들어진 타입이다.

@RestController
public class SampleController {

  @GetMapping("/sample")
  public String hello(WebRequest request){
    return "hello";
  }
}

MultipartRequest

파일 업로드시 사용할 수 있는 타입이다.

@RestController
public class SampleController {

  @PostMapping("/sample")
  public String hello(MultipartRequest request){
    return "hello";
  }
}

뿐만 아니라 MultipartRequest 인터페이스이 하위 타입인 MultipartHttpServletRequest 사용해도 된다.

HttpSession

HttpSession은 HttpServletRequest를 통해서도 가져올 수 있지만 Session만 사용하는 경우라면 HttpSession을 이용하면 된다.

@RestController
public class SampleController {

  @GetMapping("/sample")
  public String hello(HttpSession session){
    return "hello";
  }
}

TimeZone, ZoneId

요청하는 해당국의 시간 정보를 받을 수 있다. TimeZone는 기존의 날짜 관련 API이고 ZoneId의 경우에는 java8 부터 나온 API이므로 java8을 사용하지 못한다면 TimeZone 을 이용해야 된다.

@RestController
public class SampleController {

  @GetMapping("/sample")
  public String hello(ZoneId zoneId, TimeZone timeZone) {
    return "hello";
  }
}

그 외

위의 설명한 이외에도 Principal, InputStream, Reader, HttpMethod, Locale, OutputStream, Writer 등을 사용할 수 있다. 아주 다양하게 사용할 수 있으니 한번씩 살펴봐도 괜찮을 듯 싶다.

어노테이션

해당 어노테이션을 선언하면 그에 맞게 적당한 타입으로 변환시켜 준다.

PathVariable

PathVariable는 파라미터를 URL 경로에 포함시키는 방법이다. 예를들어 id로 해당하는 정보를 요청할 때 우리는 다음과 같이 작성할 수 있다.

/account?id=100

검색을 하는 것이 아니라면 id 경우에는 url에 포함시키는 것도 좋은 방법이라고 생각한다. 특히 Rest API를 설계한다면 더욱 좋은 방법이라고 생각한다. 그래서 우리는 다음과 같이 변경할 수 있다.

/account/100

이렇게 id를 url 경로에 포함시키면 좀 더 이해 하기 쉽고 보기 좋은 url을 만들 수 있다. 이때 사용할 수 있는 어노테이션이 바로 PathVariable 어노테이션이다.

@RestController
public class SampleController {

  @GetMapping("/sample/{id}")
  public String hello(@PathVariable Long id) {
    return "hello";
  }
}

{ }를 이용해 매핑되는 url 경로에 작성해주면 해당 @PathVariable 어노테이션이 작성된 파라미터에 자동으로 매핑된다. 위의 경우에는 {id}와 변수 id가 같으므로 추가적인 작업이 필요 없지만 만약 다르다면 아래와 같이 작성해야 된다.

@RestController
public class SampleController {

  @GetMapping("/sample/{id}")
  public String hello(@PathVariable("id") Long name) {
    return "hello";
  }
}

@RequestParam

요청 파라미터를 넣어주는 어노테이션이다. @PathVariable 마찬가지로 변수 앞에 작성해주면 된다. 만약 name이라는 파라미터를 넘겨 받는다면 아래와 같이 작성해 주면 된다.

@RestController
public class SampleController {

  @GetMapping("/sample")
  public String hello(@RequestParam String name) {
    return "hello";
  }
}

그럼 우리는 다음과 같이 요청 할 수 있다.

/sample?name=wonwoo

만약 name이라는 파라미터가 넘어오지 않는다면 MissingServletRequestParameterException 이라는 에러가 떨어진다. 만약 필수 값이 아니라면 @RequestParam 속성에 required를 false로 작성해 주면 된다.
이 또한 마찬가지로 @RequestParam의 변수명과 요청파라미터가 같을 경우에는 따로 작성해줄 필요는 없지만 만약 다르다면 아래와 같이 추가적으로 작성해줘야 한다.

@GetMapping("/sample")
public String hello(@RequestParam("id") String name) {
  return "hello";
}

또한 파라미터가 여러개일 경우에는 Map으로도 받을 수 있다.

@RestController
public class SampleController {

  @GetMapping("/sample")
  public String hello(@RequestParam Map<String, String> parameters) {
    return "hello";
  }
}

필자가 말한 위의 어노테이션은 모두 생략가능하다. 하지만 생략이 가능하다고해서 생략하는건 코드를 읽는데 불편할 수 있으니 작성하는 것을 권장한다. 또한 아래 설명할 @ModelAttribute랑 헷갈릴 수 있으니 작성하는 것을 권장한다.

@ModelAttribute

ModelAttribute는 메서드에도 작성할 수 있고 파라미터에도 작성할 수 있다. 두개의 동작이 조금 다른데 여기서는 파라미터로 받는 부분만 설명할 예정이다.

이 어노테이션은 @RequestParam 파라미터와는 조금 다른데 RequestParam은 파라미터와 1:1로 받는 반면 ModelAttribute는 도메인 모델이나 혹은 DTO 같은 모델을 받는 타입이다.

@RestController
public class SampleController {

  @GetMapping("/sample")
  public String hello(@ModelAttribute Account account) {
    return "hello";
  }
}

이 또한 @RequestParam과 동일하게 어노테이션을 생략할 수 있다. 하지만 필자는 작성하는 것을 권장한다. 이유는 위와 동일하다. 그러면 Spring은 어떻게 두개의 어노테이션을 생략하는데도 무엇을 보고 RequestParam 인지 ModelAttribute 인지를 판단할까? 그건 단순하게 String, int, long 등의 단순한 타입이라면 RequestParam이라고 판단하고 그게 아니라 모델 경우에는 @ModelAttribute로 판단한다.

@RequestBody

이 어노테이션은 http 본문자체를 읽는 부분을 모델로 변환시켜주는 어노테이션이다. 실제로 Rest API 만들때는 자주 사용하는 어노테이션이다.

@RestController
public class SampleController {

  @PostMapping("/sample")
  public String hello(@RequestBody Account account) {
    return "hello";
  }
}

위와 같이 xml 혹은 json으로 본문을 보낸다면 위와 같이 @RequestBody 어노테이션을 이용하면 된다. 하지만 이 어노테이션은 생략하면 안된다. 그 이유는 이것이 모델로 받는 것인지 Message(본문)으로 받는 것인지 알 수가 없어 아까 위에서 말했다시피 모델로 간주해 실제로 ModelAttribute로 변환하려해 빈 모델이 담겨온다.

AuthenticationPrincipal

이건 Spring mvc에 있는 것은 아니고 security에 있는 어노테이션이다. 인증한 정보를 가져오는 어노테이션이다. Authentication의 Principal에 있는 정보를 가져오는 것이기 때문에 해당하는 정보를 인증 후에 적절히 넣어줘야 한다.

@RestController
public class SampleController {

  @GetMapping("/sample")
  public String hello(@AuthenticationPrincipal Account account) {
    return "hello";
  }
}

@CookieValue, @RequestHeader

@CookieValue 어노테이션으로는 저장된 쿠키의 정보를 가져올 수 있다. 속성 자체는 @RequestParam과 거의 동일하므로 생략하겠다.

@RestController
public class SampleController {

  @GetMapping("/sample")
  public String hello(@CookieValue String authToken) {
    return "hello";
  }
}

@RequestHeader 어노테이션도 마찬가지다. 헤더의 정보를 가져올 수 있다.

@RestController
public class SampleController {

  @GetMapping("/sample")
  public String hello(@RequestHeader("host") String host) {
    return "hello";
  }
}

기타

이외에 추가적으로 사용할 수 있는 것들을 알아보자. 어노테이션과 Servlet을 제외한 타입들이다. 직접적으로 요청을 받는 파라미터와는 관련 없는 부가적인 타입들도 있다.

Map, Model, ModelMap

이것은 파라미터 자체를 받는 것은 아니라 모델 정보를 담는데 사용된다. 실제로 모델을 직접생성을 할 수 도 있겠지만 미리 만들어진 모델을 사용하는 것이 더 괜찮은 방법이라고 생각한다.

@RestController
public class SampleController {

  @GetMapping("/sample")
  public String hello(Model model) {
    model.addAttribute("account", accountService.findByAccount());
    return "hello";
  }
}

Map 과 ModelMap 모두 동일 하다.

Errors, BindingResult

@ModelAttribute 혹은 @RequestBody는 단지 모델에 여러개의 요청 파라미터 값을 넣어서 넘겨주는 것이 전부는 아니다. @RequestParam과는 달리 모델을 검증작업을 추가적으로 할 수 있다. 그 타입이 Errors 혹은 BindingResult로 검증 작업을 할 수 있다.
검증 작업을 하기 위해서는 @Valid 어노테이션과 함께 작성해줘야 한다.

@RestController
public class SampleController {

  @PostMapping("/sample")
  public String hello(@ModelAttribute @Valid Account account, BindingResult result) {
    if(result.hasErrors()) {
      return "error";
    }

    return "hello";
  }
}

class Account {

  @NotBlank
  private String name;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}

여기서 주의할 점은 ModelAttribute 혹은 RequestBody 어노테이션 바로 뒤에 작성되어야만 한다. 자신의 바로 앞에 있는 모델만 검증 작업을 하기 때문이다. 그 이유는 아마도 여러개의 모델이 있을 수 있어서 그런듯 싶다.

SessionStatus

컨트롤러가 제공하는 기능 중에 모델을 세션에 저장했다가 다음 페이지에서 다시 활용하게 해주는 기능이 있다. 이 기능을 사용하고 완료가 되었다면 세션의 있는 정보를 제거해줘야 한다. 그래야 메모리 누수를 방지 할 수 있다.

@RestController
public class SampleController {

  @PostMapping("/sample")
  public String hello(SessionStatus sessionStatus) {
    sessionStatus.setComplete();
    return "hello";
  }
}

RequestEntity<?>

RequestEntity를 사용해서 요청 파라미터로 받을 수 있다. 이 클래스는 RestTemplate을 자주 사용했다면 익숙한 클래스이다. 하지만 이 클래스도 Controller 파라미터 타입으로 받을 수 있다.

@RestController
public class SampleController {

  @PostMapping("/sample")
  public String hello(RequestEntity<Account> account) {
    return "hello";
  }
}

이렇게 오늘 Spring의 요청받는 파라미터 타입들에 대해서 알아봤다. 아주 다양한 파라미터 타입들을 지원한다. 필자가 알아본 파라미터 타입은 이정도 이고 좀 더 많으 내용이 있을 수 도 있다. 만약 관심이 있다면 찾아보길 바란다. 행여나 Spring이 지원해주지 않는다 하더라도 실망하지 말자. Spring은 확장성있게 해당 파라미터를 커스텀하게 만들 수 있는 인터페이스를 제공해주니 말이다. 그 인터페이스는 HandlerMethodArgumentResolver 어노테이션을 직접 구현해주면 된다. 필자가 예전에 포스팅한 내용도 있으니 참고하길 바란다.

Spring Controller 파라미터 타입 (2)