[Spring MVC 1편] 스프링 MVC - 기본 기능
프로젝트 생성

SNAPSHOT은 정식 release가 아니다
이제 JSP를 쓰지 않고 타임리프 템플릿을 쓸 것
패키징을 Jar로 선택하는 것은 별도의 톰캣 서버에 설치하는 것이 아니고 그냥 내장 톰캣으로 바로 돌리기 위해서다

enable 체크하기
스프링부트에 Jar 사용시 /resourcs/static/index.html 위치에 index.html 파일을 두면 웰컴 페이지로 처리해준다
로깅 간단히 알아보기
운영시스템에서는 println()과 같은 시스템 콘솔을 사용해서 정보를 출력하지 않고, 별도의 로깅 라이브러리로 로그를 출력한다
로깅 라이브러리
스프링 부트 라이브러리를 사용하면, 스프링 부트 로깅 라이브러리 spring-boot-starter-logging 이 함께 포함된다
SLF4J는 인터페이스고 Logback을 구현체 중 하나로 선택한 것이다
실무에서는 스프링부트가 기본으로 제공하는 Logback을 대부분 사용한다


@Controller를 쓰면 view 이름이 반환되는 것이고 @RestController라면 저 ok 문자를 그대로 반환한다 http 메세지 바디에 그냥 저 데이터를 그대로 넣어버린다
로그 레벨 설정은 application.properties에서
#전체 로그 레벨 설정(기본은 info)
logging.level.root=info
#hello.springmvc 패키지와 그 하위 로그 레벨 설정
logging.level.hello.springmvc=debug
로그의 올바른 사용법
log.debug("data=" + data) -> 잘못됨!
로그출력 레벨을 info로 설정해도 문자 더하기 연산이 발생된다
log.debug("data={}",data) -> 올바름
로그출력 레벨을 info로 설정하면 아무 일도 발생하지 않는다. 위처럼 의미없는 연산이 발생하지 않음
로그 사용시 장점
- 쓰레드 정보, 클래스 이름 같은 부가정보 볼 수 있음, 출력 모양 조정 가능
- 로그 레벨에 따라 개발 서버에서는 모든 로그를 출력하고, 운영 서버에서는 출력하지 않는 등 조절 가능하다
- 시스템 아웃 콘솔에만 출력하는게 아니라, 파일이나 네트워크 같은 별도의 위치에 로그를 남길 수 있다
- 성능도 System.out 보다 좋다
요청 매핑
요청이 왔을때 어떠한 컨트롤러가 호출이 되야하는지 매핑하는 것이다
hello/springmvc/basic/requestmapping/MappingController.class
@RequestMapping("/hello-basic")
/hello-basic URL 호출이 오면 이 메서드가 실행되도록 매핑한 것
{"/hello-basic", "/hello-go"} 와 같이 여러개 설정할 수도 있다
스프링에서는 /hello-basic과 /hello-basic/ 을 같은 요청으로 매핑한다
위처럼 @RequestMapping에 HTTP 메서드를 지정하지 않으면 GET,POST,PUT 등등 모두 허용되는 걸로 호출된다
그래서 method=RequestMethod.GET과 같이 지정해준다
이를 축약해서 표현한 것이
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
PathVariable(경로 변수) 사용

@GetMapping("/mapping/{userId}")
URL 자체에 값이 들어가있는 것이고 템플릿 형식으로 만들어놓은 것이다
URL 경로에 들어있는 값을 @PathVariable로 꺼내서 사용할 수 있다
이러한 스타일의 코드가 많이 사용된다
만약에 변수명이 userId로 경로명이랑 같으면 @PathVariable String userId로 생략 가능하다
그리고 다중 사용도 가능하다

미디어 타입 조건 매핑- HTTP 요청 Content-Type, consume

json인 경우, html인 경우와 같이 요청의 컨텐트 타입에 매핑할 수 있다
consumes이 아닌 produces로 하면 이러이러한 미디어 타입을 받아들일 수 있다는 의미다
요청 매핑 - API 예시
회원 목록 조회: GET /users
회원 등록 : POST /users
회원 조회 : GET /users/{userId}
회원 수정: PATCH /users/{userId}
회원 삭제 : DELETE /users/{userId}
URL은 똑같지만 HTTP 메서드로 어떤 기능이 동작하는지 구분할 수 있다

/mapping은 다른 예제들이랑 안 겹치게 그냥 한 것
데이터 반환하고 오고 가고 이런 것들은 생략하고 path 매핑만 해보는 것이다
그리고 여기서 /mapping/users가 중복되기 때문에
@RequestMapping("/mapping/users") 로 중복된 부분을 지울 수 있다
텍스트 html accept로 요청하면 그냥 그게 오는거고 이렇게 api 스타일로 하면 json 형식으로 스프링이 내려준다
정도만 이해하면 된다
HTTP 요청 - 기본, 헤더 조회


@RestController를 사용하면 응답값을 뷰를 찾는게 아니라 문자 바로 그대로 html 응답에 박아서 반환한다
애노테이션 기반 컨트롤러는 정말 다양한 파라미터를 받아들일 수 있다
MultiValueMap
MAP과 유사한데 하나의 키에 여러 값을 받을 수 있다
HTTP header, HTTP 쿼리 파라미터와 같이 하나의 key에 여러가지 value들 받을 때 사용한다

HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form
HTTP 요청 메세지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법에는 3가지가 있다
1. GET - 쿼리 파라미터
?username=hello&age=20 처럼 메세지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달한다
검색, 필터, 페이징에서 많이 사용
2. POST - HTML Form
메세지 바디에 쿼리 파라미터 형식으로 전달한다
회원 가입, 상품 주문, HTML Form에서 사용
위 1번과 형식이 같으므로 구분없이 조회할 수 있는데, 이걸 간단히 request parameter 조회라고 한다
3. HTTP message body에 데이터를 직접 담아서 요청
HTTP API에서 주로 사용한다 JSON, XML, TEXT
데이터 형식은 주로 JSON 사용
+ resources/static 하위에 있는 파일들은 전부 외부에 공개된다
HTTP 요청 파라미터 - @RequestParam

@RequestParam("username") String memberName
-> 여기서 username이 HTTP 요청의 이름이 되고, memberName은 변수명이다
@ResponseBody를 메서드 레벨에서 쓰거나 @RestController를 클래스 레벨에서 써서 ok 문자를 응답에 박히게 한다
여기서 HTTP 파라미터 이름이 변수 이름과 같으면 "username" "age"와 같은거 생략하고
@RequestParam String username이라고 생략 가능하다
@RequestParam(required=true) -> 기본값이며 꼭 들어와야하는 파라미터
@RequestParam(required=false) -> 이 파라미터는 필수가 아니다 없어도 된다
int형에는 null이 들어갈 수 없으므로 false를 쓰려면 Integer age로 바꿔준다 이유는 Integer는 객체형이므로
또는 defaultValue를 사용한다

defaultValue란 파라미터에 값이 없을때 기본값을 적용해주는 거다
또 파라미터를 Map, MultiValueMap으로 조회할 수 있다

파라미터 값이 1개가 확실하면 Map으로, 그렇지 않으면 MultiValueMap으로 사용하기
?userIds=id1&userIds=id2 처럼 key는 같은데 value가 여러개인 경우처럼
HTTP 요청 파라미터 - @ModelAttribute
실제론 요청 파라미터를 받아서 필요한 객체를 만들고, 그 객체에 값을 넣어주어야 하는데,
@ModelAttribute가 그 과정을 완전히 자동화해준다
먼저 요청 파라미터를 바인딩 받을 객체를 생성해준다

@Data를 쓰면 @Getter @Setter @RequiredArgsConstructor를 자동으로 만들어준다
public String modelAttributeV1(@RequestParam String username, @RequestParam int age) {
HelloData helloData = new HelloData();
helloData.setUsername(username);
helloData.setAge(age);
이 부분을

이렇게 바로 저 과정을 @ModelAttribute HelloData helloData로 축약 가능하다
과정 1.HelloData 객체를 생성한다
2. 요청 파라미터 이름으로 HelloData 객체의 프로퍼티를 찾는다. 그리고 해당 프로퍼티의 setter를 호출해
파라미터 값을 입력(바인딩)한다 ex) 파라미터 이름이 username이면 setUsername() 메서드를 찾아 호출하여 값 입력
* 프로퍼티 : 객체에 getUsername()과 같은 메서드가 있으면 이 객체는 username이라는 프로퍼티를 가지고 있다
HTTP 요청 메세지 - 단순 텍스트
HTTP message body에 데이터를 직접 담아서 요청하는 경우이다.
주로 HTTP API에서 사용하고 데이터 형식은 주로 JSON 사용한다
요청 파라미터랑 다르게 이와 같이 데이터가 직접 넘어오는 경우 @RequestParam @ModelAttribute 사용 불가능

HttpEntity : HTTP header, body 정보를 편리하게 조회할 수 있다
메세지 바디 정보를 직접 조회하며, 요청 파라미터를 조회하는 @RequestParam 과 같은 기능과 관계 없다
(요청 파라미터는 GET에 쿼리 파라미터 오는거 혹은 HTML 폼 데이터 전송하는 방식인 경우에만 사용하는거다)
응답에도 사용 가능하고 view 조회 안 한다.
HttpEntity를 상속받은 객체들도 같은 기능을 제공한다
RequestEntity : HttpMethod, url 정보가 추가,요청에서 사용
public HttpEntity<String> (RequestEntity<String> httpEntity)
ResponseEntity : HTTP 상태 코드 설정 가능, 응답에서 사용

이를 간편하게 축약할 수 있는 애노테이션 @RequestBody @ResponseBody가 존재한다.

String으로 되어있으니 그냥 HTTP message body 읽어가지고 messageBody에 넣어주는거
요청 오는건 RequestBody 응답 나가는건 ResponseBody
@RequestBody를 사용하면 HTTP 메세지를 편리하게 조회할 수 있다
아까 썼듯이 메세지 바디를 직접 조회하는 기능은 요청 파라미터를 조회하는 @RequestParam @ModelAttribute와는
전혀 관계가 없다!
HTTP 요청 메세지 - JSON
{"username":"hello", "age":20}
content-type: application/json 이러한 형식으로 보낼 것이다


objectMapper로 객체로 돌리는것을 @RequestBody 객체파라미터로 바꿀 수 있다
@RequestBody HelloData data -> @RequestBody에 직접 만든 객체를 지정할 수 있음
@RequestBody를 사용하면 HTTP 메세지 컨버터가 HTTP 메세지 바디의 내용을
우리가 원하는 문자나 객체 등으로 변환해준다

이렇게 HttpEntity를 사용해도 된다.
위 V3 코드에서는 데이터가 그대로 들어오는데 반환을 string으로 한다
그런데 반환도 스트링이 아니라 데이터 온 것을 그대로 반환하고자 한다면

data 객체가 http 메세지 컨버터에서 json으로 바뀌고 응답으로 나간다
제이슨이 객체가 되었다가 객체가 나갈 때 다시 제이슨이 되서 우리 눈에 보이게 되는 것
@Responsebody : 응답의 경우에도 이 어노테이션을 사용하면 해당 객체를 HTTP 메세지 바디에 직접 넣어줄 수 있음
@RequestBody 요청 : JSON 요청 -> HTTP 메세지 컨버터 -> 객체
@ResponseBody 응답 : 객체 -> HTTP 메세지 컨버터 -> JSON 응답
* JSON은 일정한 규칙에 맞게 작성한 문자열이다.
이 문자열을 해석해서 객체로 변환, 사용하기 쉽게 만드는 것을 '파싱'한다고 표현
HTTP 응답 - 정적 리소스, 뷰 템플릿
스프링에서 응답 데이터를 만드는 방법은 크게 3가지이다.
1. 정적 리소스
웹 브라우저에 정적인 HTML, css, js를 제공할 때 사용 -> 파일을 그대로 전달하는 것
src/main/resources/static 하위에 넣어주면 정적 리소스로 제공한다
정적 리소스는 해당 파일을 변경 없이 그대로 서비스 하는 것
2. 뷰 템플릿 사용
웹 브라우저에 동적인 HTML 제공할 때 사용
뷰 템플릿을 거쳐 HTML이 생성되고 뷰가 응답을 만들어서 전달함
src/main/resources/templates
String을 반환하는 경우 @ResponseBody가 없으면 뷰 리졸버가 실행되서 뷰를 찾고 렌더링 함
@ResponseBody가 있을 경우엔 HTTP 메세지 바디에 직접 문자가 입력된다
3. HTTP 메세지 사용
ex) HTTP API를 제공하는 경우 HTML이 아니라 데이터를 전달해야 하므로
HTTP 응답 - HTTP API, 메세지 바디에 직접 입력
HTTP API를 제공하는 경우에는 HTML이 아니라 데이터를 전달해야 하므로
HTTP 메세지 바디에 JSON 같은 형식으로 데이터를 실어보낸다

ResponseEntity는 HttpEntity를 상속 받았는데, HttpEntity는 HTTP 메세지의 헤더, 바디 정보를 가지고 있음
ResponseEntity는 여기에 더해서 HTTP 응답 코드를 설정할 수 있다.
HttpStatus.CREATED로 변경하면 201 응답이 나간다
방금 코드는 문자를 처리하는거였고 JSON을 처리하는것은 아래 코드들이다.

HTTP 메세지 컨버터를 통해 JSON 형식으로 변환되어 반환된다
이 코드에서 개선하면 아래 코드이다.

@ResponseBody를 사용하면 HelloData를 그대로 넣을 수 있다
@ResponseStatus 어노테이션으로 응답 코드를 지정할 수 있다
이는 어노테이션이기때문에 응답 코드를 동적으로 변경할 수는 없어서 그러려면 ResponseEntity를 사용하면 된다
@RestController를 사용하면 해당 컨트롤러 모두 @ResponseBody가 적용되는 효과가 있다
HTTP 메세지 컨버터
HTTP API처럼 JSON 데이터를 HTTP 메세지 바디에서 직접 읽거나 쓰는 경우 HTTP 메세지 컨버터 사용하는게 편리하다
스프링 MVC는 다음의 경우에 HTTP 메세지 컨버터를 적용한다
HTTP 요청: @RequestBody, HttpEntity(RequestEntity)
HTTP 응답: @ResponseBody, HttpEntity(ResponseEntity)
HTTP 메세지 컨버터는 요청, 응답 둘 다 사용된다
응답에 있는 메세지 바디를 읽어서 객체로 바꿔서 컨트롤러의 파라미터로 넘겨주는 역할,
컨트롤러에서 리턴값을 가지고 응답 메세지도 넣는 두가지 역할을 다한다
HTTP 요청 데이터 읽기
1. HTTP 요청이 오고, 컨트롤러에서 @RequestBody, HttpEntity 파라미터를 사용한다
2. 메세지 컨버터가 메세지를 읽을 수 있는가 확인하기 위해 canRead()를 호출한다
- 대상 클래스 타입을 지원하는지
- HTTP 요청의 Content-Type 미디어 타입을 지원하는지
3. 위 canRead() 조건을 만족하면 read()를 호출해서 객체를 생성하고 반환한다
HTTP 응답 데이터 생성
1. 컨트롤러에서 @ResponseBody, HttpEntity로 값이 반환된다
2. 메세지 컨버터가 메세지를 쓸 수 있는지 확인하기 위해 canRead()를 호출한다
- 대상 클래스 타입을 지원하는지
- HTTP 요청의 Accept 미디어 타입을 지원하는지
3. 위 canWrite() 조건을 만족하면 write()를 호출해서 HTTP 응답 메세지 바디에 데이터를 생성한다