@Controller
는 반환 값이 String이면 뷰 이름으로 인식된다. 따라서 해당 이름의 뷰를 찾고 뷰가 랜더링 된다.
반면 @RestController
는 반환 값으로 뷰를 찾는 것이 아니라, 반환 값을 HTTP 메시지 바디에 바로 입력한다.
예를 들어 @RequestMapping("/hello-basic")
으로 매핑하는 경우, 해당 URL을 호출하면 URL 매핑한 메소드가 실행된다.
URL 다중 설정도 가능하다.
ex, @RequestMapping({"/hello-basic", "/hello-go"})
method 속성을 지정할 수 있으며, 축약 애노테이션으로 @GetMapping
, @PostMapping
, @PutMapping
등이 있다.
ex, @RequestMapping(value = "/hello-basic", method = RequestMethod.GET)
ex, @GetMapping("/hello-basic")
method 속성을 지정하지 않으면 HTTP 메소드와 무관하게 호출된다. 즉 GET, POST, PUT 등 모두 허용한다.
다음 두가지 요청은 다른 URL이지만, 스프링은 같은 요청으로 매핑한다.
- 매핑:
/hello-basic
- URL 요청:
/hello-basic
,/hello-basic/
@RequestMapping
은 URL 경로를 템플릿화 할 수 있는데, @PathVariable
을 사용하면 매칭 되는 부분을 편리하게 조회할 수 있다.@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {}
@PathVariable
의 이름과 파라미터 이름이 같으면 생략할 수 있다. @GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable String userId) {}
@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long orderId) {}
@GetMapping(value = "/mapping-param", param = "mode=debug")
http://localhost:8080/mapping-param?mode=debug
와 같이mode=debug
라는 파라미터가 있는 경우에만 호출된다. 만약 http://localhost:8080/mapping-param
로 호출하면 매핑되지 않는다.@GetMapping(value = "/mapping-header", headers = "mode=debug")
mode=debug
를 넣어서 요청하는 경우에만 호출된다.@PostMapping(value = "/mapping-consume", consumes = "application/json")
@PostMapping(value = "/mapping-produce", produces = "text/html")
@RestController
public class RequestHeaderController {
@RequestMapping("/headers")
public String headers(HttpServletRequest request,
HttpServletResponse response,
HttpMethod httpMethod,
Locale locale,
@RequestHeader MultiValueMap<String, String> headerMap,
@RequestHeader("host") String host,
@CookieValue(value = "myCookie", required = false) String cookie) {
...
}
}
HttpServletRequest
, HttpServletResponse
HttpServletRequest
, HttpServletResponse
객체를 통해 HTTP 요청 메시지와 응답 메시지를 쉽게 조회할 수 있다.
HttpMethod
HTTP 메소드를 조회한다.
Locale
Locale 정보를 조회한다.
@RequestHeader MultiValueMap<String, String> headerMap
모든 HTTP 헤더를 MutiValueMap 형식으로 조회한다.
MutiValueMap
MutiValueMap은 Map과 유사한데, 하나의 키에 여러 값을 받을 수 있다.
HTTP header, HTTP 쿼리 파라미터와 같이 하나의 키에 여러 값을 받을 때 사용한다. (ex, keyA=value1&keyA=value2)
@RequestHeader("host") String host
특정 HTTP 헤더를 조회한다.
@CookieValue(value = "myCookie", required = false) String cookie
특정 쿠키를 조회한다.
HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방식으로 다음 3가지 방식이 있다.
/url?username=hello&age=20
username=hello&age=20
@RequestMapping("/request-param-v1")
public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
response.getWriter().write("ok");
}
@RequestParam
는 파라미터 이름으로 바인딩한다.@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(@RequestParam("username") String username,
@RequsetParam("age") int age) {
return "ok";
}
@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParamV3(@RequestParam String username,
@RequsetParam int age) {
return "ok";
}
@RequestParam
도 생략할 수 있다. @ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age) {
return "ok";
}
required
속성은 파라미터 필수 여부를 뜻하며, 기본값이 true
이다.
만약 @RequestParam(required = false) int age
이고 파라미터 값이 없는 경우, int형 변수에 null이 입력될 수 없기 때문에 예외가 발생한다. 따라서 이 경우 int형이 아닌 Integer형으로 변경해야 한다.
파라미터 이름만 있고 값이 없는 상태로 URL 요청을 하면, 이때는 null 값이 아닌 공백 문자가 입력된다. 예를 들어 http://localhost:8080/request-param-required/username=
인 경우, username
에는 공백 문자가 입력된다.
defaultValue
를 통해 기본 값을 적용할 수 있다. @ResponseBody
@RequestMapping("/request-param-default")
public String requestParamDefault(
@RequestParam(defaultValue = "guest") String username,
@RequsetParam(defaultVale = -1) Integer age) {
return "ok";
}
http://localhost:8080/request-param-required/username=
인 경우 username
은 guest로, age
는 -1로 매핑된다.@ResponseBody
@RequestMapping("/request-param-map")
public String requestParamMap(@RequestParam Map<String, Object> paramMap) {
log.info("username={}, age={}", paramMap.get("username"), paramMap.get("age"));
return "ok";
}
@ModelAttribute
를 사용하면 이러한 과정을 자동화해준다.@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
...
return "ok";
}
스프링 MVC는 @ModelAttribute
가 있으면 다음을 실행한다.
HelloData
객체를 생성한다.
요청 파라미터 이름(ex, username, age)으로 객체의 setter 메소드를 찾아 호출하여 파라미터의 값을 바인딩한다.
ex, 파라미터로 username=lee&age=26
데이터가 들어오면, HelloData의 setUsername()
과 setAge()
메소드를 찾아서 lee와 26이라는 데이터를 바인딩해준다.
해당 객체를 Model에 자동으로 넣어준다.
(ex, model.addAttribute("helloData", helloData);
생략 가능)
@ModelAttribute
에 이름을 지정하면 해당 이름으로 모델에 등록되고, 이름을 지정하지 않으면 클래스 이름의 첫 문자를 소문자로 바꾼 이름으로 모델에 등록된다.
ex, @ModelAttribute("data") HelloData helloData
-> data
ex, @ModelAttribute HelloData helloData
-> helloData
@ModelAttribute
는 생략할 수 있다.
@ResponseBody
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(HelloData helloData) {
log.info("username={}, age={}", helloData.getUserName(), helloData.getAge());
return "ok";
}
@ModelAttribute, @RequestParam 생략
@ModelAttribute
,@RequestParam
둘 다 생략할 수 있는데, 스프링은 어떻게 구분해서 처리할까?
변수가 String, int, Integer 같은 단순 타입이면@RequestParam
, 나머지 형이면(argument resolver로 지정해둔 타입 제외)@ModelAttribute
로 처리한다.
InputStream
: HTTP 요청 메시지 바디의 내용을 직접 조회할 수 있다.
OutputStream
: HTTP 응답 메시지 바디에 직접 결과를 출력할 수 있다.
@Slf4j
@Controller
public class RequestBodyStringController {
@PostMapping("/request-body-string-v1")
public void requestBodyString(HttpServletRequest request, HttpServletResponse response) throws IOException {
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
response.getWriter().write("ok");
}
@PostMapping("/request-body-string-v2")
public void requestBodyStringV2(InputStream inputStream, Writer responseWriter) {
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
responseWriter.write("ok");
}
}
@Slf4j
@Controller
public class RequestBodysonController {
private ObjectMapper objectMapper = new ObjectMapper();
@PostMapping("/request-body-json-v1")
public void requestBodyJsonV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
//HTTP 메시지 바디에서 데이터를 읽어와서 문자로 변환
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
//문자를 객체로 변환
HelloData data = objectMapper.readValue(messageBody, HelloData.class);
log.info("meaasgeBody={}", messageBody);
log.info("username={}, age=[]", data.getUsername(), data.getAge());
response.getWriter().write("ok");
}
}
HttpEntity
를 통해 HTTP 헤더와 바디 정보를 편리하게 조회할 수 있다.
ex, httpEntity.getHeaders()
, httpEntity.getBody()
HttpEntity
는 응답에도 사용할 수 있다. 뷰를 조회하지 않고 메시지 바디 정보를 직접 반환한다.
HttpEntity
를 상속받은 다음 객체들은 다음과 같은 기능을 제공한다.
RequestEntity
: HTTP 헤더, 바디 정보 외에도 HttpMethod, url 정보를 조회할 수 있다.
ResponseEntity
: HTTP 상태 코드를 설정할 수 있다.
ex, return new ResponseEntity<String>("ok", HttpStatus.CREATED);
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {
String messageBody = httpEntity.getBody();
log.info("messaeBody={}", messageBody);
return new HttpEntity<>("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";
}
@RequestBody
를 통해 HTTP 메시지 바디 정보를 편리하게 조회할 수 있다.
만약 헤더 정보가 필요하다면, HttpEntity
또는 @RequestHeader
를 사용하면 된다.
cf, @ResponseBody
: 리턴 값으로 뷰를 조회하지 않고, 리턴 값을 HTTP 메시지 바디에 직접 담아서 전달한다.
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
log.info("messaeBody={}", messageBody);
return "ok";
}
@PostMapping("/request-body-json-v2")
public void requestBodyJsonV2(@RequestBody String messageBody) throws IOException {
//문자를 객체로 변환
HelloData data = objectMapper.readValue(messageBody, HelloData.class);
log.info("meaasgeBody={}", messageBody);
log.info("username={}, age=[]", data.getUsername(), data.getAge());
response.getWriter().write("ok");
}
}
@ResponseBody
@PostMapping("/request-body-json-v3")
public void requestBodyJsonV3(@RequestBody HelloData data) { //JSON -> 메시지 컨버터 -> 객체
log.info("username={}, age=[]", data.getUsername(), data.getAge());
return data; //객체 -> 메시지 컨버터 -> JSON
}
@RequestBody
, HttpEntity
를 사용하면 HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 우리가 원하는 문자나 객체 형태로 변환해준다.
이때 @RequestBody
애노테이션은 생략할 수 없다. @RequestBody
애노테이션을 생략하면, 스프링은 @ModelAttribute
또는 @RequestParam
으로 인식한다.