SpringBoot 기반으로 공부용 입니다.
로깅의 필요성과 사용 방법, Spring MVC의 기본 기능인 HTTP 요청, 응답 메시지 처리를 알아보는 '공부용'입니다. 인프런의 김영한 님의 강의인 '스프링 MVC 1편'을 보고 정리한 내용입니다. https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard
Description
로깅, HTTP의 요청, 응답에 대한 Spring의 기본 기능 등을 연습해봅니다.
Environment
IntelLiJ
Java 11
Gradle
Jar
Period
Start : 2021/12/10~
End : 2021/12/14
2021/12/10
- Logging
운영 시스템에서는 System.out.println()과 같은 시스템 콘솔을 사용해서 출력하지 않고 별도의 Logging Library를 사용해서 출력한다. 로그를 사용하면 별도의 파일, 네트워크 등에 결과를 남길 수 있고, 일정 용량이 넘어가면 다른 파일을 생성하여 확장성있게 유지할 수 있다.
Spring Boot Library를 사용하면 spring-boot-starter-logging Library가 함께 포함된다. Library는 Logback, Log4J, Log4J2 등 많이 있는데, 그것을 통합해서 Interface로 제공하는 것이 SLF4J Library이다. 따라서 Interface는 SLF4J고 구현체로 Logback과 같은 Log Library를 선택하면 된다.
- 로그 선언의 방식
private Logger log = LoggerFactory.getLogger(getClass());
private static final Logger log = LoggerFactory.getLogger(Xxx.class);
@Slf4j (Lombok 사용시)
- 로그 호출 방식
log.info("hello"), log.info("log username={}", username), log.trace("trace username={}, age={}", username, age)과 같은 방식으로 호출한다.
여기서 주의해야할 점이 있다면 log.info("log username=" + username)과 같은 더하기 연산자를 쓰면 안된다는 것이다. info의 처리보다 더하기 연산자가 먼저 실행되어 로그 레벨에 따라 출력이 안되는 경우에도 더하기 연산자의 결과가 메모리를 잡아먹게 된다. 따라서 연산자를 사용하는 것 보다 log.info("log username={}", username)과 같이 Parameter를 넘겨주는 방식으로 해야된다.
- @RestController
@Controller는 반환 값이 String이면 ViewName으로 인식된다. 따라서 View를 찾고 rendering된다.
@RestController는 반환 값을 HTTP MessageBody에 바로 입력한다. (@RestController를 타고 들어가보면 @ResponseBody가 있다.) 따라서 실행 결과로 ok메시지를 얻을 수 있다.
- Test
로그가 출력되는 포멧을 확인할 수 있다. (시간, 로그 레벨, 프로세스 ID, 쓰레드 명, 클래스 명, 로그 메시지)
LEVEL : TRACE > DEBUG > INFO > WARN > ERROR
보통 개발 서버는 debug, 운영 서버는 info를 출력한다.
- 로그 레벨 설정
- 로그 사용시 장점
- 쓰레드 정보, 클래스 이름 같은 부가 정보를 함께 볼 수 있고, 출력 모양 조정 가능
- 로그 레벨에 따라 개발 서버에서는 모든 로그를 출력하고, 운영 서버에서는 출력하지 않게 상황에 따라 조절 가능
- 시스템 아웃 콘솔에만 출력하는 것이 아니라 파일, 네트워크 등 별도의 위치에 남길 수 있다.
- 성능도 System.out보다 좋다.(내부 버퍼링, 멀티 쓰레드 등) -> 수 십배의 속도 차이가 날 수 있다.
- PathVariable(경로 변수)사용
만일 @PathVariable의 이름과 Parameter의 이름이 같으면 생략할 수 있다.
최근 HTTP API는 다음과 같이 리소스 경로에 식별자를 넣는 스타일을 선호한다.
/mapping/{userId}/orders/{orderId}
- 요청 매핑 - API 예시 회원 관리를 HTTP API로 만든다고 하면 다음과 같이 만들 수 있다.
- 회원 목록 조회 : GET /users
- 회원 등록 : POST /users
- 회원 조회 : GET /users/{userId}
- 회원 수정 : PATCH /users/{userId}
- 회원 삭제 : DELETE /users/{userId}
회원 조회 Controller를 만든다면 다음과 같이 만들 수 있다. (Class Level에 @RequestMapping("/mapping/users") 선언하여 경로 통합)
2021/12/11
- HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form
클라이언트에서 서버로 요청 데이터를 전달할 때는 주로 3가지 방법을 사용한다.
- GET - 쿼리 파라미터
- /url?username=hello&age=20
- Message Body 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달한다.
- ex) 검색, 필터, 페이징에서 많이 사용하는 방식이다.
- POST - HTML Form
- content-type:application/x-www-form-urlencoded
- Message Body에 쿼리 파라미터 형식으로 전달한다. username=hello&age=20
- ex) 회원 가입, 상품 주문에서 많이 사용하는 방식이다.
- HTTP Message Body에 데이터를 직접 담아서 요청
- HTTP API에서 주로 사용(JSON, XML, TEXT)
- 데이터 형식은 주롤 JSON을 사용한다.
- POST, PUT, PATCH를 사용할 수 있다.
- 요청 파라미터 - 쿼리 파라미터, HTML Form
HttpServletRequest의 request.getParameter()를 사용하면 GET-쿼리 파라미터, POST, HTML Form 전송에서 Parameter를 뽑아낼 수 있다.
- HTTP 요청 파라미터 - @RequestParam
스프링이 제공하는 @RequestParam을 이용하면 파라미터 이름으로 바인딩을 해준다.
2021/12/12
- HTTP 요청 파라미터 - @ModelAttribute
실제 개발을 하면 요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어주어야 한다.
스프링은 다음과 같은 과정을 자동화해주는 @ModelAttribute 기능을 제공한다.
우선 요청 파라미터를 바인딩 받을 객체 HelloData를 생성하자.
@ModelAttribute가 있으면 다음과 같이 실행된다.
- HelloData 객체를 생성한다.
- 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾는다. 그리고 해당 프로퍼티의 setter를 호출하여 파라미터 값을 바인딩한다.
물론 @ModelAttribute를 생략할 수 있지만 @RequestParam과 혼란을 야기할 수 있기 때문에 추천하는 방법은 아니다.
스프링은 생략시 다음과 같은 규칙을 적용한다고 알려져 있다.
- String, int, Integer 같은 단순 타입 = @RequestParam
- 나머지 = @ModelAttribute(argument resolver로 지정해둔 타입 외)
2021/12/13
- HTTP 요청 파라미터 - 단순 텍스트
HTTP message body에 데이터를 직접 담아서 요청
- HTTP API에서 주로 사용, JSON, XML, TEXT 등
- 데이터 형식은 주로 JSON 사용
- POST, PUT, PATCH
이 때 주의해야할 점은 요청 파라미터와는 다르게, HTTP message body를 통해 데이터가 직접 넘어오는 경우는 @RequestParam, @ModelAttribute를 사용할 수 없다. 물론 HTML Form형식으로 넘어오는 경우는 message body에 요청 파라미터가 있기 때문에 사용 가능하다.
가장 단순한 텍스트 메시지를 HTTP message body에 담아서 전송하고 읽어보자. HTTP message body의 데이터를 InputStream을 사용하여 읽을 수 있다. 또한 InputStream(Reader), OutputStream(Writer)를 사용하여 HTTP 요청 메시지 바디를 직접 조회, HTTP 응답 메시지 바디에 직접 결과 출력을 할 수 있다.
- HttpEntity
HttpEntity : HTTP header, body 정보를 편리하게 조회할 수 있다.
- message body 정보를 직접 조회(요청 파라미터와 관련 없으므로 @RequestParam X, @ModelAttribute X)
- HttpMessageConverter 사용 -> StringHttpMessageConverter를 적용한다.
응답에서도 HttpEntity 사용 가능
- message body 정보 직접 반환(view 조회X)
- HttpMessageConverter 사용 -> StringMessageConverter를 적용한다.
@RequestBody
- message body 정보를 직접 조회(요청 파라미터와 관련 없으므로 @RequestParam X, @ModelAttribute X)
- HttpMessageConverter 사용 -> StringHttpMessageConverter를 적용한다.
- 만일 헤더 정보가 필요하다면 HttpEntity를 사용하거나 @RequestHeader를 사용하면 된다.
@ResponseBody
- message body 정보 직접 반환(view 조회X)
- HttpMessageConverter 사용 -> StringMessageConverter를 적용한다.
요청 파라미터 vs HTTP Message body
- 요청 파라미터를 조회하는 기능 : @RequestParam, @ModelAttribute
- HTTP Message body를 직접 조회하는 기능 : @RequestBody
- HTTP 요청 메시지 - JSON
- HttpServletRequest를 사용해서 직접 HTTP Message body에서 데이터를 읽어와 문자로 변환한다.
- 문자로된 JSON Data를 Jackson 라이브러리인 objectMapper를 사용해서 자바 객체로 변환한다.
- @RequestBody를 사용해서 HTTP Message에서 데이터를 꺼내서 message body에 저장한다.
- 문자로된 JSON Data를 Jackson 라이브러리인 objectMapper를 사용해서 자바 객체로 변환한다.
- @RequestBody 요청 : JSON 요청 -> HTTP Message Converter -> 객체
- @ResponseBody 응답 : 객체 -> HTTP Message Converter -> JSON 응답
- ResponseViewController - 뷰 템플릿을 호출하는 컨트롤러
String을 반환하는 경우 - View or HTTP Message -@ResponseBody가 없으면 response/hello로 뷰 리졸버가 실행되어서 뷰를 찾고, 렌더링한다. -@ResponseBody가 있으면 뷰 리졸버가 실행되지 않고, HTTP Message body에 직접 response/hello라는 문자가 입력된다. 여기서 뷰의 논리 이름인 response/hello를 반환하면 다음 경로의 뷰 템플릿이 렌더링 되는 것을 확인할 수 있다. (template/response/hello.html)
- HTTP 응답 - HTTP API, Message body에 직접 입력
HTTP API를 제공하는 경우 HTML이 아니라 데이터를 전달해야 하므로, HTTP Message body에 JSON같은 형식으로 데이터를 실어 보낸다.
- responseBodyV1 : Servlet를 다룰 때 처럼 HttpServletResponse 객체를 통해 HTTP Message body에 직접 ok응답을 전달한다.
- responseBodyV2 : ResponseEntity는 HttpEntity를 상속 받았는데 추가로 HTTP Status Code를 설정할 수 있다.
- responseBodyV3 : @ResponseBody를 사용하면 view를 거치지 않고, HttpMessageConverter를 통해 HTTP Message를 직접 입력할 수 있다.
- responseBodyJsonV1 : ResponseEntity를 반환한다. HTTPMessageConverter를 통해서 JSON 형식으로 변환되어 반환된다.
- responseBodyJsonV2 : ResponseEntity는 HTTP Status를 설정할 수 있는데 @ResponseBody를 사용하면 설정하기 까다롭다. 따라서 @ResponseStatus(HttpStatus.OK)를 사용하면 손 쉽게 Status도 설정할 수 있다.
- @RestController
@Controller 대신에 @RestController를 사용하면, 해당 컨트롤러에 모두 @ResponseBody가 적용되는 효과가 있다. 따라서 뷰 템플릿을 사용하는 것이 아니라 HTTP Message body에 직접 데이터를 입력한다. Rest API를 만들 때 사용하는 컨트롤러이다. 참고로 @ResponseBody는 클래스 레벨에 두면 전체 메서드에 적용되는데, @RestController 안에 @ResponseBody가 적용되어 있다. (@RestController = @ResponseBody + @Controller)
2021/12/14
- HTTP 메시지 컨버터
뷰 템플릿으로 HTML을 생성해서 응답하는 것이 아니라, HTTP API처럼 JSON 데이터를 HTTP 메시지 바디에서 직접 읽거나 쓰는 경우 HTTP 메시지 컨버터를 쓰면 편리하다.
@ResponseBody - 복습
- HTTP의 BODY에 문자 내용을 직접 반환한다.
- viewResolver 대신에 HttpMessageConverter가 동작한다.
- 기본 문자 처리 : StringHttpMessageConverter / 기본 객체 처리 : MappingJackson2HttpMessageConverter가 동작한다.
- byte처리 등 기타 여러 HttpMessageConverter가 기본으로 등록되어 있다.
- HTTP 메시지 컨버터는 HTTP 요청, HTTP 응답 둘 다 사용된다.
- canRead(), canWrite() : 메시지 컨버터가 해당 클래스, 미디어 타입을 지원하는지 체크한다.
- read(), write() : 메시지 컨버터를 통해 메시지를 읽고 쓰는 기능을 하는 메서드이다.
- 스프링부트 기본 메시지 컨버터 (우선 순위 존재, 0이 제일 큰 우선 순위를 갖는다.)
메시지 컨버터는 클래스 타입과 미디어 타입 둘 다 체크해서 사용할지 말지를 결정한다. 만일 만족하지 않으면 다음 우선 순위로 넘어간다.
- ByteArrayHttpMessageConverter : byte[] 데이터를 처리한다.
- 클래스 타입 : byte[], 미디어 타입 : / (모든 타입 처리 가능)
- 요청 예) @RequestBody byte[] data / 응답 예) @ResponseBody return byte[], 쓰기 미디어 타입 : application/octet-stream
- StringHttpMessageConverter : String 문자로 데이터를 처리한다.
- 클래스 타입 : String, 미디어 타입 : / (모든 타입 처리 가능)
- 요청 예) @RequestBody String data / 응답 예) @ResponseBody return "ok", 쓰기 미디어 타입 : text/plain
- MappingJackson2HttpMessageConverter : application/json
- 클래스 타입 : 객체 또는 HashMap, 미디어 타입 : application/json 관련
- 요청 예) @RequestBody HelloData data / 응답 예) @ResponseBody return helloData, 쓰기 미디어 타입 : application/json 관련
- HTTP 요청 데이터 읽기
- HTTP 요청이 오고, 컨트롤러에서 @RequestBody, HttpEntity 파라미터를 사용한다.
- 메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 canRead()를 호출한다. (대상 클래스 타입을 지원하는가? Content-type을 지원하는가?)
- canRead() 조건을 만족하면 read()를 호출해서 객체 생성 후 반환한다.
- HTTP 응답 데이터 생성
- 컨트롤러에서 @ResponseBody, HttpEntity로 값이 반환된다.
- 메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해 canWrite()를 호출한다. (대상 클래스 타입을 지원하는가? Content-type을 지원하는가?)
- canWrite() 조건을 만족하면 write()를 호출해서 HTTP 응답 메시지 바디에 데이터를 생성한다.
- 요청 매핑 핸들러 어댑터 구조 (RequestMappingHandlerAdapter)
- ArgumentResolver
지금까지 애노테이션 기반 컨트롤러를 사용하면서 다양한 파라미터를 사용할 수 있었다.(HttpServletRequest, Model, @RequestParam, @ModelAttribute, @RequestBody, HttpEntity 등)
이렇게 다양한 파라미터를 사용할 수 있었던 이유는 ArgumentResolver 덕분이다. 애노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdapter는 ArgumentResolver를 호출해서 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터의 값(객체)을 생성한다. 그리고 파라미터의 값이 모두 준비되면 컨트롤러를 호출하며 값을 리턴한다. 스프링은 30개가 넘는 ArgumentResolver를 기본으로 제공한다.
- ArgumentResolver의 동작 방식 - 인터페이스라 OCP를 만족하며 설계되어 있다.
- ArgumentResolver의 supportsParameter()를 호출하여 해당 파라미터를 지원하는지 체크한다.
- 지원하면 resolveArgument()를 호출하여 실제 객체를 생성한다. 그리고 생성된 객체가 컨트롤러 호출 시 넘어간다.
- ReturnValueHandler
ArgumentResolver와 비슷하게 동작하고 응답 값을 변환하고 처리한다.
- HTTP 메시지 컨버터
요청의 경우
@RequestBody를 처리하는 ArgumentResolver가 있고, HttpEntity를 처리하는 ArgumentResolver가 있다. 이 ArgumentResolver들이 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성하는 것이다.
응답의 경우
@ResponseBody와 HttpEntity를 처리하는 ReturnValueHandler가 있다. 그리고 여기에 HTTP 메시지 컨버터를 호출해서 응답 결과를 만든다.























