스프링 부트를 사용하면 자동으로 포함됨
사용하는 로깅 라이브러리
SLF4J
→ 인터페이스Logback
→ 구현체로그 선언
private final Logger log = LoggerFactory.getLogger(getClass());
롬복 @Slf4j
를 사용하면 위의 로그 선언 없이도 log를 사용할 수 있음로그 호출
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);
로그 출력 포맷
시간
—로그 레벨
—프로세스 ID
—쓰레드명
—클래스명
—로그 메시지
로그 레벨
로그 레벨 설정
application.properties에서 설정할 수 있음 (defalut = info)
# 전체 로그 레벨 설정
logging.level.root=trace
# hello.springmvc 패키지와 하위 패키지 로그 레벨 설정
logging.level.hello.springmvc=debug
로그 레벨을 설정하면 설정한 로그 레벨보다 하위 로그는 출력되지 않음
ex) 로그 레벨 : debug → trace 제외한 나머지 로그 모두 출력
ex) 로그 레벨 : warn → warn, error 로그만 출력
logging.level.root=trace
→ 프로젝트의 모든 라이브러리의 로그 레벨을 설정하기 때문에 엄청나게 많은 로그가 출력됨 (debug만 해도 엄청 많다)
올바른 로그 사용법
(X)
(O)
로그의 장점
다중 설정이 가능
/hello-basic
”, “/hello-go
”})@GetMapping("/mapping/{userId}")
public String findUser(@PathVariable("userId") String data) {
return "data = " + data;
}
@GetMapping("/mapping/{userId}")
public String findUser(@PathVariable String userId) {
return "get userId = " + userId;
}
@GetMapping("/mapping/{userId}/{username}")
public String findUser(@PathVariable String userId, @PathVariable String username) {
}
특정 파라미터가 들어와야 실행됨 (잘 사용x)
// 쿼리 파라미터에 params 가 포함되어야 함
@GetMapping(value = "/mapping-param", params = "mode=debug")
public String mappingParam() {
log.info("mappingParam");
return "ok";
}
key=value로 지정
@GetMapping(value = "/mapping-header", headers = "mode=debug")
public String mappingHeader() {
log.info("mappingHeader");
return "ok";
}
헤더 이름 - mode
헤더 값 - debug
미디어 타입 조건 매핑
consumes
→ ~~ 타입의 데이터를 사용하는 컨트롤러@PostMapping(value = "/mapping-consume", consumes = MediaType.APPLICATION_JSON_VALUE)
public String mappingConsumes() {
log.info("mappingConsumes");
return "ok";
}
produces
→ ~~타입의 데이터를 생산하는 컨트롤러@PostMapping(value = "/mapping-produce", produces = MediaType.TEXT_PLAIN_VALUE)
public String mappingProduce() {
log.info("mappingProduce");
return "ok";
}
@Slf4j
@RestController
public class RequestHeaderController {
@RequestMapping("/headers")
public String header(
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("host={}", host);
log.info("cookie={}", cookie);
return "ok";
}
}
하나의 키로 여러 개의 값을 받을 수 있음
ex) accept=[text/html,application/xhtml+xml,….]
// 옛날 방식
@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");
}
// @RequestParam 사용
@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에 파라미터 이름을 생략해도 됨 → 권장
// 요청 파라미터와 같은 이름 사용 시 -> @RequestParam 괄호 내부 생략
@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParamV3(@RequestParam String username, @RequestParam int age) {
log.info("username={}, age={}", username, age);
return "ok";
}
required
옵션primitive type
이면 null을 넣을 수 없음 → 500 에러 발생@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(
@RequestParam(required = true, defaultValue = "no") String username,
@RequestParam(required = false, defaultValue = "0") int age
) {
log.info("username={}, age={}", username, age);
return "ok";
}
파라미터 값이 빈 문자인 경우
에도 defaultValue로 설정한 값 적용됨@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age) {
log.info("username={}, age={}", username, age);
return "ok";
}
파라미터를 바인딩한 객체
를 매개변수로 받음@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
return "ok";
}
HelloData 객체 생성
HelloData 객체의 프로퍼티를 찾음
setUsername()
메소드를 찾음setter를 호출해서 값을 바인딩
함바인딩 오류
(BindException)@ModelAttribute(”helloData”)
나머지 → @ModelAttribute
cf) argument resolver로 지정된 타입은 반드시 @ModelAttribute를 써줘야 함 (뒤에서 자세히)
HTTP API
에서 주로 사용json
@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("message body = {}", 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("message body = {}", messageBody);
responseWriter.write("ok");
}
HTTP 헤더와 바디
를 간편하게 조회할 수 있는 클래스@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {
String messageBody = httpEntity.getBody();
HttpHeaders headers = httpEntity.getHeaders();
log.info("message body = {}", messageBody);
log.info("headers = {}", headers);
return new HttpEntity<>("ok");
}
"ok"
);RequestEntity
ResponseEntity
메시지 컨버터
HTTP 메시지 바디 → String, 객체 변환시켜줌 (뒤에서 자세히)
메시지 바디만
편리하게 조회하는 어노테이션@RequestHeader
또는 HttpEntity를 사용해 조회할 수 있음@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
log.info("message body = {}", messageBody);
return "ok";
}
JSON → 객체
변환 과정InputStream → String → Object
@RequestMapping("/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("String Data : {}", messageBody);
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
log.info("Object Data : {}", helloData);
response.getWriter().write("ok");
}
@ResponseBody
@RequestMapping("/request-body-json-v2")
public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException {
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
log.info("Object Data : {}", helloData);
return "ok";
}
권장
메시지 컨버터
가 바디의 내용을 문자 또는 객체로 변환해주기 때문임HTTP 요청
시 Content-Type
이 application/json인지 꼭 확인해야 함매개변수로 객체를 받는데 어노테이션을 생략하면 @ModelAttribute가 적용됨
@ResponseBody
@RequestMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData helloData) throws IOException {
log.info("Object Data : {}", helloData);
return "ok";
}
@ResponseBody
를 사용해 객체를 메시지 바디에 넣어서 보낼 수도 있음@ResponseBody
@RequestMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData helloData) throws IOException {
helloData.setAge(100);
log.info("Object Data : {}", helloData);
return helloData;
}
@ResponseBody
@RequestMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) throws IOException {
HelloData helloData = httpEntity.getBody();
log.info("Object Data : {}", helloData);
return "ok";
}
@ResponseBody
를 사용해 HttpEntity를 메시지 바디에 넣어서 보낼 수도 있음ResponseEntity
를 사용스프링 서버에서 응답 데이터를 만드는 3가지 방법
resources
)에 다음 4가지 디렉토리에 저장된 정적 리소스를 제공함/static
/public
/resources
/META_INF/resources
ex) src/main/resources
/static
/basic/hello.html
→ localhost:8080/basic/hello.html
resources
/templates
ex) 컨트롤러 메소드 return “/hi/hello”; (@ResponseBody 없는 그냥 컨트롤러)
→ src/main/resources
/templates
/hi/hello.html
해당 경로에 hello.html 파일 있으면 렌더링하여 보여줌
String
HTTP API
같은 경우 메시지 바디에 HTML이 아니라 JSON 형식의 데이터를 넘겨줘야 함
@GetMapping("/response-body-string-v1")
public void responseBodyV1(HttpServletResponse response) throws IOException {
response.getWriter().write("ok");
}
@ResponseBody
@GetMapping("/response-body-string-v3")
public String responseBodyV3() throws IOException {
return "ok";
}
@ResponseStatus(상태 상수)로 HTTP 상태코드 지정할 수 있음
@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/response-body-json-v2")
public HelloData responseBodyJsonV2() throws IOException {
HelloData helloData = new HelloData();
helloData.setAge(10);
helloData.setUsername("joo");
return helloData;
}
@GetMapping("/response-body-string-v2")
public ResponseEntity<String> responseBodyV2() throws IOException {
return new ResponseEntity<>("hi", HttpStatus.OK);
}
@GetMapping("/response-body-json-v1")
public ResponseEntity<HelloData> responseBodyJsonV1() throws IOException {
HelloData helloData = new HelloData();
helloData.setAge(10);
helloData.setUsername("joo");
return new ResponseEntity<>(helloData, HttpStatus.OK);
}
메시지 바디
↔자바 코드
변환해주는 역할
❗ 스프링 타입 컨버터와 다름!! 헷갈리지 말 것
요청 & 응답 시 HTTP 메시지 바디와 자바 코드를 변환할 때 메시지 컨버터가 동작함
@RequestBody
& HttpEntity (RequestEntity)
@ResponseBody
& HttpEntity (ResponseEntity)
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
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()
read()
& write()
스프링 부트에 기본으로 등록되는
HttpMessageConverter 구현체
우선순위
에 따라 순차적으로 조회 (중요한것 3개)ByteArray
HttpMessageConverter
ex) byte[] → application/json
ex) text/plain → byte[]
String
HttpMessageConverter
ex) String → text
ex) application/json → String
MappingJackson2
HttpMessageConverter
application/json
⇒ byte[], string을 제외한 나머지 타입
은 application/json으로만 변환될 수 있다
⇒ application/json은 byte[], string을 제외한 나머지 타입으로만 변환될 수 있다.
ex) HelloData → application/json (O)
ex) HelloData → text/plain (X)
ex) application/json → HelloData (O)
ex) application/json → String (O)
@RequestBody
, HttpEntity 사용canRead()
호출하여 읽을 수 있는지 판단@ResponseBody
, HttpEntity 사용하여 리턴canWrite()
호출하여 메시지 바디에 쓸 수 있는지 판단ArgumentResolver
ReturnValueHandler
ArgumentResolver
호출HTTP 메시지 컨버터를 호출
하여 매개변수 생성ReturnValueHandler
에게 넘겨줌HTTP 메시지 컨버터를 호출
하여 메시지 바디에 들어갈 데이터를 처리
컨트롤러가 필요로 하는 매개변수를 생성
해주는 인터페이스
HandlerMethod
ArgumentResolverpublic interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
HTTP 메시지 컨버터를 호출
하여 객체를 생성함원한다면 직접 인터페이스를 구현하여 원하는 ArgumentResolver를 만들 수 있음 (MVC 2편 로그인에서 진행)
핸들러가 넘겨준
다양한 리턴 타입의 결과를 처리
하는 인터페이스
HandlerMethod
ReturnValueHandlerpublic interface HandlerMethodReturnValueHandler {
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
return String
return Object
⇒ 스프링은 10개가 넘는 ReturnValueHandler를 제공함
ModelAndView, String, @ResponseBody, HttpEntity…
HTTP 메시지 컨버터를 호출
하여 메시지 바디에 들어갈 데이터를 처리RequestResponseBodyMethod
Processor → @RequestBody, @ResponseBody 사용 시HttpEntity
Processor → HttpEntity 사용 시사실 확장할 일은 많지 않음..