요청 매핑
스프링 프레임워크의 @RequestMapping 어노테이션을 사용하여 다양한 형태의 HTTP 요청을 처리할 수 있다. @GestMaping, @PostMapping 등의 축약된 어노테이션과 @PathVariable, params, headers, consumes, produces 등의 추가적인 매핑 조건 등이 있다.
기본 요청 매핑
@RequestMapping 어노테이션을 사용하여 특정 URL에 접근했을 때, 실행될 메서드를 지정할 수 있다.
@RestController
public class MappingController {
private Logger log = LoggerFactory.getLogger(getClass());
@RequestMapping("/hello-basic")
public String helloBasic() {
log.info("helloBasic");
return "ok";
}
}
@RestController 와 @Controller 차이
- @Controller : 반환값이 String이면 뷰 이름으로 인식된다. 그래서 뷰를 찾고 뷰가 랜더링 된다.
- @RequestController 는 반환 값으로 뷰를 찾는 것이 아니라, HTTP 메시지 바디에 바로 입력한다. 따라서 실행 결과로 OK메시지를 받을 수 있다.
HTTP 메서드 매핑
@RequestMapping 에 mathod 속성을 통해 특정 HTTP 메서드만 허용할 수 있다. 이를 축약하여 @GetMapping, @PostMapping 등을 사용할 수 있다.
HTTP 메서드 매핑
/**
* method 특정 HTTP 메서드 요청만 허용
* GET, HEAD, POST, PUT, PATCH, DELETE
*/
@RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
public String mappingGetV1(){
log.info("mappingGetV1");
return "ok";
}
POST 요청을 하면 스프링 MVC는 HTTP 405 상태코드(Method Not Allowed)를 반환한다.
HTTP 매핑 축약
/**
* 축약 어노테이션
* @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping
*/
@GetMapping("mapping-get-v2")
public String mappingGetV2(){
log.info("mappingGetV2");
return "OK";
}
HTTP 메서드를 축약한 어노테이션을 사용하는 것이 더 직관적이다. 코드를 보면 내부에서 @RequestMapping 과 method를 지정해서 사용하는 것을 확인할수 있다.
@PathVariable
URL의 일부를 변수로 사용할 수 있다. 이를 통해 동적인 URL 매핑이 가능하다.
/**
* PathVariable 사용
* 변수명이 같으면 생략 가능
* @Pathvariable("userId") String userId -> @PathVariable userId
*/
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data){
log.info("mappingPath userId={}", data);
return "OK";
}
최근 HTTP API는 다음과 같이 리소스 경로에 식별자를 넣는 스타일을 선호한다
http://localhost:8090/mapping/user1217
- @RequestMapping은 URL 경로를 템플릿화 할 수 있는데, @Pathvariable을 사용하면 매칭 되는 부분을 편리하게 조회할 수 있다.
- @PathVariable의 이름과 파라미터 이름이 같으면 생략할 수 있다.
PathVariable 다중 사용
@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long orderId) {
log.info("mappingPath userId={}, orderId={}", userId, orderId);
return "OK";
}
파라미터, 헤더 미디어 타입에 따른 매핑
params, headers, sonsumes, produces 속성을 통해 더 세밀한 요청 매핑이 가능하다. 잘 사용하지는 않는다.
- 특정 파라미터가 있는 경우 : params = "mode=debug"
- 특정 헤더가 있는 경우 : headers = "moder=debug"
- Content-Type이 특정 미디어 타입인 경우 : consumes = "application/json"
- Accept가 특정 미디어 타입인 경우 : produces = "text/html"
HTTP 요청매핑 - API 예시
회원 관리 API
* 회원 목록 조회 : GET '/users'
* 회원 등록 POST '/users'
* 회원 조회 GET '/users/{userId}'
* 회원 수정 PATCH '/users/{userId}'
* 회원 삭제 DELETE'/users/{userId}'
코드 예제 : MappingClassController
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/mapping/users")
public class MappingClassController {
@GetMapping
public String user() {
return "get users";
}
@PostMapping
public String addUser() {
return "post user";
}
@GetMapping("{userId}")
public String findUser(@PathVariable String userId) {
return "get userId=" + userId;
}
@PatchMapping("{userId}")
public String updateUser(@PathVariable String userId) {
return "update userId=" + userId;
}
@DeleteMapping("{userId}")
public String deleteUser(@PathVariable String userId) {
return "delete userId=" + userId;
}
}
클래스 레벨과 메서드 레벨 매핑
@RequestMapping("/mapping/users")
클래스 레벨에 매핑 정보를 지정하면 해당 컨트롤러 내의 모든 메서드가 이 경로를 기준으로 상대 경로를 사용하여 매핑된다.
예를 들어, 클래스 레벨에서 "/mapping/users" 가 지정되어 있으면, 메서드 레벨에서
@GetMapping("/{userId}") 는 실제동작은 "GET /mapping/users/{userId}" 로 동작된다.
HTTP 요청과 헤더 조회
스프링 프레임워크에서는 어노테이션을 통해 다양한 HTTP요청과 헤더 정보를 쉽게 조회할 수 있다.
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Locale;
@Slf4j
@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) {
log.info("request={},", request);
log.info("response={}", response);
log.info("httpMethod={}", httpMethod);
log.info("headerMap={}", headerMap);
log.info("myCookie={}", cookie);
return "OK";
}
}
2023-08-30T20:42:18.009+09:00 INFO 22816 --- [nio-8090-exec-4] h.s.rquest.RequestHeaderController : request=org.apache.catalina.connector.RequestFacade@60a99537,
2023-08-30T20:42:18.009+09:00 INFO 22816 --- [nio-8090-exec-4] h.s.rquest.RequestHeaderController : response=org.apache.catalina.connector.ResponseFacade@44093eb8
2023-08-30T20:42:18.009+09:00 INFO 22816 --- [nio-8090-exec-4] h.s.rquest.RequestHeaderController : httpMethod=GET
2023-08-30T20:42:18.009+09:00 INFO 22816 --- [nio-8090-exec-4] h.s.rquest.RequestHeaderController : headerMap={content-type=[application/json], user-agent=[PostmanRuntime/7.32.2], accept=[*/*], postman-token=[cd6dd60e-e3c2-44ee-9127-9ccd3c287334], host=[localhost:8090], accept-encoding=[gzip, deflate, br], connection=[keep-alive], content-length=[33]}
2023-08-30T20:42:18.010+09:00 INFO 22816 --- [nio-8090-exec-4] h.s.rquest.RequestHeaderController : myCookie=null
- HttpservletRequest ,HttpservletResponse : 기본적인 서블릿 요청과 응답 객체이다.
- HttpMethod : Spring의 'org.springframework.http.HttpMehtod' 를 이용하여 HTTP 메서드를 조회한다.
- Locale : 요청의 로케일 정보를 조회한다.
- @RequestHadder("host") String host : 특정 HTTP 헤더('host' 이 경우)의 값을 String으로 가져온다.
- @CookieValue(value = "myCookie", required = false) String cookie : 이름이 "myCookie" 인 쿠키의 값을 가져온다. 'required=false' 로 설정하면, 해당 쿠기가없어도 오류가 발생하지 않는다.
참고자료
@Controller 사용 가능한 파라미터 목록: Spring 공식 문서
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-arguments
@Controller 사용 가능한 응답 값 목록: Spring 공식 문서
HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form
HTTP 요청의 데이터를 어떻게 서버에서 읽을 수 있는지, 클라이언트에서 서버로 데이터를 보낼 때 주로 사용되는 방법은 다음과 같다.
클라이언트에서 서버로 요청 데이터를 전달할 때, 주로 3가지 방법을 사용한다.
- GET - 쿼리파라미터 : 데이터를 URL에 포함하여 전달한다. (url?username=hello&age=20)예 : 검색, 필터, 페이징등에서 많이 사용하는 방식
- POST - HTML Form content-type : application/x-www-form-urlencoded 형태로 메시지 바디에 데이터를 담아 전송한다. 메시지 바디 쿼리 파라미터 형식 전달 : username=hello&age=20예 : 회원 가입, 상품 주문, HTML Form 사용
- HTTP message body : API에서 사용되며, 메시지 받에 JSON, XML 등을 담아 전송한다.데이티 형식은 주로 JSON 사용POST, PUT, PATCH
쿼리 파라미터와 HTML Form
GET 방식의 쿼리 파라미터나 POST 방식의 HTML Form 두 가지 모두 "httpservletRequest" 의 "request.getParameter()" 메서드를 사용해서 쉽게 데이터를 읽을 수 있다.
RequestParamController 예제
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.io.IOException;
@Slf4j
@Controller
public class RequestParamController {
@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"));
log.info("username={}, age={}", username, age);
response.getWriter().write("OK");
}
}
테스트용 Post HTML Form 생성
src/main/resources/static 폴더에 HTML 파일을 생성하면 스프링 부트가 자동으로 인식한다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/request-param-v1" method="post">
username: <input type="text" name="username" />
age: <input type="text" name="age" />
<button type="submit">전송</button>
</form>
</body>
</html>
2023-08-30T20:58:57.632+09:00 INFO 6852 --- [nio-8090-exec-1] h.s.rquest.RequestParamController : username=김, age=20
스프링 부트는 '/src/main/resources/static/' 경로에 있는 정적 파일을 자동으로 인식하므로, 이 위치에 HTML Form을 생성할 수 있다.
HTTP 요청 파라미터 - @RequestParam
@RequestParam 은 클라이언트로부터 전송된 HTTP 요청 파라미터를 자동으로 바인딩하는 역할을 한다.
기본 사용법 - requestParamV2
@ResponseBody
@RequestMapping("request-param-v2")
public String requestParamV2(
@RequestParam("username") String memberName,
@RequestParam("age") int memberAge) {
log.info("username={}, age={}", memberName, memberAge);
return "OK";
}
@RequestParam : 파라미터 이름으로 바인딩
@ResponseBody : View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력
requestParamV3
@ResponseBody
@RequestMapping("request-param-v3")
public String requestParamV3(
@RequestParam String username,
@RequestParam int age) {
log.info("username={}, age={}", username, age);
return "OK";
}
HTTP 파라미터 이름이 변수 이름과 같으면 @Request(name="") 생략 가능하다.
requestParamV4
@ResponseBody
@RequestMapping("request-param-v4")
public String requestParamV4(String username, int age) {
log.info("username={}, age={}", username, age);
return "OK";
}
String, int, Integer 등의 단순 타입이면 @RequestParam 도 생략 가능하다.
파라미터 필수 여부 - requestParamRequired
@ResponseBody
@RequestMapping("request-param-required")
public String requestParamRequired(
@RequestParam(required = true) String username,
@RequestParam(required = false) Integer age) {
/**
* X : int a = null;
* O : Integer b = null;
*/
log.info("username={}, age={}", username, age);
return "OK";
}
@RequestParam의 required 속성으로 파라미터의 필수 여부를 지정할 수 있다.
기본 값 적용 - requestParamDefault
@ResponseBody
@RequestMapping("request-param-default")
public String requestParamDefault(
@RequestParam(required = true, defaultValue = "guest") String username,
@RequestParam(required = false, defaultValue = "-1") int age) {
log.info("username={}, age={}", username, age);
return "OK";
}
파라미터에 값이 없거나 빈 문자열인 경우에 기본 값을 설정할 수 있다.
이미 기본 값이 있기 때문에 required 는 의미가 없다.
파라미터를 Map 조회 - requestParamMap
@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";
}
@RequestParam 을 Map 또는 MultiValueMap 타입과 함께 사용하면, 모든 파라미터를 한꺼번에 조회할 수 있다.
HTTP 요청 파라미터 - @ModelAttribute
@ModelAttribute 는 클라이언트로부터 전달된 데이터를 특정 객체에 자동으로 바인딩 해주는 기능을 한다.
이를 통해 요청 파라미터를 수동으로 매핑하는 번거로움 없이 객체를 사용할 수 있다.
사용법
@ModelAttribute는 메서드 파라미터 앞에 위치하여, 해당 타입의 객체가 자동으로 생성되고 요청 파라미터가 그 객체의 필드에 적절하게 바인딩된다.
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
log.info("username={}, age={}", helloData.getUsername(),helloData.getAge());
return "OK";
}
동작 방식
- Spring MVC는 먼저 "HelloData" 클래스의 인스턴스를 생성한다.
- 이후 요청 파라미터를 통해 전달된 값들을 "HelloData" 객체의 각 필드와 매핑 시킨다.
- 객체의 필드와 요청 파라미터 이름이 일치하는 경우 자동으로 해당 필드에 값이 설정된다.
@ModelAttribute 생략
@ModelAttribute 는 생략 가능하며, @RequestParam도 생략할 수 있어 혼란이 발생할 수 있다.
스프링은 해당 생략시 다음과 같은 규칙을 적용한다.
- 기본 데이터 타입(String, int, integer 등)은 "@RequestParam"으로 취급된다.
- 그외의 타입은 "@ModelAttribute" 로 취급된다.
@ResponseBody
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(HelloData helloData) {
log.info("username={}, age={}", helloData.getUsername(),helloData.getAge());
return "OK";
}
바인딩 오류
age=abc 처럼 숫자가 들어가야 할 곳에 문자를 넣으면 BindException 이 발생한다.
HTTP 요청 메시지 - 단순 텍스트
HTTP message body 에 데이터를 직접 담아서 요청한다.
- HTTP API에서 주로 사용, JSON, XML, TEXT
- 데이터 형식은 주로 JSON 사용
- POST, PUT, PATCH
요청 파라미터와 다르게, HTTP 메시지 바디를 통해 데이터가 직접 데이터가 넘어오는 경우는 @RequestParam, @ModelArribute 를 사용할 수 없다. (단 HTML Form 형식으로 전달되는 경우는 요청 파라미터로 인정된다.)
InputStream과 OutputStream 사용
RequestBodyStringController v1
@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");
}
이 방식에서는 'HttpServletRequest' 와 'HttpServletResponse' 객체를 이용하여 직접 HTTP 메시지 바디를 다룬다.
RequestBodyStringController v2
@PostMapping("/request-body-string-v2")
public void requestBodyStringV2(InputStream inputStream, Writer responseWriter) throws IOException{
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
responseWriter.write("OK");
}
v2에서는 'InputStream' 과 'Writer' 파라미터를 사용하여 요청과 응답을 다룬다. 이를 통해 Servlet API에 의존하지 않고도 HTTP 메시지를 처리할 수 있다.
- InputStream(Reader) : HTTP 요청 메시지 바디의 내용을 직접 조회
- OutputStream(Writer) : HTTP 응답 멧지의 바디에 직접 결과 출력
HttpEntity 사용
RequestBodyStringController v3
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException{
String messageBody = httpEntity.getBody();
log.info("messageBody={}", messageBody);
return new HttpEntity<>("Ok");
}
'HttpEntity' 는 HTTP 헤더와 바디 정보를 캡슐화하여 쉽게 다룰 수 있게 해준다.
- 메시지 바디 정보를 직접 조회
- 요청 파라미터를 조회하는 기능과 관계 없음 -
@RequestParam X, @ModelAttribute X
'HttpEntity' 응답에도 사용 가능
- 메시지 바디 정보 직접 반환
- 헤더 정보 포함 가능
- view 조회 X
@RequestBody 와 @ResponseBody 사용
RequestBodyStringController v4
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV3(@RequestBody String messageBody) throws IOException{
log.info("messageBody={}", messageBody);
return "OK";
}
@RequestBody 는 HTTP 메시지 바디의 데이터를 자동으로 Java 객체로 변환해주며,
@ResponseBody는 Java 객체를 HTTP 메시지 바디로 변환해준다.
요청 파라미터 vs HTTP 메시지 바디
요청 파라미터를 조회하는 기능 : @RequestParam, @ModelAttribute
HTTP 메시지 바디를 직접 조회하는 기능 : @RequestBody
HTTP 요청 메시지 - JSON
Spring 프레임워크에서 HTTP 메시지 바디에 있는 JSON 형식의 데이터를 쉽게 다룰 수 있다.
/**
* {"username" : "hello", "age" : 20}
* content-type : application/json
*/
HttpServletRequest, HttpServletResponse 사용
RequestBodyJsonV1
@PostMapping("/request-body-json-v1")
public void requestBodyJsonV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
log.info("userName={}, age={}", helloData.getUsername(), helloData.getAge());
response.getWriter().write("OK");
}
HttpServletRequest 를 사용해 HTTP 메시지 바디에서 데이터를 읽어 문자열로 변환한다.
Jackson 라이브러리의 ObjectMapper 를 사용하여 문자열 형태의 JSON 데이터를 Java 객체로 변환한다.
@RequestBody 사용
RequestBodyJsonV2 - RequestBody 문자 변환
@ResponseBody
@PostMapping("/request-body-json-v2")
public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException {
log.info("messageBody={}", messageBody);
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
log.info("userName={}, age={}", helloData.getUsername(), helloData.getAge());
return "OK";
}
@RequestBody 를 사용하여 HTTP 메시지 바디의 JSON 데이터를 Java의 String 형태로 가져온다.
이후 마찬가지로 'ObjectMapper'를 사용해 JSON 문자열을 Java 객체로 변환한다.
RequestBodyJsonV3 - @RequestBody 객체 변환
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData data) {
log.info("userName={}, age={}", data.getUsername(), data.getAge());
return "OK";
}
@RequestBody 에 직접 만든 객체 ('HelloData')를 지정하면, Spirng은 HTTP 메시지 바디의 JSON 데이터를 해당 객체로 자동 변환해준다.
HttpEntity 사용
RequestBodyJsonV4, RequestBodyJsonV5
@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 생략 불가능(@ModelAttribute 가 적용되어 버림)
* HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (content - type : application/json)
*
* @ResponseBody 적용
* - 메시지 바디 정보 직접 반환(view 조회 X)
* - HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter 적용 (Accept : application/json)
*/
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
log.info("userName={}, age={}", data.getUsername(), data.getAge());
return data;
}
HttpEntity 를 사용하면 HTTP 메시지의 헤더와 바디를 함께 받을 수 있다.
@ResponseBody
응답의 경우에도 @ResponseBody 를 사용하면 해당 객체를 HTTP 메시지 바디에 직접 넣어줄 수 있다.
- @RequestBody 요청 : JSON 요청 -> HTTP 메시지 컨버터 -> 객체
- @ResponseBody 응답 : 객체 -> HTTP 메시지 컨버터 -> JSON 응답
주의사항
- @RequestBody 는 생략이 불가능하다. 생략하면 @ModelAttribute 가 적용되어 HTTP 메시지 바디가 아니라 요청 파라미터를 처리하게 된다.
- HTTP 요청 시 'content - type' 이 'application/json' 인지 반드시 확인해야 한다. 그래야 JSON을 처리 할 수 있는 HTTP 메시지 컨버터가 실행된다.
HTTP 응답 - 정적 리소스, 뷰 템플릿
스프링(서버)에서 클라이언트에게 데이터를 응당하는 방법은 크게 3가지이다.
1. 정적 리소스
- 정적 리소스는 HTML, CSS, JavaScript 등 변경 없이 그대로 제공되는 파일이다.
- 기본 정적 리소스 경로 : 'src/main/resources/static'
- src/main/resources는 리소스를 보관하는 곳이며, 또 Class path의 시작 경로이다. 따라서 다음 디렉토리에 리소스를 넣어두면 스프링 부트가 정적 리소스로 서비스를 제공한다.
- 예를 들어 'src/main/resources/static/basic/hello-form.html' 이 있다면 웹 브라우저에서는 'http://localhost:8080/basic/hello-form.html' 으로 접근이 가능하다.
2. 뷰 템플릿
동적으로 HTML을 생성하는 뷰 템플릿을 사용할 수 있다. Thymeleaf, JSP 등이 있다.
뷰 템플릿 경로 : src/main/resources/templates
뷰 템플릿 생성 : src/main/resoursce/templates/response/hello.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p th:text="${data}">empty</p>
</body>
</html>
ResponseViewController - 뷰 템플릿을 호출하는 컨트롤러
package hello.springmvc.basic.response;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class ResponseViewController {
@RequestMapping("/response-view-v1")
public ModelAndView responseViewV1() {
ModelAndView mav = new ModelAndView("response/hello").addObject("data", "hello!");
return mav;
}
@RequestMapping("/response-view-v2")
public String responseViewV2(Model model) {
model.addAttribute("data", "hello!");
return "response/hello";
}
@RequestMapping("/response/hello")
public void responseViewV3(Model model) {
model.addAttribute("data", "hello!");
}
}
String을 반환하는 경우 - View 또는 HTTP 메시지
@ResponseBody 가 없으면 response/hello 로 뷰 리졸버가 실행되어서 뷰를 찾고, 렌더링 한다.
@ResponseBody 가 있으면 뷰 리졸버를 실행하지 않고, HTTP 메시지 바디에 직접 response/hello 라는 문자가 입력되어 응답한다.
Void를 반환하는 경우
@Controller 를 사용하고, HttpservletResponse, OutputStream(Writer) 같은 HTTP 메시지 바디를 처리하는 파라미터가 없으면 요청 URL을 참고해서 논리 뷰 이름으로 사용
이 방식은 명시성이 없기 때문에, 권장되지 않는다.
3. HTTP 메시지
RESTful API와 같이 HTM대신 JSON 데이터를 제공하는 경우에는 HTTP 메시지 바디를 직접 사용한다.
1.정적 리소스는 변경이 없는 데이터를 제공할 때,
2.뷰 템플릿은 동적인 웹 페이지를 생성할 때
3.HTTP메시지는 API등 데이터 자체를 제공할 때
이러한 방식들은 각각의 목적과 상황에 따라 적절하게 사용된다.
HTTP 응답 - HTTP API(RESTful API), 메시지 바디 직접입력
HTTP API를 제공할 때는 대부분 HTML가 아닌 데이터를 클라이언트에게 전달해야 한다. 이럴 때는 HTTP 응답 메시지 바디에 JSON 같은 형식으로 데이터를 담아 전송하게 된다.
참고
HTML이나 뷰 템플릿을 사용해도 HTTP 응답 메시지 바디에 HTML 데이터가 담겨서 전달된다.
아래 설명하는 내용은 정적 리소스, 뷰 템플릿 과정을 거치지 않고, 직접 HTTP 응답 메시지를 전달하는 경우를 말한다.
메시지 바디에 직접 데이터 입력하기
사용되는 어노테이션 및 클래스
- @Controller 와 @RestController : 스프링에서 컨트롤러를 정의하는 어노테이션이다.
- @ResponseBody : 메서드에 이 어노테이션을 붙이면, 리턴 값이 뷰를 통해 처리 되지 않고, HTTP 응답 바디에 그대로 담긴다.
- @ResponseEntity : HTTP 응답 상태 코드, 헤더, 바디 등을 설정할 수 있는 클래스이다.
- @ResponseStatus : 응답 코드를 설정할 수 있는 어노테이션이다.
response-body-string-v1
@GetMapping("/response-body-string-v1")
public void responseBodyV1(HttpServletResponse response) throws IOException {
response.getWriter().write("OK");
}
서블릿의 HttpServletResponse 객체를 사용하여 HTTP 메시지 바디에 직접 "OK" 라는 응답을 작성할 수 있다.
response-body-string-v2
@GetMapping("/response-body-string-v2")
public ResponseEntity<String> responseBodyV2() {
return new ResponseEntity<>("OK", HttpStatus.OK);
}
ResponseEntity 를 사용하면 HTTP 상태 코드도 함께 설정할 수 있다. "OK" 바디와 함께 "HttpStatus.OK" 상태코드 200를 반환한다.
response-body-string-v3
@ResponseBody
@GetMapping("/response-body-string-v3")
public String responseBodyV3(){
return "OK";
}
@ResponseBody 어노테이션을 사용하면, 뷰 없이 HTTP 메시지 바디에 직접 데이터를 담을 수 있다.
@ResponseEntity도 동일한 방식으로 동작된다.
response-body-json-v1
@GetMapping("/response-body-json-v1")
public ResponseEntity<HelloData> responseBodyJsonV1() {
HelloData helloData = new HelloData();
helloData.setUsername("hello");
helloData.setAge(20);
return new ResponseEntity<>(helloData, HttpStatus.OK);
}
이 경우, 'HelloData' 객체가 JSON으로 변환되어 응답 바디에 담긴다.
response-body-json-v2
@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/response-body-json-v2")
public HelloData responseBodyJsonV2() {
HelloData helloData = new HelloData();
helloData.setUsername("hello");
helloData.setAge(20);
return helloData;
}
ResponseEntity 는 HTTP 응답 코드를 설정할 수 있지만, @ResponseBody를 사용하면 이런 설정하기 까다롭다.
@ResponseStatus(HttpStatus.OK) 어노테이션을 사용하면 응답 코드도 설정할 수 있다.
이 방식은 상태 코드를 동적으로 변경할수 없기 때문에, 동적으로 변경하려면 ResponseEntity 를 사용해야 한다.
HTTP 메시지 컨버터
뷰 템플릿으로 HTML을 생성해서 응답하는 것이 아니라, HTTP API처럼 JSON데이터를 HTTP 메시지 바디에서 직접 읽거나 쓰는 경우 HTTP 메시지 컨버터를 사용하면 편리하다.
HTTP 메시지 컨버터는 @RequestBody, @ResponseBody 등을 사용할 때 내부에서 동작한다. 스프링 부트는 기본적으로 여러 메시지 컨버터를 지원한다.
- HTTP 요청 : @RequestBody, HttpEntity(RequestEntity)
- HTTP 응답 : @ResponseBody, HttpEntity(ResponseEntity)
HttpMessageConverter 인터페이스
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
// ... (중략)
}
- canRead(), canWrite() : 메시지 컨버터가 지원하는 타입과 미디어 타입을 확인한다.
- read(), write() : 메시지 컨버터를 통해서 메시지를 읽거나 쓴다.
주요 메시지 컨버터들 우선순위
0 = ByteArrayHttpMessageConverter
1 = StringHttpMessageConverter
2 = MappingJackson2HttpMessageConverter
스프링 부트는 다양한 메시지 컨버터를 제공하지만, 대상 클래스 타입과 미디어 타입 둘을 확인해서 사용여부를 결정한다.
확인 후 만족하지 않으면 다음 우선순위 메시지 컨버터로 넘어간다.
동작 과정
요청 데이터 읽기
1. canRead() 를 호출해서 메시지 컨버터의 지원 여부를 확인한다.
2. 조건을 만족하면 read() 를 호출해서 객체를 생성하고 반환한다.
응답 데이터 생성
1. canWrite() 를 호출해서 메시지 컨버터의 지원 여부를 확인한다.
2. 조건을 만족하면 write() 를 호출해서 HTTP 응답 메시지 바디에 데이터를 생성한다.
요청 매핑 헨들러 어댑터 구조
스프링 MVC에서 HTTP 메시지 컨버터(HttpMessageConverter) 와 요청 매핑 헨들러 어댑터(RequestMappingHandlerAdapter)는 요청과 응답을 처리하는 핵심 구성 요소이다.
RequestMappingHandlerAdapter 동작 방식
ArgumentResolver
- ArgumentResolver 가 컨트롤러에 필요한 다양한 파라미터를 생성한다. 이는 HandlerMethodArgumentResolver 인터페이스로 구현되어 있다.
- 어노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdapter 는 바로 ArgumentResolver 를 호출해서 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터의 값(객체)를 생성한다. 그리고 이렇게 파라미터의 값이 모두 준비되면 컨트롤러를 호출하면서 값을 넘겨준다.
- 스프링은 30개가 넘는 AgumentResolver 를 기본으로 제공한다.
동작 방식
ArgumentResolver 의 suppersParameter() 를 호출해서 해당 파라미터를 지원하는지 체크하고, 지원하면 resolverArgument() 를 호출해서 실제 객체를 생성한다. 생성된 객체가 컨트롤러 호출시 넘어간다.
참고 - 가능한 파라미터 목록 (공식 메뉴얼)
Method Arguments :: Spring Framework
JDK 8’s java.util.Optional is supported as a method argument in combination with annotations that have a required attribute (for example, @RequestParam, @RequestHeader, and others) and is equivalent to required=false.
docs.spring.io
ReturnValueHandler
- HandlerMethodReturnValueHandler 를 줄여서 ReturnValueHandler 라 부른다. ArgumentResolver 와 비슷하지만, 이것은 응답 값을 변환하고 처리한다.
- 컨트롤러에서 스프링으로 뷰 이름을 반환해도, 동작하는 이유는 ReturnValueHandler 덕분이다.
- 스프링은 10개가 넘는 ReturnValueHandler 를 제공한다.
- 예 ) ModelAndView, @ResponseBody, HttpEntity, String
참고 - 가능한 응답 값 목록 (공식 메뉴얼)
Return Values :: Spring Framework
A single value type, e.g. Mono, is comparable to returning DeferredResult. A multi-value type, e.g. Flux, may be treated as a stream depending on the requested media type, e.g. "text/event-stream", "application/json+stream", or otherwise is collected to a
docs.spring.io
HTTP 메시지 컨버터 위치
요청의 경우 @RequestBody 와 HttpEntity 를 처리하는 ArgumentResolver 가 있고, 이 것이 HTTP 메시지 컨버터를 사용하여 필요한 객체를 생성한다.
응답의 경우 @ResponseBody 와 HttpEntity 를 처리하는 ReturnValueHandler 가 있고, 이것이 HTTP 메시지 컨버터를 호출해서 응답 결과를 만든다.
출처 : 인프런 - 🔗 스프링 MVC 1 - 백엔드 개발 핵심기술 by 우아한형제 김영한님
'Spring > Spring MVC - 활용' 카테고리의 다른 글
[Spirng] Cookie, Session(쿠키, 세션) (0) | 2023.09.08 |
---|---|
[Spirng] Bean Vaildation(빈 밸리데이션) - 검증 (0) | 2023.09.07 |
[Spring] Validation(밸리데이션) - 검증 (0) | 2023.09.06 |
메시지, 국제화 (0) | 2023.09.03 |
로그(logging) (0) | 2023.08.29 |