plugins {
id 'org.springframework.boot' version '2.4.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head><body>
<ul>
<li>로그 출력
<ul>
<li><a href="/log-test">로그 테스트</a></li>
</ul>
</li>
<!-- -->
<li>요청 매핑
<ul>
<li><a href="/hello-basic">hello-basic</a></li>
<li><a href="/mapping-get-v1">HTTP 메서드 매핑</a></li>
<li><a href="/mapping-get-v2">HTTP 메서드 매핑 축약</a></li>
<li><a href="/mapping/userA">경로 변수</a></li>
<li><a href="/mapping/users/userA/orders/100">경로 변수 다중</a></li>
<li><a href="/mapping-param?mode=debug">특정 파라미터 조건 매핑</a></li>
<li><a href="/mapping-header">특정 헤더 조건 매핑(POST MAN 필요)</a></li>
<li><a href="/mapping-consume">미디어 타입 조건 매핑 Content-Type(POST MAN 필요)</a></li>
<li><a href="/mapping-produce">미디어 타입 조건 매핑 Accept(POST MAN 필요)</a></li>
</ul>
</li>
<li>요청 매핑 - API 예시
<ul>
<li>POST MAN 필요</li>
</ul>
</li>
<li>HTTP 요청 기본
<ul>
<li><a href="/headers">기본, 헤더 조회</a></li>
</ul>
</li>
<li>HTTP 요청 파라미터
<ul>
<li><a href="/request-param-v1?username=hello&age=20">요청 파라미터 v1</a></li>
<li><a href="/request-param-v2?username=hello&age=20">요청 파라미터 v2</a></li>
<li><a href="/request-param-v3?username=hello&age=20">요청 파라미터 v3</a></li>
<li><a href="/request-param-v4?username=hello&age=20">요청 파라미터 v4</a></li>
<li><a href="/request-param-required?username=hello&age=20">요청 파라미터 필수</a></li>
<li><a href="/request-param-default?username=hello&age=20">요청 파라미터 기본 값</a></li>
<li><a href="/request-param-map?username=hello&age=20">요청 파라미터 MAP</a></li>
<li><a href="/model-attribute-v1?username=hello&age=20">요청 파라미터 @ModelAttribute v1</a></li>
<li><a href="/model-attribute-v2?username=hello&age=20">요청 파라미터 @ModelAttribute v2</a></li>
</ul>
</li>
<li>HTTP 요청 메시지
<ul>
<li>POST MAN</li>
</ul>
</li>
<li>HTTP 응답 - 정적 리소스, 뷰 템플릿
<ul>
<li><a href="/basic/hello-form.html">정적 리소스</a></li>
<li><a href="/response-view-v1">뷰 템플릿 v1</a></li>
<li><a href="/response-view-v2">뷰 템플릿 v2</a></li>
</ul>
</li>
<li>HTTP 응답 - HTTP API, 메시지 바디에 직접 입력
<ul>
<li><a href="/response-body-string-v1">HTTP API String v1</a></li>
<li><a href="/response-body-string-v2">HTTP API String v2</a></li>
<li><a href="/response-body-string-v3">HTTP API String v3</a></li>
<li><a href="/response-body-json-v1">HTTP API Json v1</a></li>
<li><a href="/response-body-json-v2">HTTP API Json v2</a></li>
</ul>
</li>
</ul>
</body>
</html>
학습할 내용을 편리하게 참고하기 위한 Welcome 페이지
Spring Boot에 Jar 를 사용할 때,
/resources/static/index.hml
위치에index.html
파일을 두면 Welcome 페이지로 처리해준다.
📌 운영 시스템에서는 System.out.println() - Console
사용 X
→ 별도의 로깅 라이브러리를 사용
Spring Boot Library를 사용하면 Spring Boot Logging Library
(spring-boot-starter-logging)
가 함께 포함된다.
📌 Logback, Log4J, Log4J2 등 여러 Library를 통합해 Interface로 제공하는 것이 바로 SLF4J
📌 실무에서는 대부분 Logback
사용
Code
package hello.springmvc.basic;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/*
@Slf4j 를 사용하면 private final Logger log = LoggerFactory.getLogger(getClass()); 생략 가능
*/
@Slf4j
@RestController
public class LogTestController {
// private final Logger log = LoggerFactory.getLogger(getClass());
@RequestMapping("/log-test")
public String logTest(){
String name = "Spring";
//System.out.println("name = " + name);
//log.trace("trace log ="+name);
//이 방법은 권장 X, 불필요한 작업에 리소스를 사용할 여지가 있다.
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";
}
}
@Slf4j
private final Logger log = LoggerFactory.getLogger(getClass());
생략 가능application.properties
#전체 로그 레벨 설정(기본 info)
logging.level.root=info
#hello.springmvc 패키지와 그 하위 로그 레벨 설정
logging.level.hello.springmvc=debug
LEVEL
log.debug("data="+data);
log.debug("data={}", data);
참고
SLF4J - http://www.slf4j.org
Logback - http://logback.qos.ch
Spring Boot Log 기능 -https://docs.spring.io/spring-boot/docs/current/reference/html/spring-bootfeatures.html#boot-features-logging
package hello.springmvc.basic.requestmapping;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
public class MappingController {
@RequestMapping("/hello-basic")
public String helloBasic() {
log.info("helloBasic");
return "ok";
}
/**
* method 특정 HTTP 메서드 요청만 허용
* GET, HEAD, POST, PUT, PATCH, DELETE
*/
@RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
public String mappingGetV1() {
log.info("mappingGetV1");
return "ok";
}
/**
* 편리한 축약 애노테이션 (코드보기)
*
* @GetMapping
* @PostMapping
* @PutMapping
* @DeleteMapping
* @PatchMapping
*/
@GetMapping(value = "/mapping-get-v2")
public String mappingGetV2() {
log.info("mapping-get-v2");
return "ok";
}
/**
* PathVariable 사용
* 변수명이 같으면 생략 가능
*
* @PathVariable("userId") String userId -> @PathVariable String userId
*/
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable String userId) {
log.info("mappingPath userId={}", userId);
return "ok";
}
/**
* 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";
}
/**
* 특정 헤더로 추가 매핑
* 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";
}
/**
* 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";
}
/**
* 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";
}
}
@RequestMapping("/hello-basic")
public String helloBasic() {
log.info("helloBasic");
return "ok";
}
/hello-basic
URL이 호출되면 해당 메서드가 실행되도록 Mappingex) @RequestMapping({"/hello-basic", "/hello-go"})
/**
* method 특정 HTTP 메서드 요청만 허용
* GET, HEAD, POST, PUT, PATCH, DELETE
*/
@RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
public String mappingGetV1() {
log.info("mappingGetV1");
return "ok";
}
@RequestMapping
에 method 속성으로 HTTP Method
지정 X → HTTP Method
와 무관하게 호출 /**
* 편리한 축약 애노테이션 (코드보기)
*
* @GetMapping
* @PostMapping
* @PutMapping
* @DeleteMapping
* @PatchMapping
*/
@GetMapping(value = "/mapping-get-v2")
public String mappingGetV2() {
log.info("mapping-get-v2");
return "ok";
}
/**
* PathVariable 사용
* 변수명이 같으면 생략 가능
* @PathVariable("userId") String userId -> @PathVariable String userId
* 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";
}
@PathVariable
을 사용하면 매칭 되는 부분을 조회 가능@PathVariable의 이름
== Parameter 이름
→ 생략 가능 /**
* 특정 헤더로 추가 매핑
* 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";
}
/**
* 특정 헤더로 추가 매핑
* 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";
}
/**
* 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";
}
/**
* 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";
}
package hello.springmvc.basic.requestmapping;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@Slf4j
@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 = " + 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;
}
}
package hello.springmvc.basic.request;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.jni.Local;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.function.ServerRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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("locale={}", locale);
log.info("headerMap={}", headerMap);
log.info("header host={}", host);
log.info("myCookie={}", cookie);
return "ok";
}
}
필수 값 여부: required
기본 값 속성: defaultValue
필수 값 여부: required
기본 값: defaultValue
MultiValueMap
- 하나의 키에 여러 값을 받을 수 있다.
- HTTP header, HTTP 쿼리 파라미터와 같이 하나의 키에 여러 값을 받을 때 사용한다.
📌 클라이언트에서 서버로 요청 데이터를 전달할 때는 주로 3가지 방법을 사용한다.
GET Query Parameter 이든 POST HTML Form 이든 형식이 같으므로 구분없이 조회 가능
→ 간단하게 요청 파라미터(request parameter) 조회라 함
package hello.springmvc.basic.request;
import hello.springmvc.basic.HelloData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
@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");
}
@ResponseBody
// 없으면 return 하는 string으로 Viewresolver를 통해 해당하는 이름의 view를 찾게됨, 이걸 쓰면 response body부분에 return 하는 값을 넣어서 반환
@RequestMapping("/request-param-v2")
public String requestParamV2(@RequestParam("username") String memberName, @RequestParam("age") int memberAge) {
log.info("username = {}, age = {}", memberName, memberAge);
return "ok";
}
//HTTP 파라미터명과 변수명이 같으면 ("~~") 부분 생략 가능!
@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParamV3(@RequestParam String username, @RequestParam int age) {
log.info("username = {}, age = {}", username, age);
return "ok";
}
//기본 자료형에 대해 @RequestParam 생략 가능; 근데 써주는게 좀 더 직관적으로 이해하기 편함
@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age) {
log.info("username = {}, age = {}", username, age);
return "ok";
}
// Java에선 기본형(int, byte, char, short, long, float, double, boolean)에
// null을 입력 할 수 없음
// null을 int형에 넣으려면 Integer으로 선언
// required = true 일 때, /request-param-required/username= 와 같이 빈문자가 입력되면 입력이 된 것으로 받아들임
@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(@RequestParam(required = true) String username, @RequestParam(required = false) Integer age) {
log.info("username = {}, age = {}", username, age);
return "ok";
}
// 사실상 defaultValue를 사용하면 required가 의미가 없음
// 빈 문자열이 들어오더라도 defaultValue가 적용됨
// null이 들어와도 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";
}
@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";
}
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
log.info("username = {}, age = {}", helloData.getUsername(), helloData.getAge());
log.info("helloData = {}", helloData);
return "ok";
}
//@ModelAttribute 는 생략 가능하지만 혼돈이 생길 수도 있음
@ResponseBody
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(HelloData helloData) {
log.info("username = {}, age = {}", helloData.getUsername(), helloData.getAge());
log.info("helloData = {}", helloData);
return "ok";
}
}
@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");
}
HttpServletRequest
가 제공하는 방식으로 Request Parameter 조회@RequestParam
@ResponseBody
// @ResponseBody가 없으면 return 하는 string으로 Viewresolver를 통해 해당하는 이름의 view를 찾음
// @ResponseBody를 추가하면 response body에 return하는 값을 넣어서 반환
@RequestMapping("/request-param-v2")
public String requestParamV2(@RequestParam("username") String memberName, @RequestParam("age") int memberAge) {
log.info("username = {}, age = {}", memberName, memberAge);
return "ok";
}
@ReqeustParam name 속성 생략
//HTTP Parameter 이름과 변수 이름이 같으면 @RequestParma의 name 속성 생략 가능!
@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParamV3(@RequestParam String username, @RequestParam int age) {
log.info("username = {}, age = {}", username, age);
return "ok";
}
생략 가능
@RequestParam 생략
//기본 자료형에 대해 @RequestParam 생략 가능
//히지만, 써주는 것이 직관적으로 이해하기 편함
@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age) {
log.info("username = {}, age = {}", username, age);
return "ok";
}
생략 가능
Required
// Java에선 기본형(int, byte, char, short, long, float, double, boolean)에
// null을 입력 할 수 없음
// null을 int형에 넣으려면 Integer으로 선언
// "" != null
@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(@RequestParam(required = true) String username, @RequestParam(required = false) Integer age) {
log.info("username = {}, age = {}", username, age);
return "ok";
}
@RequestParam.required
- Parameter 필수 여부
- Default = true
⚠️ 주의
// 사실상 defaultValue를 사용하면 required가 의미가 없음
// 빈 문자열이 들어오더라도 defaultValue가 적용됨
// null이 들어와도 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";
}
@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";
}
Map
, MultiValueMap
으로 조회 가능Parameter
값이 하나가 아니라면, MultiValueMap
사용@ModelAttribute
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
log.info("username = {}, age = {}", helloData.getUsername(), helloData.getAge());
log.info("helloData = {}", helloData);
return "ok";
}
Property
를 찾는다. Property
객체에 getUsername() , setUsername() 메서드가 있으면, 이 객체는 username 이라는 프로퍼티를 가지고 있다.
@ModelAttribute 생략
//@ModelAttribute 는 생략 가능하지만 혼돈이 생길 수도 있음
@ResponseBody
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(HelloData helloData) {
log.info("username = {}, age = {}", helloData.getUsername(), helloData.getAge());
log.info("helloData = {}", helloData);
return "ok";
}
@ModelAttribute
생략 가능
@RequestParam 또는 @ModelAttribute 생략시 다음 규칙 적용
- String , int , Integer 같은 단순 타입 ⇒ @RequestParam
- 나머지(argument resolver 로 지정해둔 타입 외) ⇒ @ModelAttribute
HTTP API
에서 주로 사용
ex) JSON, XML, TEXT ..
데이터 형식은 주로 JSON
사용
HTTP Message Body
를 통해 직접 데이터가 넘어오는 경우
RequestBodyStringController
package hello.springmvc.basic.request;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
@Slf4j
@Controller
public class RequestBodyStringController {
@PostMapping("/request-body-string-v1")
public void requestBodyStringV1(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) throws IOException {
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody = {}", messageBody);
responseWriter.write("ok");
}
/**
* HttpEntity: HTTP header, body 정보를 편리하게 조회
* - 메시지 바디 정보를 직접 조회(@RequestParam X, @ModelAttribute X)
* - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
*
* 응답에도 HttpEntity 사용 가능
* - 메시지 바디 정보 직접 반환(view 조회X)
* - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
*/
@PostMapping("/request-body-string-v3")
public void requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException {
String messageBody = httpEntity.getBody();
log.info("messageBody = {}", messageBody);
//responseWriter.write("ok");
}
/**
* @RequestBody
* - 메시지 바디 정보를 직접 조회(@RequestParam X, @ModelAttribute X)
* - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
*
* @ResponseBody
* - 메시지 바디 정보 직접 반환(view 조회X)
* - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
*/
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) throws IOException {
log.info("messageBody = {}", messageBody);
return "ok";
}
}
Spring MVC가 지원하는 Parameter
InputStream(Reader)
OutputStream(Writer)
HttpEntity
RequestEntity
ResponseEntity
@RequestBody
📌 Header정보가 필요하다면 HttpEntity
또는 @RequestHeader
사용
Request Paramter vs HTTP Message Body
@RequestParam
,@ModelAttribute
→ Request Paramter를 조회하는 기능@RequestBody
→ HTTP Message Body를 직접 조회하는 기능@ResponseBody
→ 응답 결과를 HTTP Message Body에 담아서 전달
- View 사용 X
package hello.springmvc.basic.request;
import com.fasterxml.jackson.databind.ObjectMapper;
import hello.springmvc.basic.HelloData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* {"username":"hello", "age":20}
* content-type: application/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 = {}, userage = {}",helloData.getUsername(),helloData.getAge());
response.getWriter().write("ok");
}
/**
* @RequestBody
* HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
*
* @ResponseBody
* - 모든 메서드에 @ResponseBody 적용
* - 메시지 바디 정보 직접 반환(view 조회X)
* - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
*/
@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 생략 불가능(@ModelAttribute 가 적용되어 버림)
* HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (contenttype:
application/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";
}
@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> data) throws IOException {
HelloData helloData = data.getBody();
log.info("username = {}, age = {}",helloData.getUsername(),helloData.getAge());
return "ok";
}
/**
* @RequestBody 생략 불가능(@ModelAttribute 가 적용되어 버림)
* HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (contenttype:
application/json)
*
* @ResponseBody 적용
* - 메시지 바디 정보 직접 반환(view 조회X)
* - HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter 적용
(Accept: application/json)
*/
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData helloData) throws IOException {
log.info("username = {}, age = {}",helloData.getUsername(),helloData.getAge());
return helloData;
}
}
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 = {}, userage = {}",helloData.getUsername(),helloData.getAge());
response.getWriter().write("ok");
}
/**
* @RequestBody
* HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
*
* @ResponseBody
* - 모든 메서드에 @ResponseBody 적용
* - 메시지 바디 정보 직접 반환(view 조회X)
* - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
*/
@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 생략 불가능(@ModelAttribute 가 적용되어 버림)
* HttpMessageConverter 사용
-> MappingJackson2HttpMessageConverter (contenttype: application/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";
}
@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> data) throws IOException {
HelloData helloData = data.getBody();
log.info("username = {}, age = {}",helloData.getUsername(),helloData.getAge());
return "ok";
}
@RequestBody에 직접 만든 객체를 지정할 수 있다.
HttpEntity
, @RequestBody
를 사용하면,
→ HTTP Message Converter가 HTTP Message Body의 내용을 지정한 문자나 객체 등으로 변환
📌 HTTP Message Converter는 문자 뿐만 아니라 JSON
도 객체로 변환
/**
* @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 helloData) throws IOException {
log.info("username = {}, age = {}",helloData.getUsername(),helloData.getAge());
return helloData;
}
@ResponseBody
를 사용하면 객체를 HTTP Message Body
에 직접 넣어줄 수 있다.HttpEntity
역시 사용 가능Classpath
의 특정 디렉토리에 있는 정적 리소스 제공보관
하는 장소 + Classpath의 시작 경로
Template를 통해 동적으로 HTML 생성
Spring Boot는 기본 View Template 경로 제공
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 modelAndView = new ModelAndView("/response/hello")
.addObject("data","hello!");
return modelAndView;
}
//@ResponseBody
//이 경우 View를 찾지 않고 Response Body에 직접 문자열을 실어서 반환
@RequestMapping("/response-view-v2")
public String responseViewV2(Model model){
model.addAttribute("data","hello!");
return "response/hello";
}
/*
@Controller 사용 && HttpServletResponse ,OutputStream 사용 X
-> 요청 URL을 논리 View 이름으로 사용
**명시성이 떨어짐 -> 권장하지 않음
*/
@RequestMapping("/response/hello")
public void responseViewV3(Model model){
model.addAttribute("data","hello!");
}
}
@Controller
사용 + HTTP Message Body를 처리하는 Parameter (HttpServletResponse , OutputStream(Writer))
가 없으면 요청 URL을 참고해서 논리 View 이름으로 사용→ HTTP Message Body에 JSON
같은 형식으로 Data 입력
package hello.springmvc.basic.response;
import hello.springmvc.basic.HelloData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Controller
@Slf4j
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() throws IOException {
return new ResponseEntity<>("ok", HttpStatus.OK);
}
@ResponseBody
@GetMapping("/response-body-string-v3")
public String responseBodyV3() throws IOException {
return "ok";
}
@GetMapping("/response-body-json-v1")
public ResponseEntity<HelloData> responseJsonV1() throws IOException {
HelloData helloData = new HelloData();
helloData.setUsername("userA");
helloData.setAge(10);
return new ResponseEntity<>(helloData,HttpStatus.OK);
}
@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/response-body-json-v2")
public HelloData responseJsonV2() throws IOException {
HelloData helloData = new HelloData();
helloData.setUsername("userA");
helloData.setAge(10);
return helloData;
}
}
HttpServletResponse
객체를 통해서 HTTP Message Body에 직접 응답 메시지 입력HttpEntity
는 HTTP Message의 Header, Body 정보를 가지고 있다.ResponseEntity
는 추가로 HTTP 응답 코드
를 설정 가능ResponseEntity
를 사용
@RestController
- @Controller 대신 @RestController 사용 → 해당 Controller에 모두
@ResponseBody
적용
- View Template 사용 X
- HTTP Message Body에 직접 데이터 입력
- Rest API(HTTP API)를 만들 때 사용
- 참고로 @ResponseBody 는 클래스 레벨에 두면 전체에 메서드에 적용
📌 JSON 데이터
를 HTTP Message Body에서 직접 읽거나 쓰는 경우
→ HTTP Message Converter
를 사용
@ResponseBody 사용 원리
StringHttpMessageConverter
MappingJackson2HttpMessageConverter
참고
응답의 경우 클라이언트의 HTTP Accept Header & 서버의 Controller 반환 타입 정보를 조합해서 HttpMessageConverter 선택
Spring MVC는 아래 경우 HTTP Message Converter 적용
- HTTP 요청 → @RequestBody , HttpEntity(RequestEntity)
- HTTP 응답 → @ResponseBody , HttpEntity(ResponseEntity)
package org.springframework.http.converter;
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
}
canRead()
, canWrite()
Message Converter
가 해당 클래스, 미디어타입을 지원하는지 체크read()
, write()
Message Converter
를 통해서 메시지를 읽고 쓰는 기능Spring Boot 기본 Message Converter (일부 생략)
- ByteArrayHttpMessageConverter
- StringHttpMessageConverter
- MappingJackson2HttpMessageConverter
// content-type: application/json
@RequestMapping
void hello(@RequetsBody String data) {}
// content-type: application/json@RequestMapping
void hello(@RequetsBody HelloData data) {}
ex) @RequestBody 의 대상 클래스 (byte[] , String , HelloData)
ex) text/plain , application/json , /
canRead()
조건 만족 → read()
를 호출 → 객체 생성 & 반환ex) text/plain , application/json , /
canWrite()
조건 만족 → write()
호출 → HTTP Response Message Body
에 데이터 입력RequestMappingHandlerAdapter
호출RequestMappingHandlerAdapter
는Parameter & Annotation
기반으로 필요한Argument Resolver
호출Argument Resolver
는 HTTP Message Converter를 호출해 필요한 객체를 생성 및 반환Argument Resolver
는 Controller에 필요한 전달 데이터 생성public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
@Nullable WebDataBinderFactory binderFactory) throws Exception;
}
ArgumentResolver
의 supportsParameter()
호출resolveArgument()
호출 → HTTP Message Converter를 호출해 실제 객체 생성public interface HandlerMethodReturnValueHandler {
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(@Nullable Object returnValue,
MethodParameter returnType,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception;
}
ReturnValueHandler
의 supportsReturnType()
호출
처리할 수 있다면 handleReturnValue()
호출
→ HTTP Message Converter
를 통해Response Message
생성
참고