스프링 MVC - 기본 기능
- Jar vs War
- Jar : 항상 내장 서버(톰켓 등)을 사용하고 webapp 경로를 사용하지 않고 내장 서버 사용에 최적화되어 있는 기능
- War : 내장 서버도 사용 가능하지만 주로 외부 서버에 배포하는 목적으로 사용한다.
- 최근에는 Jar를 주로 사용
- 스프링 부트에 Jar 사용 시 /resources/static 위치에 index.html 파일을 두면 Welcome 페이지로 처리한다.
- Jar는 정적 컨텐츠들을 /resources/static 하위에 위치시켜 사용한다.
로깅
- 운영 시스템에서는 System.out.println()으로 콘솔에 필요한 정보를 출력하지 않고 별도의 로깅 라이브러리를 통해 로그를 출력한다.
- 스프링 부트는 기본으로 SLF4J와 Logback 로깅 라이브러리를 사용한다.
@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);
log.debug("debug log={}", name);
log.info("info log={}", name);
log.warn("warn log={}", name);
log.error("error log={}", name);
return "ok";
}
}
- 로깅 라이브러리 사용 시 SLF4J의 Logger를 사용하며 getLogger() 인자로 현재 클래스를 넣어준다.
- @Slf4j 롬복을 사용해도 된다.
- 콘솔 출력과 달리 로그로 출력 시 날짜 및 시간, 스레드, ID, 클래스명, 메시지 등 많은 정보가 출력된다.
- 로그를 찍을 때 로그 레벨을 지정할 수 있다.
- 로그 레벨 : TRACE > DEBUG > INFO > WARN > ERROR (우측에 위치할수록 수준이 낮음)
- trace : 가장 상세한 로그 레벨이며 애플리케이션 실행 흐름과 디버깅 정보를 상세히 기록하며 디버깅 시 사용된다.
- debug : 디버깅 목적으로 사용되며 개발 단계 / 서버에서 주로 사용한다.
- info : 정보성 메시지(비지니스 정보), 운영 서버에서도 필요한 내용을 기록하는 용도로 사용한다.
- warn : 경고성 메시지를 기록하기 위해서 사용한다.
- error : 오류 메시지를 기록하기 위해서 사용한다.
- 로그 메시지 출력 시 {} 중괄호는 변수로 1:1 대체된다.
- 중괄호가 여러개 있고 변수가 여러개 있으면 각각 대체 된다.
- ★올바른 로그 사용법★
//잘못된 로그 사용법
log.info("data = " + data)
//올바른 로그 사용법
log.info("data = {}", data)
- {} 중괄호 대신 =을 사용하게 되면 설정된 로그 레벨보다 높은 수준일 때에도 문자열끼리 더하기 연산이 발생한다.
그렇기에 = 대신 {} 를 사용해서 의미 없는 연산이 발생하지 않도록 해야한다.
logging.level.패키지명=trace
- 로깅 시스템 설정은 application.yml / properties 파일에서 할 수 있다.
- 패키지와 그 하위 로그 레벨 설정할 수 있으며 로그 레벨을 지정하면 해당 레벨을 포함한 하위 수준의 로그까지 확인할 수 있다.
- ex) trace의 경우 모든 수준의 로그 확인가능, warn의 경우 warn, error 수준의 로그 확인 가능
- 기본 값은 info 수준이다.
- 보통 로컬 서버는 trace/debug 개발 서버는 debug, 운영 서버는 info로 지정한다.
- 로깅은 수준에 맞게 원하는 것만 찍을 수 있지만 System.out.println()으로 콘솔 출력 시 모든 내용이 다 남기 때문에 사용하기에 적절하지 않다.
- 로그 사용 시 장점 => 실무에서 무조건 로그를 사용해야 하는 이유
- 쓰레드 정보, 클래스 명과 같은 부가 정보를 함께 볼 수 있고 출력 모양을 조정할 수 있다.
- 로그 레벨에 따라 개발 서버에서는 모든 로그를 출력하고 운영 서버에서는 출력하지 않는 등 로그를 상황에 맞게 조절할 수 있다.
- System.out과 같이 콘솔에만 출력하는 것이 아닌 파일, 네트워크 등 로그를 별도의 위치에 남길 수 있다. (파일로 남길 시 로그를 분할 가능)
- System.out 보다 성능면에서도 좋다.
요청 매핑
- @Controller vs @RestController
- @Controller : 반환 값이 String이면 뷰 이름으로 인식하기 때문에 뷰를 찾고 뷰가 렌더링 된다.
- @RestController : 반환 값으로 뷰를 찾는 것이 아닌 HTTP 메시지 바디에 바로 입력하기 때문에 실행 결과로 문자열을 받을 수 있다.
- @RequestMapping(url) : 해당 URL 호출이 오면 메소드가 실행된다.
- {}로 url을 다중 설정할 수 있다.
ex) @RequestMapping({"/hello", "/hello-basic"}) - URL 요청 시 /hello와 /hello/는 서로 다른 URL이지만 스프링에서 같은 요청으로 매핑한다.
- method 속성 값을 지정하지 않으면 HTTP 메소드(GET, POST, PUT ... 등)와 무관하게 호출된다.
- @PostMapping, @GetMapping등 HTTP 메소드 매핑을 method 속성을 사용하지 않고 축약할 수 있다.
- 메소드끼리 중복되는 URL은 클래스 레벨에서 @RequestMapping(공통url)로 공통 처리할 수 있으며 메소드 레벨에서 해당 정보와 조합해서 사용한다.
- {}로 url을 다중 설정할 수 있다.
/**
* PathVariable(경로 변수) 사용
* 변수명이 같으면 생략 가능
*
* @PathVariable("userId") String userId -> @PathVariable String userId
*/
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {
log.info("mappingPath userId={}", data);
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";
}
- PathVariable(경로 변수) 방식
- URL에 값 자체가 넘어오는 경우 @PathVariable로 매칭되는 부분을 편리하게 조회할 수 있다.
- 변수명과 PathVariable의 이름이 같은 경우 PathVariable 명을 생략할 수 있다.
- 생략하는 경우 자바 컴파일러에 -parameters 옵션을 설정해야 하기에 생략하지 않는 것을 권장한다.
- 쿼리 파라미터 방식 : url?data1&data2 형태로 데이터가 넘어오는 방식
- 쿼리 파라미터의 데이터는 @RequestParam으로 데이터를 조회할 수 있다.
/**
* 파라미터로 추가 매핑
* params="mode",
* params="!mode"
* params="mode=debug"
* params="mode!=debug" (! = )
* params = {"mode=debug","data=good"}
*/
@GetMapping(value = "/mapping-param", params = "mode=debug")
public String mappingParam() {
log.info("mappingParam");
return "ok";
}
- params 옵션을 주면 URL도 일치하면서 해당 파라미터 정보까지 매핑되어야 메소드가 실행된다.
- headers 옵션은 헤더 정보를 매핑
- consume 옵션은 Content-Type을 매핑
- produce 옵션은 Accept를 매핑
HTTP 요청 - 기본, 헤더 조회
@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";
}
}
- @Controller에서 사용 가능한 파라미터 (외에도 다양한 것 들이 많다.)
- HttpMethod - HTTP 메소드 (GET, POST, PUT, PATCH ...등등)
- Locale - 언어 정보
- @RequestHeader - MultiValueMap 사용 시 모든 헤더 정보를 조회하고 단건 조회 시 해당 헤더 명을 통해 조회할 수 있다.
- @CookieValue - 쿠키 정보
- 단건 조회의 경우 옵션으로 required, defaultValue 등의 옵션을 제공한다.
- required는 필수 값 여부로 기본 값은 true
- defaultValue는 값이 없을 경우 기본 값을 지정해줄 수 있다.
- MultiValueMap : Map과 유사하며 하나의 Key에 여러 Value를 받을 수 있다.
- @Controller에서 반환 값도 String 외에 많은 타입들이 있다.
HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form
- 클라이언트에서 서버로 요청 데이터 전달 방식
- GET - 쿼리 파라미터 : 메시지 바디 없이 URL의 쿼리 파라미터에 데이터 전달
- POST - HTML Form : 메시지 바디에 쿼리 파라미터 형식으로 전달
- HTTP message body : HTTP API에서 주로 사용하며 데이터 형식은 주로 JSON
- HTML Form과 GET 쿼리 파라미터 방식은 조회하는 방식이 같으며 요청 파라미터(request parameter) 조회라고 한다.
- getParameter()로 조회
- @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의 이름과 변수명이 같은 경우 파라미터 이름을 생략하거나 @RequestParam을 생략할 수 있다.
- 생략하는 경우 자바 컴파일러에 -parameters 옵션을 설정해야 하기에 생략하지 않는 것을 권장한다.
- ex) @RequestParam("username") String memberName
= String memberName = request.getParameter("username")
- @RequestParam의 이름과 변수명이 같은 경우 파라미터 이름을 생략하거나 @RequestParam을 생략할 수 있다.
- @ResponseBody : View 조회를 무시하고 HTTP message body에 직접 해당 내용을 입력한다.
- @RestController와 같은 기능을 한다.
- @Controller에서 String 반환타입인 경우 뷰 논리 이름으로 인식하는데 클래스 레벨에 @RestController를 사용하거나 메소드 단위에 @ResponseBody를 사용하면 뷰를 조회하지 않고 HTTP message body에 직접 내용을 입력하기에 실행 결과로 문자열을 받을 수 있다.
- @RestController와 같은 기능을 한다.
@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age) {
log.info("username={}, age={}", username, age);
return "ok";
}
- HTTP 파라미터 이름과 변수 명이 같은 경우
- 파라미터 이름을 생략할 수 있다.
- @RequestParam을 생략할 수 있다.
- String, int, Integer 등 단순 타입인 경우에만 생략 가능
- ★스프링 부트 3.2 이후 버전 파라미터 이름 인식 문제★
- @RequestParam을 생략, @PathVariable name 속성 생략, 파라미터 이름 생략하는 등의 경우에 예외 발생 (java.lang.IllegalArgumentException: Name for argument of type [java.lang.String]
not specified, and parameter name information not found in class file either.) - 예외 발생 해결 방법 (3가지)
- ★항상 파라미터 이름과 @RequestParam 어노테이션을 모두 적어준다.★
- 자바 컴파일러에 -parameters 옵션을 넣어준다.
- IntelliJ IDEA에서 File Settings를 연다.
- Build, Execution, Deployment → Compiler → Java Compiler로 이동
- Additional command line parameters라는 항목에 -parameters를 추가한다.
- out 폴더를 삭제 후 다시 실행한다.
- Gradle을 사용해서 빌드하고 실행한다.
- @RequestParam을 생략, @PathVariable name 속성 생략, 파라미터 이름 생략하는 등의 경우에 예외 발생 (java.lang.IllegalArgumentException: Name for argument of type [java.lang.String]
@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(@RequestParam(required = true) String username,
@RequestParam(required = false) int age) {
log.info("username={}, age={}", username, age);
return "ok";
}
- @RequestParam에 required 옵션으로 파라미터 필수 여부를 지정할 수 있다.
- required = true가 기본 값이며 true인 경우 파라미터 값이 없으면 400 에러가 발생한다.
- true인 파라미터에 아무값도 넣지 않으면 빈문자로 들어가게 된다.
- required = false 인 경우 해당 파라미터 값이 없어도 된다.
- required = true가 기본 값이며 true인 경우 파라미터 값이 없으면 400 에러가 발생한다.
- 위 예제에서 파라미터로 username만 넘긴다면?
- 원래대로라면 오류가 발생하지 않아야 하지만 500 에러가 발생한다.
=> age는 int 타입이므로 null 값이 들어갈 수 없기 때문에 500 에러가 발생하므로 Integer 타입으로 변경해주거나 defaultValue를 사용해야 한다.
- 원래대로라면 오류가 발생하지 않아야 하지만 500 에러가 발생한다.
@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";
}
- defaultValue를 옵션으로 사용하면 required 옵션은 사실상 필요가 없다.
- defaultValue는 빈 문자의 경우에도 설정한 기본 값으로 적용된다.
@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으로 조회할 수 있다.
- @RequestParam Map => Map (key = value)
- @RequestParam MultiValueMap => MultiValueMap (key = [value1, value2, ....])
- 파라미터 값이 한 개인 것이 확실한 경우에만 Map을 사용하고 그렇지 않으면 MultiValueMap를 사용하자
HTTP 요청 파라미터 - @ModelAttribute
- 요청 파라미터를 받아서 필요한 객체를 만들고 객체에 값을 넣어주는 과정을 자동화 해주는 @ModelAttribute 기능을 제공한다.
//@ModelAttribute 사용 전
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@RequestParam String username,
@RequestParam int age) {
HelloData helloData = new HelloData();
helloData.setUsername(username);
helloData.setAge(age);
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
log.info("helloData={}", helloData);
return "ok";
}
//@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";
}
- 예제에서 @ModelAttribute 동작 방식
- HelloData 객체 생성
- 요청 파라미터 이름으로 HelloData 객체의 프로퍼티를 찾는다.
- 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 바인딩(입력)한다.
ex) 파라미터 이름이 username이면 setUsername() 메소드를 찾아서 호출하고 파라미터로 값을 입력한다.
- 파라미터에 다른 타입이 들어가면 바인딩 오류가 발생한다.
- @ModelAttribute는 생략할 수 있다.
- 스프링은 생략 시 다음 규칙을 따른다.
- String, int, Integer와 같이 단순 타입은 @RequestParam으로 인식
- 나머지는 @ModelAttribute로 인식 (argument resolver로 지정해둔 타입은 제외)
- 스프링은 생략 시 다음 규칙을 따른다.
- 프로퍼티 : 필드와 메소드 간 기능의 중간인 클래스 멤버의 특수한 유형을 의미한다.
- 일반적으로 Getter, Setter 메소드 호출로 변환된다.
- ex) 객체에 getUsername(), setUsername() 메소드가 있으면 이 객체는 username이라는 프로퍼티를 가지고 있는 것이다. username의 프로퍼티 값을 변경하면 setUsername()이 호출, 조회하면 getUsername()이 호출된다.
HTTP 요청 메시지 - 단순 텍스트
- 요청 파라미터와 다르게 HTTP 메시지 바디를 통해 직접 데이터가 넘어오는 경우는 @RequestParam, @ModelAttribute를 사용할 수 없다.
- 요청 파라미터 = HTML Form 방식, GET 쿼리 파라미터 방식
@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("messageBody={}", messageBody);
response.getWriter().write("ok");
}
- 메시지 바디로 넘어오는 경우 Stream을 통해 데이터를 받아와야 한다.
- Stream은 byte 타입이기에 StreamUtils.copyToString()을 통해 문자열로 변환해줘야 한다.
- 메소드의 파라미터로 request/response 말고 InputStream, Writer를 직접 받아올 수 있다.
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException {
String messageBody = httpEntity.getBody();
log.info("messageBody={}", messageBody);
return new HttpEntity<>("ok");
}
- HttpEntity : HTTP header, body 정보를 편리하게 조회할 수 있도록 도와주는 객체이다.
- 메시지 바디 정보를 직접 조회 가능
- 요청 파라미터를 조회하는 기능과는 관계가 없다.
- 응답에도 사용할 수 있다.
- HttpEntity를 상속받은 RequestEntity, ResponseEntity도 있다.
- HTTP 상태 코드 지정 가능
- HttpMessageConverter : 스프링MVC 내부에서 HTTP 메시지 바디를 읽어서 문자나 객체로 변환해서 전달해주는 기능이다.
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) throws IOException {
log.info("messageBody={}", messageBody);
return "ok";
}
- @RequestBody : HTTP 메시지 바디 정보를 편리하게 조회할 수 있도록 도와주는 어노테이션이다.
- 메시지 바디 정보를 읽어서 해당 객체에 담아주는 역할
- 헤더 정보가 필요한 경우 HttpEntity나 @RequestHeader를 사용하면 된다.
- @ResponseBody : 응답 결과를 HTTP 메시지 바디에 직접 담아서 전달하도록 도와주는 어노테이션이다.
- 이 경우 view를 사용하지 않는다.
- 요청 파라미터 vs HTTP 메시지 바디
- 요청 파라미터를 조회하는 기능 : @RequestParam, @ModelAttribute
- HTTP 메시지 바디를 직접 조회하는 기능 : @RequestBody
- @RequestBody, @ResponseBody 모두 HttpEntity로 대체해서 사용할 수 있다.
HTTP 요청 메시지 - JSON
@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 방식
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData helloData) throws IOException {
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
return "ok";
}
//HttpEntity 방식
@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 메시지 컨버터가 HTTP 메시지 바디 내용을 우리가 원하는 문자나 객체등으로 변환해준다.
- HTTP 메시지 컨버터에는 문자열을 처리하는 컨버터, JSON을 처리하는 컨버터 등이 있다.
- @RequestBody는 생략하면 안된다.
- @RequestBody 생략 시 단순 타입은 @RequestParam, 나머지는 @ModelAttribute로 취급된다.
- JSON 타입의 경우 HTTP 요청 시 Content-Type이 application/json인지 꼭 확인해야 한다.
- 그래야 JSON을 처리할 수 있는 HTTP 메시지 컨버터가 실행된다.
@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가 붙으면 객체를 JSON 형태로 반환할 수 있다.
- @ResponseBody를 사용하면 문자열 뿐만 아니라 객체를 HTTP 메시지 바디에 직접 넣을 수 있다.
- 예제에서 HttpMessageConverter 동작 방식
- 클라이언트에게서 전달받은 JSON 데이터를 @RequestBody에 의해 HelloData 객체로 변환
- HelloData 객체를 @ResponseBody에 의해 JSON 데이터 타입으로 변환 후 반환
- 객체(JSON)를 반환하는 경우 HTTP Accept가 application/json 타입인지 확인해야 한다.
HTTP 응답 - 정적 리소스, 뷰 템플릿
- 정적 리소스 : 파일의 변경 없이 그대로 서비스하는 것
- 스프링 부트는 클래스 패스의 다음 디렉토리에 있는 정적 리소스를 제공한다.
/static, /public, /resources, /META-INF/resources - ex) 웹 브라우저에 정적인 HTML, css, js를 제공할 때
- 스프링 부트는 클래스 패스의 다음 디렉토리에 있는 정적 리소스를 제공한다.
- 뷰 템플릿 사용
- 뷰 템플릿 경로는 src/main/resources/templates 이다.
- ex) 웹 브라우저에 동적인 HTML을 제공할 때
- HTTP 메시지 사용
- HTTP API를 제공하는 경우 HTML이 아닌 데이터를 전달해야 하므로 HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 실어 보낸다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p th:text="${data}">empty</p>
</body>
</html>
- Thymleaf 뷰 템플릿 예제
- 렌더링 시 <p th:text="${data}">empty</p> 태그로 인해 Content(empty)에 data 변수 값이 치환되게 된다.
//뷰 템플릿 호출 예제
@RequestMapping("/response-view-v1")
public ModelAndView responseViewV1() {
ModelAndView mav = new ModelAndView("response/hello")
.addObject("data", "hello!");
return mav;
}
@RequestMapping("/response-view-v2")
public String responseViewV2(Model model) {
model.addAttribute("data", "hello!!");
return "response/hello";
}
@RequestMapping("/response/hello")
public void responseViewV3(Model model) {
model.addAttribute("data", "hello!!");
}
- String을 반환하는 경우
- @ResponseBody가 없으면 뷰 리졸버가 실행되서 뷰의 논리이름으로 뷰를 찾고 렌더링한다.
- @ResponseBody가 있으면 뷰 리졸버를 실행하지 않고 HTTP 메시지 바디에 직접 내용을 입력한다.
- @ResponseBody나 HttpEntity를 사용하는 것은 뷰 템플릿을 사용하는 것이 아닌 HTTP 메시지 바디에 직접 응답 데이터를 출력하는 것이다.
- 예제의 경우 resoureces/templates/response/hello.html 뷰를 찾는다.
- void를 반환하는 경우
- @Controller를 사용하고 HttpServletResponse나 OutputStream같은 HTTP 메시지 바디를 처리하는 파라미터가 없으면 요청 URL을 참고해서 논리 뷰 이름으로 사용한다.
- 뷰 논리 이름과 Controller의 매핑URL이 같은 경우 void로 반환해도 되지만 너무 명시성이 떨어지기에 권장하지 않는 방법.
- @Controller를 사용하고 HttpServletResponse나 OutputStream같은 HTTP 메시지 바디를 처리하는 파라미터가 없으면 요청 URL을 참고해서 논리 뷰 이름으로 사용한다.
- 스프링 부트에 Thymleaf 라이브러리 추가 시 자동으로 Thymleaf 뷰 리졸버와 필요한 스프링 빈들을 등록한다.
- 기본 값으로 prefix = classpath:/templates/ 이고 suffix=.html이기에 필요한 경우 application.yml/properties에 설정하면 된다.
HTTP 응답 - HTTP API, 메시지 바디에 직접 입력
//응답 - HTTP 메시지 바디에 직접 입력
@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() {
return new ResponseEntity<>("ok", HttpStatus.OK);
}
@ResponseBody
@GetMapping("/response-body-string-v3")
public String responseBodyV3() {
return "ok";
}
- ResponseEntity나 @ResponseBody를 사용해서 view를 사용하지 않고 HTTP 메시지를 직접 입력
//응답 - HTTP API (JSON)
@GetMapping("/response-body-json-v1")
public ResponseEntity<HelloData> responseBodyJsonV1() {
HelloData helloData = new HelloData();
helloData.setUsername("userA");
helloData.setAge(20);
return new ResponseEntity<>(helloData, HttpStatus.OK);
}
@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/response-body-json-v2")
public HelloData responseBodyJsonV2() {
HelloData helloData = new HelloData();
helloData.setUsername("userA");
helloData.setAge(20);
return helloData;
}
- @ResponseEntity는 HTTP 응답 코드를 설정할 수 있다.
- 동적으로 응답 코드를 설정하려면 ResponseEntity를 사용해야 한다.
- @RestController = @Controller + @ResponseBody
- 각 메소드마다 @ResponseBody를 사용하려면 번거롭기에 클래스 레벨에서 사용해도 된다.
- @ResponseBody를 클래스 레벨에서 사용하는 경우 @RestController를 사용하면 된다.
- 즉 @RestController는 Rest API(HTTP API)를 만들 때 사용하는 어노테이션이다.
HTTP 메시지 컨버터
- @ResponseBody, @RequestBody, HttpEntity 사용
- HTTP 바디에 문자 내용을 직접 반환
- 뷰 리졸버 대신 HttpMessageConverter가 동작
- 기본 문자 처리 - StringHttpMessageConverter
- 기본 객체 처리 - MappingJackson2HttpMessageConverter
- byte 처리 등 기타 여러 HttpMessageConverter가 등록되어 있다.
- 응답의 경우 클라이언트의 HTTP Accept 헤더와 서버의 컨트롤러 반환 타입 정보를 조합해서 HttpMessageConverter가 선택된다.
- HTTP 메시지 컨버터는 HTTP 요청, 응답 둘 다 사용된다.
- canRead(), canWrite() : 메시지 컨버터가 해당 클래스, 미디어 타입을 지원하는지 체크한다.
- read(), write() : 메시지 컨버터를 통해서 메시지를 읽고 쓰는 기능
- 메시지 컨버터 종류 (이외에도 많다)
- 0순위 ByteArrayHttpMessageConverter : byte[] 데이터를 처리한다.
- 클래스 타입: byte[] , 미디어타입: */* ,
- 요청 예) @RequestBody byte[] data
- 응답 예) @ResponseBody return byte[]
쓰기 미디어타입 : application/octet-stream
- 1순위 StringHttpMessageConverter : String 문자로 데이터를 처리한다.
- 클래스 타입: String , 미디어타입: */*
- 요청 예) @RequestBody String data
- 응답 예) @ResponseBody return "ok"
쓰기 미디어타입 : text/plain
- 2순위 MappingJackson2HttpMessageConverter : application/json
- 클래스 타입: 객체 또는 HashMap , 미디어타입 application/json 관련
- 요청 예) @RequestBody HelloData data
- 응답 예) @ResponseBody return helloData
쓰기 미디어타입 : application/json 관련
- 0순위 ByteArrayHttpMessageConverter : byte[] 데이터를 처리한다.
- @RequestBody String 타입과 Content-Type에 application/json이 넘어오는 경우
- String이 JSON보다 우선순위가 높고 @RequestBody의 String 타입에 의해 먼저 String 메시지 컨버터가 선택된다.
- String 메시지 컨버터의 경우 모든 Content-Type을 허용하기에 String 메시지 컨버터로 처리된다.
- HTTP 요청 데이터 읽기
- HTTP 요청이 오고 컨트롤러에서 @RequestBody / HttpEntity를 파라미터로 사용한다.
- 메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 canRead() 호출
- 대상 클래스 타입을 지원하는지
- Content-Type 미디어 타입을 지원하는지
- canRead() 조건을 만족하면 read()를 호출해서 객체 생성 후 반환
- HTTP 응답 데이터 생성
- 컨트롤러에서 @ResponseBody / HttpEntity 로 값을 반환한다.
- 메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해 canWrite() 호출
- 대상 클래스 타입을 지원하는지
- Accept 미디어 타입을 지원하는지
- canWrite() 조건을 만족하면 write()를 호출해서 HTTP 응답 메시지 바디에 데이터를 생성한다.
요청 매핑 핸들러 어댑터 구조
- ArgumentResolver : 컨트롤러가 필요로 하는 다양한 파라미터 값(객체)를 생성한다.
- 파라미터 값이 준비가 되면 컨트롤러를 호출하면서 값을 넘겨준다.
- supportsParameter()를 호출해서 해당 파라미터를 지원하는지 체크 후 지원하면 resolveArgument()를 호출해서 실제 객체를 생성한다.
- ReturnValueHandler : 응답 값을 변환하고 처리한다.
- String으로 뷰 이름을 반환해도 ReturnValueHandler가 동작하도록 처리해준다.
- HTTP 메시지 컨버터는 ArgumentResolver와 ReturnValueHandler에 각각 위치하고 동작한다.
출처 : [인프런 김영한 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술]
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 강의 | 김영한 - 인프런
김영한 | 웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습
www.inflearn.com
'Spring > [인프런 김영한 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술]' 카테고리의 다른 글
[인프런 김영한 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술] 스프링 MVC - 웹 페이지 만들기 (2) | 2024.10.08 |
---|---|
[인프런 김영한 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술] 스프링 MVC - 구조 이해 (2) | 2024.10.04 |
[인프런 김영한 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술] MVC 프레임워크 만들기 (2) | 2024.10.03 |
[인프런 김영한 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술] 서블릿, JSP, MVC 패턴 (3) | 2024.10.02 |
[인프런 김영한 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술] 서블릿 (3) | 2024.09.30 |