일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- jpa 활용
- 트위터
- 김영한
- 스프링
- 예제 도메인 모델
- Bean Validation
- 일론머스크
- 프로젝트 환경설정
- QueryDSL
- 스프링 데이터 JPA
- 스프링MVC
- 로그인
- 스프링 mvc
- 기본문법
- 값 타입 컬렉션
- 타임리프 문법
- 컬렉션 조회 최적화
- JPA 활용 2
- 불변 객체
- Spring Data JPA
- 페이징
- 실무활용
- 타임리프
- 벌크 연산
- JPQL
- JPA 활용2
- 임베디드 타입
- 검증 애노테이션
- JPA
- API 개발 고급
- Today
- Total
RE-Heat 개발자 일지
스프링 MVC 1편 - [6] 기본 기능(하편) 본문
출처 : https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1
인프런 김영한님의 스프링 MVC 1편 강의를 듣고 정리한 내용입니다.
[8] HTTP 요청파라미터 - @ModelAttribute
실제 개발을 하면 요청 파라미터를 받은 후 필요한 객체를 만들고 그 값을 객체에 넣어야 하는 번거로움이 있다. 그런데 스프링에선 이 과정을 자동화해 주는 @ModelAttribute라는 편리한 기능이 있다.
HelloData
@Data
public class HelloData {
private String username;
private int age;
}
파라미터를 담을 객체 생성
참고 : 롬복의 @Data 애노테이션
=> Getter , @Setter , @ToString , @EqualsAndHashCode , @RequiredArgsConstructor를 자동으로 적용해 줌.
RequestParamController: model-attribute-v1 @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";
}
@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 객체를 만들고 파라미터를 담는 과정을 건너뛴 것을 확인할 수 있다.
스프링 MVC @ModelAttribute
1. HelloData객체를 생성
2. 요청 파라미터 이름으로 HelloData객체의 프로퍼티를 찾음.
3. 찾으면 해당 프로퍼티의 setter를 호출해 파라미터 값을 입력(바인딩).
RequestParamController: model-attribute-v2
@ResponseBody
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(HelloData helloData){
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
log.info("helloData={}", helloData);
return "ok";
}
@ModelAttribute 생략 가능
이유 : 스프링은 String, int, Integer 등 단순 타입은 @RequestParam 그 외는 @ModelAttribute로 자동 처리
[9] HTTP 요청 메시지 - 단순 텍스트
Http message body에 데이터를 직접 담아서 요청하는 방식
- HTTP API에서 주로 사용 (JSON, XML, TEXT)
- 데이터 형식은 주로 JSON 사용
- POST, PUT, PATCH
HTTP message body에 데이터를 담아서 요청할 땐 @RequestParam과 @ModelAttribute를 사용할 수 없다. 그럴 땐 어떻게 읽어야 할까?
RequestBodyStringController
@Slf4j
@Controller
public class RequestBodyStringController {
@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");
}
@PostMapping("/request-body-string-v2")
public void requestBodyStringV2(InputStream inputStream, Writer responseWriter) throws IOException {
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
responseWriter.write("ok");
}
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException {
String body = httpEntity.getBody(); // http 메시지의 body를 꺼냄
log.info("messageBody={}", body);
return new HttpEntity<String>("ok"); // 맨 처음에 return 메시지 값
}
//참고용 RequestEntity, ResponseEntity
@PostMapping("/request-body-string-v3-v2")
public HttpEntity<String> requestBodyStringV32(RequestEntity<String> httpEntity) throws IOException {
String body = httpEntity.getBody(); // http 메시지의 body를 꺼냄
log.info("messageBody={}", body);
return new ResponseEntity<>("ok", HttpStatus.CREATED); // 맨 처음에 return 메시지 값
}
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
log.info("messageBody={}", messageBody);
return "ok";
}
}
v1 : InputStream을 사용해 직접 읽을 수 있다.
v2 : 스프링이 지원하는 파라미터인 InputStream, OutputStream을 활용해 코드를 더 간결하게 만듦.
v3 : HttpEntity로 HTTP header, body 정보를 편리하게 조회.
v3-v2 : HttpEntity를 상속받은 RequestEntity, ResponseEntity도 사용 가능
=> ResponseEntity는 HTTP 상태 코드 설정 가능하다는 게 차이점( ex) HttpStatus.CREATED)
v4 : @RequestBody로 HTTP 메시지 바디 정보를 편리하게 조회. 헤더 정보가 필요하면 HttpEntity를 활용하거나 @RequestHeader를 쓰면 된다.
!!!중요!!!
요청 파라미터는 GET의 쿼리스트링 또는 HTML Form방식인 경우에만 한함. 그 외엔 HttpEntity를 사용하거나 데이터를 직접 꺼내야 한다.
1. 요청파라미터 : @RequestParam, @ModelAttribute
2. HTTP 메시지 바디 조회 : @RequestBody
[10] HTTP 요청 메시지 - JSON
RequestBodyJsonController
/*
* {"username":"hello", "age":20}
* content-type:application/json
* */
@Slf4j
@Controller
public class RequestBodyJsonController {
private ObjectMapper objectMapper = new ObjectMapper();
@PostMapping("/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("messageBody={}", messageBody);
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
response.getWriter().write("ok");
}
@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); //굳이 ObjectMapper로 바꿔야 하나?
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
return "ok";
}
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData data) {
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) {
HelloData data = httpEntity.getBody();
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
log.info("username={}, age={}", data.getUsername(), data.getAge());
return data; // 이렇게 하면 JSON값으로 반환 가능
}
}
v1 : 메시지 바디 JSON 데이터를 inputStream으로 불러온 후 String화 => ObjectMapper를 활용해 원하는 객체에 매핑
v2 : @RequestBody로 HTTP 메시지 데이터를 꺼내고 messageBody에 저장 -> 문자화된 JSON 데이터를 ObjectMapper로 자바 객체로 변환
v3 : @RequestBody로 직접 만든 객체(HelloData)에 매핑 가능. 단, 단순 타입을 제외하면 모두 @ModelAttribute가 붙으므로 @RequestBody는 생략 불가능!
=> 생략 시 HelloData data -> @ModelAttribute HelloData data가 되어 버림
v4 : HttpEntity를 활용한 방식
v5 : @ResponseBody를 사용하면 해당 객체를 HTTP 메시지 바디에 직접 넣을 수 있음. JSON값으로 반환 가능
@RequestBody : JSON 요청 -> HTTP 메시지 컨버터 -> 객체
@RespnseBody : 객체 -> HTTP 메시지 컨버터 -> JSON 응답
[11] 응답 - 정적 리소스, 뷰 템플릿
정적 리소스 : 웹브라우저에 정적인 HTML, CSS, JS를 제공할 땐 정적 리소스 사용
뷰 템플릿 사용 : 웹브라우저에 동적인 HTML을 제공할 땐 뷰 템플릿 사용(jsp, thymeleaf 등)
1] 정적 리소스
정적 리소스 경로 : src/main/resources/static
2] 뷰 템플릿
뷰 템플릿 경로 : src/main/resources/templates
hello.html
<!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>
① th:text는 empty부분을 치환해 줌.
② ${data}는 Model에 있는 데이터 중 data를 꺼내쓸 때 사용.
ResponseViewController
@Slf4j
@Controller
public class ResponseViewController {
@RequestMapping("/response-view-v1")
public ModelAndView responseViewV1(){
ModelAndView mav = new ModelAndView("response/hello");
mav.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!");
}
}
v1 : ModelAndView에 논리이름 담고, data라는 변수에 hello! 값을 담아 리턴
v2 : 스프링이 제공하는 매개변수인 Model 객체를 받고 데이터를 넣음. 그 후 논리이름을 반환
v3 : 이러면 requestMapping 이름을 논리이름으로 사용함. 그러나 명시성이 떨어져 추천하지 않는 방법
◎ 타임리프 스프링부트 설정방법
build.gradle에 이미 추가 돼 있음(https://start.spring.io/의 디펜던시에 타임리프 추가했기 때문)
스프링부트가 타임리프에 필요한 ThymeleafViewResolver와 필요한 스프링 빈들을 자동 등록.
[12] HTTP 응답 - HTTP API, 메시지 바디에 직접 입력
HTTP API를 제공할 땐 HTML이 아닌 데이터를 전달해야 하므로 HTTP 메시지 바디에 JSON, TEXT 형식의 데이터를 실어 보내야 한다.
ResponseBodyController
@Slf4j
@RestController
public class ResponseBodyController {
@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() throws IOException {
return new ResponseEntity<>("ok", HttpStatus.OK);
}
//@ResponseBody
@GetMapping("/response-body-string-v3")
public String responseBodyV3() {
return "ok";
}
@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);
}
//@ResponseBody
@GetMapping("/response-body-json-v2")
public HelloData responseBodyJSONV2(){
HelloData helloData = new HelloData();
helloData.setUsername("userA");
helloData.setAge(20);
return helloData;
}
@ResponseStatus(HttpStatus.OK)
//@ResponseBody
@GetMapping("/response-body-json-v3")
public HelloData responseBodyJSONV3(){
HelloData helloData = new HelloData();
helloData.setUsername("userA");
helloData.setAge(20);
return helloData;
}
}
String
v1 : Writer객체를 불러 메시지 바디에 담을 내용 직접 입력.
v2 : ResponseEntity<>로 메시지 바디에 담을 내용 + 상태코드 추가(HttpStatus.OK)
v3 : @ResponseBody를 활용해 메시지 바디에 직접 입력.
JSON
v1 : ResponseEntity를 반환. HTTP 메시지 컨버터를 거쳐 JSON 형식으로 반환됨.
v2 : @ResponseBody로 객체 반환
v3 : ResponseEntity가 아닌 @ResponseBody로 반환하면 HTTP 응답코드를 설정하기 어려운데, @ResponseStatus(HttpStatus.OK) 애노테이션을 활용하면 응답코드도 설정 가능
@RestController = @ResponseBody + @Controller
해당 컨트롤러 모두에 @ResponseBody가 적용되는 효과. 따라서 뷰 템플릿을 사용하지 않고 HTTP 메시지 바디에 직접 데이터를 입력하는 형식. 즉, Rest API를 만들 때 사용하는 컨트롤러다.
[13] HTTP 메시지 컨버터
뷰 템플릿으로 HTML을 생성해 응답하지 않고, HTTP API처럼 JSON 데이터를 HTTP 메시지 바디에서 직접 읽거나 쓸 때 HTTP 메시지 컨버터를 사용하면 편리하다.
스프링 MVC는 다음 같은 케이스에 HTTP 메시지 컨버터를 사용한다.
- HTTP request : @RequestBody, HttpEntity(RequestEntity)
- HTTP response : @ResponseBody, HttpEntity(ResponseEntity)
스프링 HttpMessageConverter의 우선순위
0순위 = ByteArrayHttpMessageConverter
[클래스 타입 : byte[], 미디어타입 : */*]
예시) @RequestBody byte[] data, @ResponseBody return byte[]
1순위 = StringHttpMessageConverter
[클래스 타입 : String, 미디어타입 : */*]
예시) @RequestBody String data, @ResponseBody return "ok"
2순위 = MappingJackson2HttpMessageConverter
[클래스 타입 : 객체·HashMap, 미디어타입 : application/json]
예시) @RequestBody HelloData data, @ResponseBody return helloData
위 코드의 경우 클래스타입이 객체이므로 MappingJackson2HttpMessageConverter가 호출. 그런데 미디어 타입이 application/json이 아닌 text/html이므로 오류 발생.
HttpMessageConverter의 내부 메서드
① canRead() : 메시지 컨버터가 요청 데이터를 대상으로 해당 클래스·미디어타입을 지원하는지 체크
② canWrite() : 메시지 컨버터가 응답 데이터를 대상으로 해당 클래스·미디어타입을 지원하는지 체크
③ read() , write() : 메시지 컨버터를 통해서 메시지를 읽고 쓴다.
[14] 요청 매핑 핸들러 어댑터 구조
그렇다면 HTTP 메시지 컨버터는 스프링 구조 어디 부분에서 이용될까?
전체적인 구조 중 핸들러 어댑터에서 핸들러로 넘어가는 과정에서 HTTP 메시지 컨버터가 사용되는데,
우선 애노테이션 기반 컨트롤러에서 우리는 다양한 파라미터(HttpServletRequest, Model, @RequestParam, @ModelAttribute 등)를 사용할 수 있는데, 이는 HandlerMethodArgumentResolver(a.k.a ArgumentResolver) 덕분이다.
ArgumentResolver
동작방식
1] supportsParameter()를 호출해 해당 파라미터 지원하는지 확인
2] 지원하면 resolverArgument()를 호출해 실체 객체를 생성하고, 이 생성된 객체가 컨트롤러 호출 시 넘어감
ReturnValueHandler(=HandlerMethodReturnValueHandler)
ArgumentResolver와 비슷한데, 응답값을 반환하고 처리한다는 게 차이점.
ArgumentResolver가 HTTP 메시지의 요청(@RequestBody, HttpEntity), ReturnValueHanlder가 HTTP 메시지의 응답(@ResponseBody, HttpEntity) 등을 처리할 때 쓰는 게 HTTP 메시지 컨버터다.
기능 확장
필요하면 ArgumentResolver·ReturnValueHandler도 확장 가능하다. 기능 확장은 "WebMvcConfigurer"를 상속받아서 "스프링 빈"으로 등록하면 된다. 단, 스프링이 대부분 기능을 제공하기 때문에 잘 쓰일 일은 없다.
WebMVCConfigurer
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
//...
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//...
}
};
}
'백엔드 > 스프링' 카테고리의 다른 글
스프링 MVC 1편 - [7] 웹 페이지 만들기 [하편] (0) | 2023.07.06 |
---|---|
스프링 MVC 1편 - [7] 웹 페이지 만들기 [상편] (0) | 2023.07.05 |
스프링 MVC 1편 - [6] 기본 기능(상편) (0) | 2023.06.30 |
스프링 MVC 1편 - [5] 스프링 MVC 구조 이해 (0) | 2023.06.29 |
스프링 MVC 1편 - [4] MVC 프레임워크 만들기 (0) | 2023.06.23 |