-
[스프링 MVC 1] 6. 스프링 MVC - 기본 기능INFLEARN/스프링 MVC 1편 2021. 11. 5. 17:45
6. 스프링 MVC - 기본 기능
[강의 정리] 김영한님 :: 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
1. 프로젝트 생성
# 프로젝트 생성
- Packaging은 Jar로 선택
- 더 이상 JSP를 사용하지 않으므로
- 앞으로 스프링 부트를 사용하면 이 방식 주로 사용
- Jar를 사용하면 항상 내장 서버(톰캣 등)를 사용, 최적화 되어 있음
- webapp 경로 사용하지 않음
+) War는 내장 서버도 가능하지만, 주로 외부 서버 배포 목적
# Welcome 페이지
- 스프링 부트에서 Jar를 사용하기 위해 /resources/static 위치에 index.html파일을 두었음
- 스프링 부트가 Welcome 페이지로 처리해줌
2. 로깅 간단히 알아보기
# 로깅 라이브러리
- 스프링 부트 라이브러리를 사용하면 기본적으로 스프링 부트 로깅 라이브러리 spring-boot-starter-logging이 포함
- SLF4J 라이브러리 (인터페이스)
- Logback 라이브러리 (구현체)
- Log4J 라이브러리
- Log4J2 라이브러리
# 로그 선언
- private final Logger log = LoggerFactory.getLogger(getClass());
- @sl4fj : 롬복 사용
- 로그호출 : log.info("xxx");
# @RestController
- @Controller
- 반환 값이 String이면 뷰 이름으로 인식
- 뷰를 찾고 뷰를 렌더링
- @RestController
- 반환 값으로 뷰를 찾지 않음
- HTTP 메시지 바디에 바로 입력
- 즉, 실행 결과로 "ok"와 같은 String 메세지 받을 수 있음
# 테스트
- 로그 레벨
- TRACE > DEBUG > INFO > WARN > ERROR
- 개발 서버 : debug
- 운영 서버 : info
- 로그 레벨 설정 (application.properties)
- 기본 설정은 루트 레벨의 info : logging.level.root=info
- 패키지와 그 하위 로그 레벨 설정 가능 : logging.level.hello.springmvc=debug
# 올바른 로그 사용법
- log.debug("data="+data) => 사용 X
- 예를 들어, properties에서 특정 레벨을 적용한다고 가정해서 실제로 적용할 수 있는 레벨이 아님에도 더하기 연산자를 사용하면 실제로는 더하기 연산이 제일 먼저 일어나서 리소스를 사용하게 됨
- log.debug("data={}", data)
- parameter만 넘겨서 실행하여, 의미없는 연산이 발생하지 않음
# 로그 사용시 장점
- 쓰레드 정보, 클래스 이름 같은 부가 정보 볼 수 있음
- 출력 모양 조정 가능
- 로그 레벨에 따라 개발/운영 서버 각각에 출력하고 싶은 로그만 출력 가능
- 시스템 아웃은 콘솔에만 출력되지만, 로그는 파일, 네트워크 등의 위치에도 남길 수 있음
- 파일에서 로그 분할도 가능
- 성능(내부 버퍼링, 멀티 쓰레드)도 시스템 아웃보다 좋음
3. 요청 매핑
# 기본 요청
- URL 호출이 왔을 때 메서드가 실행될 수 있도록 매핑
- @Controller가 아니라 @RestController를 붙여서, 반환 값으로 뷰를 찾는 것이 아니라 HTTP 메시지 바디에 바로 입력
- 다중설정도 가능 (배열)
- HTTP 메서드를 지정하지 않아서 모두 허용 가능 (GET, HEAD, POST, PUT, PATCH, DELETE)
# HTTP 메서드 매핑
- method 특정 HTTP 메서드 요청만 허용
- 예시처럼 GET으로 세팅해놨는데 POST 요청을 하면 스프링 MVC는 405 반환
# HTTP 메서드 매핑 축약
- @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping
# PathVariable(경로 변수) 사용
- PathVariable 사용
- 변수명이 같으면 생략 가능
- 다중 사용 가능
# 특정 파라미터 조건 매핑 (잘 사용하지 않음)
# 특정 헤더 조건 매핑
# 미디어 타입 조건 매핑 - HTTP 요청 Content-Type, consume
- 맞지 않으면 415 상태코드 반환
# 미디어 타입 조건 매핑 - HTTP 요청 Accept, produce
- 맞지 않으면 406 상태코드 반환
4. 요청 매핑 - API 예시
# 매핑
- 회원 목록 조회 : GET /users
- 회원 등록 : POST /users
- 회원 조회 : GET /users/{userId}
- 회원 수정 : PATCH /users/{userId}
- 회원 삭제 : DELETE /users/{userId}
5. HTTP 요청 - 기본, 헤더 조회
# HTTP 헤더 정보 조회
- HttpServletRequest
- HttpServletResponse
- HttpMethod
- HTTP 메서드 조회
- Locale
- Locale 정보를 조회
- @RequestHeader MultiValueMap<String, String> headerMap
- 모든 HTTP 헤더를 MultiValueMap 형식으로 조회
- MultiValueMap : 하나의 키에 여러 값을 받을 수 있음
- @RequestHeader("host") String host
- 특정 HTTP 헤더 조회
- 속성 (필수 값 여부 : required, 기본 값 속성 : defaultValue)
- @CookieValue(value = "myCookie", required = false) String cookie
- 특정 쿠키 조회
- 속성 (필수 값 여부 : required, 기본 값 속성 : defaultValue)
+) 참고
- @Controller의 사용 가능한 파라미터 목록 : https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-arguments
- @Controller의 사용 가능한 응답 값 목록 : https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-return-types
6. HTTP 요청 파라미터 - 쿼리 파라미터, HTML 조회
# 클라이언트 -> 서버 (요청 데이터 전달)
- GET - 쿼리 파라미터
- 메세지 바디 없이 URL의 쿼리 파라미터에 데이터를 포함해서 전달
- POST - HTML From
- 메세지 바디에 쿼리 파라미터 형식으로 전달
- HTTP message body에 데이터 직접 담아서 요청
- HTTP API에서 주로 사용, JSON
# 요청 파라미터 - 쿼리 파라미터, HTML FORM
- HttpServletRequest의 request.getParameter() 사용
- GET 실행 (http://localhost:8080/request-param-v1?username=kim&age=20)
- 아래 예시는 view 조회 x
- POST Form 실행 (http://localhost:8080/basic/hello-form.html)
- /resources/static 아래에 두면 스프링 부트가 자동으로 인식
- Jar를 사용하면 webapp 경로 알 수 없어 정적 리소스도 클래스 경로에 포함해야함
7. HTTP 요청 파라미터 - @RequestParam
# requestParamV2
- @RequestParam : 파라미터 이름으로 바인딩
- @RequestParam("paramName") String name
- -> request.getParameter("paramName")
- @ResponseBody : View 조회를 무시하고, HTTP Message Body에 직접 해당 내용 입력
# requestParamV3
- HTTP 파라미터 이름이 변수 이름과 같은 경우
- @RequestParam(name="xxx") 생략 가능
# requestParamV4
- String, int, Integer 등 단순 타입이면 @RequestParam 생략 가능
- 하지만, @RequestParam이 있으면 명확하게 요청 파라미터에서 데이터를 읽는다는 것을 알 수 있으므로 살려두는 것을 권장
# 파라미터 필수 여부 - requestParamRequired
- @RequestParam.required
- 파라미터 필수 여부
- 기본값은 파라미터 필수(true)
- /request-param 요청을 했을 때, 필수 값이 없으면 400 예외
- (주의) /request-param?username=
- 파라미터 이름만 있고 값이 없는 경우 필수값이어도 빈문자로 통과됨
- null과 빈문자는 다른거
- (주의) 기본형(primitive)에 null 입력
- ex) @RequestParam(required = true) int age 일 때
- null을 int에 입력하는 것은 불가능하므로 500 예외 발생
- 따라서, null을 받을 수 있는 Integer로 변경하거나, defaultValue 사용
# 기본 값 적용 - requestParamDefault
- 파라미터에 값이 없는 경우 defaultValue를 사용하면 기본 값 적용 가능
- 이미 기본 값이 있으므로 required는 의미 없음
- 빈 문자의 경우에도 설정한 기본 값 적용
# 파라미터를 Map으로 조회하기 - requestParamMap
- 파라미터를 Map, MultiValueMap으로 조회할 수 있음
- @RequestParam Map<String, Object> paramMap
8. HTTP 요청 파라미터 - @ModelAttribute
# modelAttributeV1
- 요청 파라미터를 받아서 필요한 객체를 만들고, 그 객체에 값을 넣어줘야함
- @ModelAttribute를 사용하면, HelloData 객체가 생성되고 요청 파라미터의 값도 들어있음
# @ModelAttribute 흐름
- 스프링 MVC는 @ModelAttribute가 있으면 다음을 실행
- 해당 객체를 생성
- 요청 파라미터의 이름으로 해당 객체의 프로퍼티를 찾음
- 그리고 해당 프로피터의 setter를 호출해서 파라미터의 값을 입력(바인딩)
- 프로퍼티
- 객체에 getxxx(), setxxx() 메서드가 있으면, 이 객체는 xxx라는 프로퍼티를 가지고 있음
- xxx 프로퍼티의 값을 변경하면 setxxx()가 호출되고, 조회하면 getxxx()가 호출됨
- 바인딩 오류
- age = abc 처럼 숫자가 들어가야할 곳에 문자를 넣으면 BindException 발생
# modelAttributeV2 - @ModelAttribute 생략 가능
- @ModelAttribute는 생략 할 수 있음
- 하지만, @RequestParam도 생략 가능하여 혼란이 발생할 수 있음
- String, int, Integer와 같은 단순 타입 = @RequestParam
- 나머지 = @ModelAttribute (argument resolver로 지정해둔 타입 외)
9. HTTP 요청 메시지 - 단순 텍스트
# HTTP message body
- 요청 파라미터와 다르게 HTTP 메세지 바디를 통해 직접 데이터가 넘어오는 경우 @RequestParam, @ModelAttribute를 사용할 수 없음 (HTML Form 형식 제외)
# requestBodyStringV1
- request.getInputStream()
# requestBodyStringV2 - Input, Output Stream, Reader
- InputStream(Reader) : HTTP 요청 메시지 바디의 내용을 직접 조회
- OutputStream(Writer) : HTTP 응답 메시지의 바디에 직접 결과 출력
# requestBodyStringV3 - HttpEntity
- HttpEntity : HTTP header, body 정보를 편리하게 조회
- httpEntity.getHeaders(), httpEntity.getBody()
- 메시지 바디 정보를 직접 조회
- 요청 파라미터를 조회하는 기능과 관계 없음 (@RequestParam X, @ModelAttribute X)
- 응답에도 사용 가능
- 메시지 바디 정보 직접 반환
- 헤더 정보 포함 가능
- view 조회 X (Body data -> HTTP 응답 메시지)
- RequestEntity
- HttpMethod, url 정보가 추가, 요청에서 사용
- Responseentity
- HTTP 상태 코드 설정 가능, 응답에서 사숑
- return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED)
- 메시지 컨버터 (HttpMessageConverter)
- 스프링 MVC 내부에서 HTTP 메시지 바디를 읽어서 문자나 객체로 변환해서 전달할 때 사용되는 기능
# requestBodyStringV4 - @RequestBody
- @RequestBody
- HTTP 메시지 바디 정보 직접 조회
- 헤더 정보가 필요하다면 HttpEntity 또는 @RequestHeader 사용하면 됨
- @RequestParam, @ModelAttribute 와 전혀 관계 없음! (-> 요청 파라미터 조회)
- 전혀 다른 HttpMessageConverter라는 매커니즘이 존재
- @ResponseBody
- 응답 결과를 HTTP Message Body에 직접 담아서 전달
- View X
10. HTTP 요청 메시지 - JSON
# requestBodyJsonV1
- HttpServletRequest를 사용하여 직접 HTTP 메시지 바디에서 데이터 읽어서 문자로 변환
- 문자로 된 JSON 데이터를 Jackson 라이브러리인 objectMapper 사용하여 자바 객체로 변환
# requestBodyJsonV2 - @RequestBody 문자 변환
- @RequestBody를 사용해서 HTTP 메시지를 꺼내고 메시지 바디에 저장
- 문자로 된 JSON 데이터인 messageBody를 objectMapper를 통해서 자바 객체로 변환
# requestBodyJsonV3 - @RequestBody 객체 변환
- @RequestBody에 직접 만든 객체를 지정
- HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 우리가 원하는 문자나 객체(JSON 객체)로 변환 (HttpEntity, @RequestBody 사용시)
- @RequestBody는 생략 불가능
- 만약 생략하게 되면 @ModelAttribute로 인식하게 됨 (이러면 HTTP 메시지 바디가 아니라 요청 파라미터를 처리하게 됨)
# requestBodyJsonV4 - HttpEntity
- HttpEntity 가능
# requestBodyJsonV5
- @ResponseBody
- 응답의 경우도 @ResponseBody를 사용하면 해당 객체를 HTTP 메시지 바디에 직접 넣을 수 있음 (물론 HttpEntity도 가능)
- 이 때, 클라이언트에서 받아들일 수 있는 타입인지를 체크함 (Accept : application/json)
- @RequestBody 요청
- JSON 요청 -> HTTP 메시지 컨버터 -> 객체
- @ResponseBody 응답
- 객체 -> HTTP 메시지 컨버터 -> JSON 응답
11. 응답 - 정적 리소스, 뷰 템플릿
# 스프링(서버)에서 응답 데이터를 만드는 방법
- 정적 리소스 (정적인 HTML, css, js)
- 뷰 템플릿 사용 (동적인 HTML)
- HTTP 메시지 사용 (JSON)
# 정적 리소스
- 스프링 부트는 클래스패스의 다음 디렉토리에 있는 정적 리소스를 제공
- /static, /public, /resource, /META-INF/resources
- src/main/resources는 리소스를 보관하는 곳이며, 클래스패스의 시작 경로
- 스프링 부트가 정적 리소스로 서비스를 제공
- src/main/resources/static/basic/hello-form.html
- => http://localhost:8080/basic/hello-form.html
# 뷰 템플릿
- 뷰 템플릿을 거쳐서 HTML이 생성되고, 뷰가 응답을 만들어서 전달
- 일반적으로는 HTML을 동적으로 생성하는 용도로 사용
- src/main/resources/templates/response/hello.html
# responseViewV1
- ModelAndView
# responseViewV2
- String을 반환하는 경우, view or HTTP 메시지
- @ResponseBody가 없으면 response/hello로 뷰 리졸버가 실행되어서 뷰를 찾고, 렌더링 함
- 아래 예시에서는 templates/reponse/hello.html 실행하게됨
- templates/xxx/xxx.html 은 미리 스프링부트가 세팅해놓음
- build.gradle에 implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
- 스프링 부트가 자동으로 ThymeleafViewResolver와 필요한 스프링 빈들을 등록
- application.properties에 아래와 같이 default로 설정되어 있음
- spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
- @ResponseBody가 있으면 뷰 리졸버를 실행하지 않고, HTTP 메시지 바디에 response/hello 라는 문자가 입력
# responseViewV3
- void를 반환하는 경우 - 권장하지 않음
- @Controller를 사용하고, HttpServletResponse, OutputStream(writer) 같은 HTTP 메시지 바디를 처리하는 파라미터가 없으면, 요청 URL을 참고해서 논리 뷰 이름으로 사용
- 요청 URL : /response/hello
- 실행 : templates/response/hello.html
+) 참고 - 스프링 부트 타임리프 관련 추가 설정
12. 응답 - HTTP API, 메시지 바디에 직접 입력
+ ) 정적 리소스나 뷰 템플릿을 거치지 않고, 직접 HTTP 응답 메시지를 전달하는 경우
- HTML이나 뷰 템플릿을 사용해도 HTTP 응답 메시지 바디에 HTML 데이터가 담겨서 전달됨
# responseBodyV1
- (서블릿을 직접 다룰때처럼) HttpServletResponse 객체를 통해서 HTTP 메시지 바디에 직접 "ok" 응답 메시지 전달
- response.getWriter().write("ok");
# responseBodyV2
- ResponseEntity는 HttpEntity를 상속 받음
- HTTP 메시지의 헤더 바디 정보 + HTTP 응답 코드 설정 가능
# responseBodyV3
- @ResponseBody 사용하여 view를 사용하지 않고, HTTP 메시지 컨버터를 통해서 HTTP 메시지를 직접 입력
# responseBodyJsonV1
- HTTP 메시지 컨버터를 통해서 JSON 형식으로 변환되어서 반환
# responseBodyJsonV2
- ResponseEntity를 사용하지 않고 @ResponseBody를 사용하기 때문에 HTTP 응답 코드 설정이 까다로움
- @ResponseStatus(HttpStatus.OK) 애노테이션 사용하여 응답 코드 설정
- 애노테이션이므로 동적으로 변경은 불가능 (동적 변경이 필요하면 ResponseEntity 사용)
# @RestController
- @Controller 대신 @RestController 애노테이션을 사용하면, 해당 컨트롤러에 모두 @ResponseBody가 적용되는 효과가 있음
- 따라서, 뷰 템플릿을 사용하는 것이 아니라 HTTP 메시지 바디에 직접 데이터를 입력
- Rest API (HTTP API) 만들 때 사용하는 컨트롤러
- @Controller + @ResponseBody
13. HTTP 메시지 컨버터
# @ResponseBody 사용 원리
- HTTP의 BODY에 문자 내용을 직접 반환
- viewResolver 대신에 HttpMessageConverter가 동작
- 기본 문자 처리 : StringHttpMessageConverter
- 기본 객체 처리 : MappingJackson2HttpMessageConverter
# 스프링 MVC HTTP 메시지 컨버터 적용
- HTTP 요청 : @RequestBody, HttpEntity(RequestEntity)
- HTTP 응답 : @ResponseBody, HttpEntity(ResponseEntity)
- canRead(), CanWrite() : 메시지 컨버터가 해당 클래스, 미디어타입을 지원하는지 체크
- read(), write() : 메시지 컨버터를 통해서 메시지를 읽고 쓰느 기능
# 스프링 부트 기본 메시지 컨버터
- 스프링 부트는 대상 클래스 타입과 미디어 타입 툴을 체크해서 사용여부를 결정 (만족하지 않으면 우선순위에 따라 넘어감)
- MappingJackson2HttpMessageConverter
- 가능 (content-type, 클래스 타입 일치)
- 불가능 (content-type이 text/html이면 안됨)
# HTTP 요청 데이터 읽기
- HTTP 요청이 오고, 컨트롤러에서 @RequestBody, HttpEntity 파라미터를 사용
- 메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 canRead() 호출
- 대상 클래스 타입을 지원하는가
- 예) @RequestBody의 대상 클래스 (byte[], String, HelloData)
- HTTP 요청의 Content-Type 미디어 타입을 지원하는가
- 예) text/plain, application/json, */*
- 대상 클래스 타입을 지원하는가
- canRead() 조건을 만족하면 read()를 호출해서 객체를 생성하고 반환
# HTTP 응답 데이터 읽기
- 컨트롤러에서 @ResponseBody, HttpEntity로 값이 반환
- 메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 canWrite() 호출
- 대상 클래스 타입을 지원하는가
- 예) return의 대상 클래스 (byte[], String, HelloData)
- HTTP 요청의 Accept 미디어 타입을 지원하는가 (더 정확히는 @RequestMapping 의 produces)
- 예) text/plain, application/json, */*
- 대상 클래스 타입을 지원하는가
- canWrite() 조건을 만족하면 write()를 호출해서 HTTP 응답 메시지 바디에 데이터 생성
14. 요청 매핑 핸들러 어댑터 구조
# @RequestMappingHandlerAdapter (요청 매핑 핸들러 어댑터) 동작 방식
- ArgumentResolver
- 애노테이션 기반의 컨트롤러는 매우 다양한 파라미터를 사용할 수 있고, 이렇게 파라미터를 유연하게 처리할 수 있는 이유는 ArgumentResolver 때문
- 즉, 애노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdapter는 ArgumentResolver를 호출해서 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터의 값(객체)를 생성
- HandlerMethodArgumentResolver (줄여서 -> ArgumentResolver)
- ArgumentResolver의 supportsParameter()를 호출해서 해당 파라미터를 지원하는지 체크
- 지원한다면 resolveArgument()를 호출해서 실제 객체를 생성
- 생성된 객체가 컨트롤러 호출시 넘어감
- 애노테이션 기반의 컨트롤러는 매우 다양한 파라미터를 사용할 수 있고, 이렇게 파라미터를 유연하게 처리할 수 있는 이유는 ArgumentResolver 때문
+ 참고) 가능한 파라미터 목록 : https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-arguments
- HandlerMethodReturnValueHandler (줄여서 -> ReturnValueHandler)
- 응답 값을 변환하고 처리
- 컨트롤러에서 String으로 뷰 이름을 반환해도, 동작하는 이유
+ 참고) 가능한 응답 값 목록 : https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-return-types
# HTTP 메시지 컨버터 위치
- HTTP 메시지 컨버터를 사용하는 @RequestBody도 컨트롤러가 필요로 하는 파라미터의 값에 사용되고, @ResponseBody도 컨트롤러의 반환 값을 이용
- 요청 : @RequestBody, HttpEntity를 처리하는 ArgumentResolver가 있고, 이 ArgumentResolver들이 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성
- 응답 : @ResponseBody, HttpEntity를 처리하는 ReturnValueHandler가 있고, 여기에서 HTTP 메시지 컨버터를 호출해서 응답 결과를 생성
- 스프링 MVC는
- @RequestBody, @ResponseBody => RequestResponseBodyMehtodProcessor(ArgumentResolver) 사용
- @HttpEntity => HttpEntityMethodProcessor(ArgumentResolver) 사용
- 스프링은 다음을 모두 인터페이스로 제공하여, 필요하다면 언제든지 기능을 확장할 수 있음
- HandlerMethodArgumentResolver
- HandlerMethodReturnValueResolver
- HttpMessageConverter
'INFLEARN > 스프링 MVC 1편' 카테고리의 다른 글
[스프링 MVC 1] 7. 스프링 MVC - 웹 페이지 만들기 (0) 2021.11.05 [스프링 MVC 1] 5. 스프링 MVC - 구조 이해 (0) 2021.10.27 [스프링 MVC 1] 4. MVC 프레임워크 만들기 (0) 2021.10.23 [스프링 MVC 1] 3. 서블릿, JSP, MVC 패턴 (0) 2021.10.18 [스프링 MVC 1] 2. 서블릿 (0) 2021.10.16 - Packaging은 Jar로 선택