로깅
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LogTestController {
private final Logger log = LoggerFactory.getLogger(LogTestController.class);
@RequestMapping("/log-test")
public String logTest() {
String name = "spring";
log.info("name = " + name);
return "ok";
}
}
Logger와 LoogerFactory는 org.slf4j 패키지에 있는 걸 사용한다.
@Controller 대신 @RestController 가 사용되었다. 차이점은 @Controller 는 반환되는 값으로 뷰를 찾고 뷰가 렌더링 되지만
@RestController 는 반환 값으로 HTTP 메시지 바디에 바로 입력해준다. @RequestBody 와 관련 있지만 다음 시간에 배운다.
Log 기능
@RequestMapping("/log-test")
public String logTest() {
String name = "spring";
log.trace("trace log={}", name);
log.debug("debug log={}", name);
log.info(" info log={}", name);
log.warn(" warn log={}", name);
log.error("error log={}", name);
return "ok";
}
trace와 debug가 찍히지 않았다.
로그 level은 trace, debug, info, warn, error 순서로 있으며 디폴트로 info로 지정되어있다.
바꾸려면 application.properties 에서 설정한다.
#hello.springmvc 패키지와 그 하위 로그 레벨 설정
logging.level.hello.springmvc=trace
전체 패키지에 로그 레벨을 설정할 수도 있다. debug로 변경하고 실행 보자.
# 전체 로그 레벨 설정 (기본 info)
logging.level.root=debug
#hello.springmvc 패키지와 그 하위 로그 레벨 설정, root보다 우선권이 높다.
logging.level.hello.springmvc=info
엄청난 DEBUG 로그가 출력된다.
스프링의 구성요소마다 로그가 세팅된 걸 알 수 있다.
로그를 사용할 때 클래스마다 아래의 코드를 선언해야 할까?
private final Logger log = LoggerFactory.getLogger(??.class);
이전과 마찬가지로 로그를 편안하게 사용할 수 있는 기능이 존재한다.
@Slf4j
@RestController
public class LogTestController {
// private final Logger log = LoggerFactory.getLogger(LogTestController.class);
...
}
로그의 장점
- 쓰레드 정보, 클래스 정보를 볼 수 있고 출력 모양을 조정할 수 있다.
- 로그 레벨 제어로 상황에 맞게 로그를 출력할 수 있다.
- System.out.println()은 시스템 콘솔에만 출력되지만 로그는 파일, 네트워크, 디비 등 별도의 위치에 남길 수 있고, 시간이나 용량에 따라 분할도 가능하다.
- 성능이 일반 System.out보다 좋기 때문에 실무에서 사용한다.
스프링 MVC - 기본 기능 (요청 매핑)
매핑에는 여러 가지 기능이 존재한다. 그중 배열로 하나의 메서드를 실행하게 할 수 있다.
@RequestMapping({"/hello-basic", "/hello-go"})
public String helloBasic() {
log.info("hello baisc");
return "ok";
}
HTTP 메서드
매핑에 method 속성이 존재하는데, 없으면 GET, POST 무관하게 호출이 된다.
HTTP 메서드 매핑은 다음과 같다.
@Slf4j
@RestController
public class MappingController {
...
// HTTP 매핑
@RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
public String mappingGetV1() {
log.info("mappingGetV1");
return "ok";
}
}
하지만 경로와 HTTP를 매핑도 반복되어 단축된 방법도 있다.
HTTP 매핑 메서드 축약
/**
* 편리한 축약 애노테이션 (코드보기)
*@GetMapping
*@PostMapping
*@PutMapping
*@DeleteMapping
*@PatchMapping
*/
@GetMapping(value = "/mapping-get-v2")
public String mappingGetV2() {
log.info("mapping-get-v2");
return "ok";
}
PathVariable(경로 변수) 사용
/**
* PathVariable 사용
* 변수명이 같으면 생략 가능
*@PathVariable("userId") String userId ->@PathVariable userId
*/
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {
log.info("mappingPath userId={}", data);
return "ok";
}
경로를 값으로 이용한다.
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";
}
위와 동일한 방법으로 호출하면 된다.
파라미터 추가 매핑
@GetMapping(value = "/mapping-param", params = "mode=debug")
public String mappingParam() {
log.info("mappingParam");
return "ok";
}
특정 파라미터와 값이 존재해야 해당 메서드가 호출이 된다.
ex) localhost:8080/mapping-param?mode=debug 이렇게 넣어야지 호출됨
지금은 잘 사용 안 하는 방식이라 한다.
특정 헤더로 추가 매핑
/**
* 특정 헤더로 추가 매핑
* headers="mode",
* headers="!mode"
* headers="mode=debug"
* headers="mode!=debug" (! = )
*/
@GetMapping(value = "/mapping-header", headers = "mode=debug")
public String mappingHeader() {
log.info("mappingHeader");
return "ok";
}
위와 같으며, 헤더로 넘어온 값으로 확인한다. 확인하려면 postman을 사용해야 한다.
Content-type 헤더 기반 추가 매핑
/**
* Content-Type 헤더 기반 추가 매핑 Media Type * consumes="application/json"
* consumes="!application/json"
* consumes="application/*"
* consumes="*\/*"
* MediaType.APPLICATION_JSON_VALUE
*/
@PostMapping(value = "/mapping-consume", consumes = "application/json")
public String mappingConsumes() {
log.info("mappingConsumes");
return "ok";
}
확인하려면 postman을 사용해야 한다. (만약 맞지 않으면 406 상태코드(Not Acceptable) 반환한다.)
Accept 헤더 기반 Media Type
/**
* Accept 헤더 기반 Media Type
* produces = "text/html"
* produces = "!text/html" * produces = "text/*"
* produces = "*\/*"
*/
@PostMapping(value = "/mapping-produce", produces = "text/html")
public String mappingProduces() {
log.info("mappingProduces");
return "ok";
}
확인하려면 postman을 사용해야 한다. (만약 맞지 않으면 406 상태코드(Not Acceptable) 반환한다.)
이것도 위와같다.
요청 매핑 - API 예시
경로 변수를 이용하여 어떻게 HTTP API를 만드는지 확인해보자.
회원관리 API를 간단히 만들어 본다.
@RestController
@RequestMapping("/mapping/users")
public class MappingClassController {
/*
회원 목록 조회: GET `/users`
회원 등록: POST `/users`
회원 조회: GET `/users/{userId}`
회원 수정: PATCH `/users/{userId}`
회원 삭제: DELETE `/users/{userId}`
*/
@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 = " + user();
}
@PatchMapping("/{userId}")
public String updateUser(@PathVariable String userId) {
return "update userId = " + userId;
}
@DeleteMapping("/{userId}")
public String deleteUser(@PathVariable String userId) {
return "delete userId = " + userId;
}
}
HTTP 요청 - 기본, 헤더 조회
@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("locale={}", locale);
log.info("headerMap={}", headerMap);
log.info("header host={}", host);
log.info("myCookie={}", cookie);
return " ok";
}
}
- HttpMethod : HTTP 메서드를 조회
- Locale : locale 정보를 조회
- @RequestHeader MultiValueMap<String, String> headerMap : 모든 HTTP 헤더를 MultiValueMap 형식으로 조회
- @RequestHeader(key) : 특정 HTTP 헤더를 조회**@CookieValue(key)** : 특정 쿠키를 조회
@Slf4J
다음 코드를 자동으로 생성해서 로그를 선언해준다. 개발자는 편리하게 log 라고 사용하면 된다.
private static final org.slf4j.Logger log =
org.slf4j.LoggerFactory.getLogger(RequestHeaderController.class);
참고 : MultiValueMap는 하나의 키에 여러 값을 받을 수 있는 Map이다.
HTTP 요청 파라미터 - @RequestParam
스프링이 제공하는 @RequestParam을 이용하면 편안하게 요청 데이터를 사용할 수 있다.
@RequestMapping("/request-param-v2")
public String requestParamV2(
@RequestParam("username") String memberName,
@RequestParam("age") int memberAge) {
log.info("username={}", memberName, memberAge);
return "ok";
}
더 나아가 @RequestParam(key)에서 key와 변수명이 동일한 경우 key를 생략할 수 있다.
@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParamV3(
String username,
@RequestParam int age) {
log.info("username={}, age={}", username, age);
return "ok";
}
String, int, Integer 같은 단순 타입이면 @RequestParam 생략도 가능하다.
@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age) {
log.info("username={}, age={}", username, age);
return "ok";
}
→ 하지만 @RequestParam 을 생략하지 않고 항상 적어주는 것을 권장한다.
필수 파라미터 여부
@RequestParam 속성엔 required가 있다. default=true이며, 필수인 값이 없으면 400 에러가 반환된다.
@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(
@RequestParam(required = true) String username,
@RequestParam(required = false) int age) {
log.info("username={}, age={}", username, age);
return "ok";
}
- 주의할점은 String은 파라미터 이름만 있고 값이 없는 username= 부분으로 온다면 빈문자로 통과할 수 있다.
- null을 int에 입력하는것은 불가능(500 예외 발생) 따라서 null을 받을 수 있는 Integer로 변경하거나 다음에 나오는 defaultValue를 사용한다.
@RequestParam은 defaultValue 속성이 존재한다.
@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";
}
필수 데이터가 없어도 디폴트 값을 지정해서 400 에러가 발생하지 않는다.
Map 타입으로 요청 데이터를 담을 수 있다.
@ResponseBody
@RequestMapping("/request-param-map")
public String requestParamDefault(
@RequestParam Map<String, Object> paramMap) {
log.info("username={}, age={}", paramMap.get("username"), paramMap.get("age"));
return "ok";
}
- Map은 키-값 으로 이루어지는데 HTTP 요청 파라미터의 키가 항상 문자열 형태이므로 String으로 받고 값은 Object로 받아서 문자열,숫자,불리언등 다양한 타입의 데이터를 처리할 수 있다.
- 만약에 데이터가 배열로 넘어온다면 MultiValueMap을 사용하자.
HTTP 요청 파라미터 - @ModelAttribute
보통 데이터를 받으면 객체를 생성하고 그 객체에 값을 세팅하는데,
스프링은 이 과정을 자동화시켜주는 @ModelAttribute 기능을 제공한다.
@Data
public class HelloData {
private String username;
private int age;
}
- 롬복의 @Data를 사용하면 @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor를 적용해준다.
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@RequestParam String username, @RequestParam int age) {
HelloData helloData = new HelloData();
helloData.setUsername(username);
helloData.setAge(age);
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
return "ok";
}
이런식으로 객체를 생성하고 값을 세팅해야 했지만 @ModelAttribute 를 사용하면
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
return "ok";
}
이렇게 자동으로 만들어주어 사용할 수 있다. (@ModelAttribute 생략도 가능하다.)
스프링의 @ModelAttribute 실행 순서
- HelloData 객체 생성
- 요청 파라미터의 이름으로 HelloData 객체 프로퍼티를 찾고, setter 호출해서 값을 바인딩한다.
- 예) 파라미터 이름이 username 이면 setUsername() 을 메서드를 찾아 호출하면서 값을 입력. (요청 데이터와 객체의 프로퍼티 명이 일치해야 한다)
- 만약 타입에 어긋나는 데이터가 들어오면 에러가 발생한다.
스프링은 @RequestParam, @ModelAttribute 를 생략하면 다음의 규칙을 따른다.
- String, int, Integer 같은 단순 타입이면 @RequestParam을 자동 적용
- 나머지는 @ModelAttribute 를 적용한다. 단, argument resolver로 지정해둔 타입은 제외. (이 내용은 나중에 언급됨)
HTTP 요청 메시지 - 단순 텍스트
이전 시간에 했던 요청 파라미터와 다르게 HTTP 메시지 바디를 통해 데이터 넘어오는 경우
@RequestParam, @ModelAttribute를 사용할 수 없다.
단, HTML Form 형식은 요청 파라미터로 인정된다.
HTTP 메시지 바디의 데이터는 InputStream을 사용해서 직접 읽을 수 있다.
@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); // 바이트코드를 UTF-8 변환
log.info("messageBody", messageBody);
response.getWriter().write("ok");
}
}
확인은 postman으로 한다.
HttpServletRequest 에서 받아와서 처리하는 과정이 너무 길다.
스프링 MVC는 다음 파라미터를 지원한다.
InputStream(Reader) : HTTP 요청 메시지 바디의 내용을 직접 조회
OutputStream(Writer) : HTTP 응답 메시지의 바디에 직접 결과 출력
@PostMapping("/request-body-string-v2")
public void requestBodyStringV2(InputStream inputStream, Writer responseWriter) throws IOException {
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); // 바이트코드를 UTF-8 변환
log.info("messageBody", messageBody);
responseWriter.write("ok");
}
이것도 처음에 비하면 과정이 간단히 생략되었을 뿐 사용방법은 흡사해서 계속 반복되는 불편함이 있다.
스프링 MVC는 더 나아가 메시지 컨퍼버라는 기능을 제공한다. (자세한 내용은 다음에 언급된다.)
HttpEntity
@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");
}
HTTP 메시지처럼 주고 받는다.
- HTTP header, body 정보를 편리하게 조회
- 요청 파라미터를 조회하는 기능과 관계없음 (@RequestParam, @ModelAttribute)
- 응답에도 사용 가능
- 메시지 바디 정보 직접 반환
- 헤더 정보 포함 가능
- view 조회X
HttpEntity를 상속받은 다음 객체들도 존재한다
- RequestEntity
- HttpMethod, url 정보가 추가, 요청에 사용
- ResponseEntity
- HTTP 상태 코드 설정 가능, 응답에서 사용
- return new ResponseEntity<String>("hello world", responseHeaders, HttpStatus.CREATED)
@PostMapping("/request-body-string-v4")
public HttpEntity<String> requestBodyStringV4(HttpEntity<String> httpEntity) throws IOException {
String messageBody = httpEntity.getBody();
log.info("messageBody={}", messageBody);
return new ResponseEntity<String>("ok",HttpStatus.CREATED);
}
스프링MVC 내부에서 HTTP 메시지 바디를 읽어서 문자나 객체로 변환하여 전달한다. 이때 HTTP 메시지 컨버터(HttpMessageConverter) 기능을 사용한다. (자세한 내용은 다음에)
@RequestBody
@RequestBody를 사용하면 HTTP 메시지 바디 정보를 편리하게 조회할 수 있다. 헤더 정보가 필요하면 HttpEntity나 @RequestHeader를 사용한다. 메시지 바디를 조회하는 기능은 @RequestParam, @ModelAttribute와는 관계없다.
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String meesageBody) throws IOException {
log.info("messageBody={}", meesageBody);
return "ok";
}
정리
- 요청 파라미터를 조회하는 기능 : @RequestParam, @ModelAttirubte
- HTTP 메시지 바디를 직접 조회하는 기능 : @RequestBody
- @ResponseBody
- 응답 결과를 HTTP 메시지 바디에 직접 담아서 전달 가능 (뷰를 사용하지 않는다.)
HTTP 요청 메시지 - JSON
HTTP API 에서 주로 사용하는 JSON 데이터 형식으로 조회해보자
기존 서블릿 방식
@Slf4j
@Controller
public class RequestBodyJsonController {
private ObjectMapper objectMapper = new ObjectMapper();
@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");
}
}
@RequestBody 방식 - String
@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 방식 - JSON -> 객체 자동 변환
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData helloData) throws IOException {
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
return "ok";
}
@RequestBody 객체 파라미터
- 직접 만든 객체를 지정할 수 있다. @RequestBody HelloData helloData
HttpEntity, @RequestBody를 사용하면 HTTP 메시지 컨버터가 메시지 바디의 내용을 지정된 문자나 객체로 변환해준다.
JSON도 객체로 변환시켜주는데 V2 에서 했던 objectMapper.readValue(값, 객체)를 대신 처리해준다. (자세한 내용은 다음에)
@RequestBody는 생략 불가능하다. 생략하면 @RequestParam 이나 @ModelAttribute로 적용되어
HTTP 메시지 바디가 아니라 요청 파라미터를 처리하게 된다. (요청 파라미터 처리 기능은 메시지 바디 데이터를 인식 못한다)
명심할 것은 HTTP 요청 시에 content-type : application/json 인지 확인해야 한다. 그래야 HTTP 메시지 컨버터가 실행된다.
HttpEntity 방식
@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) throws IOException {
HelloData data = httpEntity.getBody();
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
@RequestBody - 응답 방식
- 해당 객체를 HTTP 메시지 바디에 직접 넣을 수 있다
- 이 경우에도 HttpEntity 사용이 가능하다.
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) throws IOException {
log.info("username={}, age={}", data.getUsername(), data.getAge());
return data;
}
정리
@RequestBody 요청
- JSON 요청 -> HTTP 메시지 컨버터 -> 객체
@RequestBody 응답
- 객체 -> HTTP 메시지 컨버터 -> JSON 응답
HTTP 응답 - 정적 리소스, 뷰 템플릿
- 정적 리소스
- ex) 웹 브라우저에 정적인 HTML, css , js을 제공할 때 정적 리소스 사용
- 뷰 템플릿 사용
- ex) 웹 브라우저에 동적인 HTML을 제공할 때는 뷰 템플릿을 사용
- HTTP 메시지 사용
- HTTP API를 제공하는 경우에는 HTML이 아니라 데이터를 전달해야 하므로, HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 보
- 정적 리소스
정적 리소스
스프링 부트는 클래스패스의 다음 디렉토리에 있는 정적 리소스를 제공한다.
/static, /public, /resource, /META-INF/resources
정적 리소스 경로
src/main/resources/static
뷰 템플릿
뷰 템플릿을 거쳐서 HTML이 생성되고, 뷰가 응답을 만들어서 전달한다.스프링부트는 기본 뷰 템플릿 경로를 제공한다.
뷰 템플리 경로 : src/main/resource/templates
@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 or HTTP 메시지
@ResponseBody가 없으면 response/hello로 뷰 리볼버가 실행되어서 뷰를 찾고 렌더링 한다.
만약에 있으면 뷰 리졸버를 건너뛰고 HTTP 메시지 바디에 직접 response/hello 문자가 입력되어 반환된다.
@ResponseBody가 없고, 뷰 이름도 반환되지 않는다면?
스프링은 자체적으로 경로로 뷰 템플릿을 찾아서 반환된다. (명시적이 없어 비권장한다)
HTTP 응답 - HTTP API, 메시지 바디에 직접 입력
HTTP API를 제공하는 경우에 HTML이 아니라 데이터를 전달해야 하므로, HTTP 메시지 바디에 JSON 형식으로 데이터를 보낸다.
@Slf4j
@Controller
public class ResponseBodyController {
@GetMapping("/response-body-string-v1")
public void responseBodyV1(HttpServletResponse response) throws IOException {
response.getWriter().write("ok");
}
@GetMapping("/response-body-string-v2")
public ResponseEntity<String> responseBodyV2() {
return new ResponseEntity<>("ok", HttpStatus.OK);
}
@ResponseBody
@GetMapping("/response-body-string-v3")
public String responseBodyV3() {
return "ok";
}
@GetMapping("/response-body-json-v1")
public ResponseEntity<HelloData> responseBodyJsonV1() {
HelloData helloData = new HelloData();
helloData.setAge(20);
helloData.setUsername("userA");
return new ResponseEntity<>(helloData, HttpStatus.OK);
}
@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/response-body-json-v2")
public HelloData responseBodyJsonV2() {
HelloData helloData = new HelloData();
helloData.setUsername("userA");
helloData.setAge(20);
return helloData;
}
}
- responseBodyV1 : HttpServletResponse 객체를 통해서 HTTP 메시지 바디에 응답 메시지를 전달한다.
- responseBodyV2 : HttpEntity는 HTTP 메시지의 헤더, 바디 정보를 갖는데, 이를 상속받은 ResponseEntity는 HTTP 응답 코드를 설정할 수 있다.
- responseBodyV3 : @ResponseBody를 사용하면 HTTP 메시지 컨버터를 통해 HTTP 메시지를 직접 입력한다.
- responseBodyJsonV1 : ResponseEntity로 반환한다. HTTP 메시지 컨버터를 통해 JSON 형식으로 반환 된다.
- responseBodyJsonV2 : @ResponseBody 사용할 경우에 HTTP 응답 코드는 @ResponseStatus로 설정한다.
@RestController
@Controller 대신에 @RestController 애노테이션을 사용하면, 해당 컨트롤러에 모두 @ResponseBody 가 적용되는 효과가 있다. 따라서 뷰 템플릿을 사용하지 않고 HTTP 메시지 바디에 직접 데이터를 입력한다. 주로 Rest API 를 만들 때 사용되는 컨트롤러이다.
HTTP 메시지 컨버터
뷰 템플릿으로 HTML으로 응답하는 게 아니라, HTTP API처럼 JSON 데이터를 HTTP 메시지 바디에서 읽거나 쓰는 경우 HTTP 메시지 컨버터를 사용하면 편리하다.
스프링 MVC는 HTTP 요청과 응답에 HTTP 메시지 컨버터를 적용한다.
- HTTP 요청 : @RequestBody, HttpEntity(RequestEntity)
- HTTP 응답 : @ResponseBody, HttpEntity(ResponseEntity)
스프링 부트 기본 메시지 컨버터
스프링 부트는 핸들러 매핑과 핸들러 어댑터처럼 다양한 메시지 컨버터를 제공하며
대상 클래스와 미디어의 타입을 체크하여 사용 여부를 결정한다.
0 = ByteArrayHttpMessageConverter
1 = StringHttpMessageConverter
2 = MappingJackson2HttpMessageConverter
- ByteArrayHttpMessageConverter : byte[] 데이터를 처리
- 클래스 타입 : byte[]
- 미디어 타입 : /
- 요청 ex) @RequestBody byte[] data
- 응답 ex) @ResponseBody return byte[] 쓰기 미디어타입 'application/octet-stream'
- StringHttpMessageConverter : String 문자로 데이터를 처리
- 미디어 타입 : /
- 요청 ex) @RequestBody String data
- 응답 ex) @ResponseBody return "ok" 쓰기 미디어타입 'text/plain'
- MappingJackson2HttpMessageConverter : application/json
- 클래스 타입 : 객체 or HashMap
- 미디어 타입 : application/json
- 요청 ex) @RequestBody HelloData data
- 응답 ex) @ResponseBody return helloData 쓰기 미디어타입 'application/json'
HTTP 요청 데이터 읽기
요청이 오면 컨트롤러에서 @RequestBody, HttpEntity 파라미터를 사용
-> 메시지 컨버터가 carRead()로 확인, 클래스타입와 미디어타입 확인
-> 조건을 만족하면 read()로 객체를 생성하고 반환
HTTP 요청 데이터 쓰기
컨트롤러에서 @RequestBody, HttpEntity 로 값이 반환
-> 메시지 컨버터가 canWrite()로 확인, 클래스 타입와 Accept 미디어타입 확인
-> 조건을 만족하면 write()로 HTTP 응답 메시지 바디에 데이터를 생성
요청 매핑 헨들러 어뎁터 구조
HTTP 메시지 컨버터는 핸들러 어댑터의 RequestMappingHandlerAdapter에 있다.
ArgumentResolver (정확한 명칭은 HandlerMethodArgumentResolver)
지금까지 애노테이션 기반 컨트롤러에서 다양한 파라미터(@RequestParam, @ModelAttribute, HttpEntity...)를 사용할 수 있었다.
ArgumentResolver 덕분에 유연하게 파라미터를 처리할 수 있다.
확장
스프링은 위의 기능들을 확장해서 사용할 수 있도록 인터페이스를 제공한다
- HandleMethodArgumentResolver
- HandleMethodReturnValueHandler
- HttpMessageConverter
하지만 스프링이 대부분의 기능을 제공해서 실제로 확장할 일은 거의 없다.
원하면 WebMvcConfigurer를 상속받아 스프링 빈으로 등록하면 된다.
'Develop > Spring' 카테고리의 다른 글
[Spring/MVC] 스프링 MVC 구조 (0) | 2024.03.21 |
---|---|
[Spring/MVC] MVC 프레임워크 (0) | 2024.03.21 |
[Spring/MVC] MVC 패턴 (0) | 2024.03.21 |
[Spring/MVC] HttpServletRequest,Response, HTTP 요청 데이터 (0) | 2024.03.21 |
[Spring/MVC] 웹 애플리케이션 (0) | 2024.03.20 |
댓글