후라이
[SpringMVC] Spring MVC 파헤치기 | 요청편 본문
오늘은 Spring MVC의 기능들을 깊게 살펴보겠습니다.
(복습용..)
1. 요청 매핑
@ResrController
@Slf4j
public class MappingController {
@RequestMapping("/hello/basic")
public String helloBasic() {
log.info("helloBasic");
return "ok";
}
}
@ResetController : 원래 @Controller는 반환값이 String이면 이 string이 뷰 이름으로 인식됩니다.
그래서 뷰를 찾고 렌더링하게 되는데 @RestController는 반환 값으로 바로 HTTP 메시지 바디에 입력으로 들어가게 됩니다.
@RequestMapping : /hello-basic URL 호출이 오면 이 메서드가 실행되도록 매핑합니다.
다중 배열을 제공하므로 {"/hello-basic", "/hello-go"} 와 같이 쓸 수 있습니다.
참고로 여기서 스프링 부트 3.0 이후 버전부터는 /hello-basic과 /hello-basic/은 서로 다른 URL 요청을 사용해야 합니다.
매핑: /hello-basic -> URL 요청: /hello-basic
매핑: /hello-basic/ -> URL 요청: /hello-basic/
2. HTTP 메서드
위에서 본 예시처럼 @RequestMapping에는 별도의 Method가 지정되어 있지 않으므로 HTTP 메서드와 무관하게 호출됩니다.
GET, HEAD, POST, PUT, PATCH, DELETE
2.1) HTTP 메서드 매핑
HTTP 메서드 매핑을 위해
@RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
이렇게 method 지정자를 사용할 수도 있지만 더 간단하게
@GetMapping(value = "/mapping-get-v2")
@GetMapping 애너테이션을 쓰면 되겠습니다.
2.2) @PathVariable
또한, PathVariable(경로변수)를 사용할 수도 있습니다.
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {
log.info("mappingPath userId={}", data);
return "ok";
}
만약 http://localhost:8080/mapping/userA 에 접속하면 HTTP 바디 메시지에 ok가 담겨 ok 사인을 볼 수 있겠죠?
PathVariable()의 이름과 파라미터 이름이 같으면 생략하고
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable String userId) {
log.info("mappingPath userId={}", userId);
return "ok";
}
이렇게 쓰면 됩니다 :)
@PathVarible도 다중 사용이 가능합니다.
2.3) 미디어 타입 조건 매핑 - HTTP 요청 Content-Type, consume
@PostMapping(value="/mapping-consume", consumes = "application/json)
public String mappingConsumes() {
log.info("mappingConsumes");
return "ok";
}
HTTP 요청의 Content-Type 헤더를 기반으로 미디어 타입으로 매핑합니다.
만약, 맞지 않으면 HTTP 415 상태코드(Unsupported Media Type)을 반환하게 됩니다.
consumes = "text/plain"
consumes = {"text/plain", "application/*"}
consumes = MediaType.TEXT_PLAIN_VALUE
위 예시처럼 사용하면 되겠습니다.
2.4) 미디어 타입 조건 매핑 - HTTP 요청 Accept, produce
@PostMapping(value = "/mapping-produce", produces = "text/html")
public String mappingProduces() {
log.info("mappingProduces");
return "ok";
}
HTTP 요청의 Accept 헤더를 기반으로 미디어 타입으로 매핑합니다.
만약, 맞지 않으면 HTTP 406 상태코드(Not Acceptable)을 반환하게 됩니다.
produces = "text/plain"
produces = {"text/plain", "application/*"}
produces = MediaType.TEXT_PLAIN_VALUE
produces = "text/plain;charset=UTF-8"
위 예시처럼 사용하면 되겠습니다.
3. 요청 매핑 - API 예시
회원 관리를 HTTP API로 만든다고 가정해봅시다. 그럼 우리는 아래와 같이 회원관리 API를 설계할 수 있습니다.
- 회원 목록 조회 : GET /users
- 회원 등록 : POST /users
- 회원 조회 : GET /users/{userId}
- 회원 수정 : PATCH /users/{userId}
- 회원 삭제 : DELETE /users/{userId}
@RestController
@RequestMapping("/mapping/users")
public class MappingClassController {
// GET /mapping/users
@GetMapping
public String users() {
return "get users";
}
// POST /mapping/users
@PostMapping
public String addUser() {
return "post user";
}
// GET /mapping/users/{userId}
@GetMapping("/{userId}")
public String findUser(@PathVariable String userId) {
return "get userId=" + userId;
}
// PATCH /mapping/users/{userId}
@PatchMapping("/{userId}")
public String updateUser(@PathVariable String userId) {
return "update userId=" + userId;
// DELETE /mapping/users/{userId}
@DeleteMapping("/{userId}")
public String deleteUser(@PathVariable String userId) {
return "delete userId=" + userId;
}
}
이렇게 매핑 애너테이션을 통해 매핑할 수 있었는데요,
이제 HTTP 요청이 보내는 "데이터"들을 스프링 MVC로 어떻게 조회하는지 알아봅시다.
4. 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";
}
}
어떤 파라미터들이 있는지 참고용으로 보시면 좋을 것 같습니다.
- HttpServletRequest와 HttpServletResponse
- HttpMethod : HTTP 메서드 조회
- Locale : Locale 정보 조회
- @RequestHeader MultivalueMap<String, String> headerMap : 모든 HTTP 헤더를 MultiValueMap 형식으로 조회
- @RequestHeader("host") String host : 특정 HTTP 헤더를 조회
- @Cookie Value(value = "myCookie", required = false) String cookie : 특정 쿠키를 조회
5. HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form
클라이언트에서 서버로 요청 데이터를 전달하는 방법은 주로 아래의 3가지 방법을 사용합니다.
- GET - 쿼리 파라미터
- /url**?username=hello&age=20**
- 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달하는 방식입니다.
- ex) 검색, 필터, 페이징 등 - POST - HTML Form
- content-type: application/x-www-form-urlencoded
- 메시지 바디에 쿼리 파라미터 형식으로 전달하는 방식입니다. username=hello&age=20
- ex) 회원 가입, 상품 주문, HTML form 사용 - HTTP message body에 데이터를 직접 담아서 요청
- HTTP API에서 주로 사용하며, JSON, XML, TEXT를 사용합니다.
- 데이터 형식은 주로 JSON을 사용합니다.
- POST, PUT, PACTH 메서드도 사용 가능
5.1) 요청 파라미터 - 쿼리 파라미터, HTML Form
HttpServletRequest와 request.getPatameter()를 사용하면 쿼리 파라미터 전송과 HTML Form 전송 모두를 조회할 수 있습니다.
GET, 쿼리 파라미터 전송
http://localhost:8080/request-param?username=hello&age=20
POST, HTML Form 전송
POST /request-param ...
content-type: application/x-www-form-urlencoded
username=hello&age=20
GET 쿼리 파라미터 전송 방식, POST HTML Form 전송 방식 모두 사실상 형식이 같으므로 구분없이 조회 가능합니다.
=> 요청 파라미터(request parameter) 조회
이제 이 요청 파라미터 조회 방법에 대해 알아봅시다.
제일 단순하게는 HttpServletRequest를 사용해서 request.getParameter("username") -> 이런식으로 파라미터를 가져올 수 있겠습니다만, 스프링에서 제공하는 @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 : 파라미터 이름으로 바인딩
참고로 여기서 나온 @ResponseBody는 @RestController와 비슷하게 View 조회를 무시하고 HTTP message body에 직접 해당 내용을 입력하게 됩니다.
그리고 더 간단하게, @RequestParam의 이름과 변수 이름이 같으면 @RequestParam(name="xx") 생략이 가능합니다.
또한, 파라미터가 String, int, Integer 등의 단순 타입이면 @RequestParam도 생략할 수 있습니다!
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(String useranme, int age) {
log.info("username={}, age={}", useranme, age);
return "ok";
}
무척 간단해졌죠?
하지만 애너테이션을 완전히 생략하는 방법은 추천하지 않습니다.
애너테이션을 사용함으로써, 어느정도 코드상으로 요청 파라미터에서 데이터를 읽는다는 정보가 드러나게끔 리팩토링 하는 것이 좋을 듯 합니다.
5.2) HTTP 요청 파라미터 - @ModelAttribute
실제 개발 과정에서 요청 파라미터를 받아 필요한 객체를 만들고, 그 객체에 값을 넣어주는 식으로 설계를 할텐데요
그럼
@RequestParam String username, @RequestParam int age를 가져온 후
HelloData data= new HelloDate()라고 하는 객체에
data.setUsername(username);
data.setAge(age)l 이렇게 작성하게 될 것입니다.
스프링은 이 과정을 완전히 자동화해주는 @ModelAttribute 기능을 제공합니다.
@Data
public class HelloData {
private String username;
private int age;
}
우선 이렇게 간단한 HelloData라는 객체가 있다고 합시다.
@ModelAttribute를 어떻게 사용하냐면
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
return "ok";
}
이렇게 메서드의 파라미터에 @ModelAttribute HelloData hellodata 를 써주면 됩니다.
정말 신기하게,, HelloData 객체가 생성되고 요청 파라미터 값들이 이 객체에 들어가게 됩니다.
- HelloData 객체를 생성한다.
- 요청 파라미터 이름으로 HelloData 객체의 프로퍼티를 찾습니다. 그리고 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩)하게 됩니다.
- ex) 파라미터 이름이 username이면 setUsername() 메서드를 찾아서 호출하면서 값을 입력
프로퍼티?
객체에 getUsername(), setUsername() 메서드가 있으면, 이 객체는 username이라는 프로퍼티를 가지고 있다고 합니다.
앞서 다른 애너테이션처럼 @ModelAttribute도 생략이 가능합니다. 하지만, 개발자가 코드를 보기에 @RequestParam과 혼란이 발생할 수 있습니다.
- String , int , Integer 같은 단순 타입 = @RequestParam
- 나머지 = @ModelAttribute (argument resolver 로 지정해둔 타입 외)
6. HTTP 요청 메시지 - 단순 텍스트
앞서 클라이언트가 서버에게 데이터를 전달할 수 있는 마지막 방식이
바로 HTTP message body에 데이터를 직접 담아서 요청한다는 것이었습니다.
HTTP API에서 주로 사용하며, JSON, XML, TEXT가 담길 수 있다고 하였습니다.
요청 파라미터인 GET 쿼리, POST HTML Form과는 다르게 이 방식은
@RequestParam과 @ModelAttribute를 사용할 수 없습니다.
그럼 해당 애너테이션이 없이, 어떻게 전달하게 되는지 한번 알아봅시다!
6.1) InputStream
가장 단순한 텍스트 메시지를 HTTP 메시지 바디에 담아서 전송하고, 읽어보는 상황입니다.
HTTP 메시지 바디의 데이터를 InputStream을 사용해서 직접 읽을 수 있습니다.
@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");
}
}
6.2) Input, Output 스트림, Reader
@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");
}
springMVC는 다음 파라미터를 지원합니다.
- InputStream(Reader) : HTTP 요청 메시지 바디의 내용을 직접 조회
- OutputStream(Writer) : HTTP 응답 메시지의 바디에 직접 결과 조회
그런데, 지금 위 두 가지 방식은 꽤나 귀찮습니다..
뭔가 자동화되는 스프링MVC의 기능을 사용하고 싶다는 생각이 들기도 하죠.?
6.3) HttpEntity
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {
String messageBody = httpEntity.getBody();
log.info("messageBody={}", messageBody);
return new HttpEntity<>("ok");
}
스프링 MVC는 HttpEntity를 파라미터를 받도록 지원하는 기능을 가지고 있습니다.
<HttpEntity>
- HTTP header, body 정보를 편리하게 조회
- 메시지 바디 정보를 직접 조회
- 요청 파라미터를 조회하는 기능과 관계 없음 -> @RequestParam x, @ModelAttribute x
- 응답에도 사용 가능 -> 메시지 바디 정보 직접 반환, 헤더 정보 포함 가능, view 조회 x
이 HttpEntity의 상속을 받은 두 가지 객체들도 같은 기능을 제공합니다.
- RequestEntity
- HttpMethod, url 정보 추가, 요청에서 사용 - ResponseEntity
- HTTP 상태 코드 설정 가능, 응답에서 사용
- return new ResponseEntity<String>("Hello world", responseHeaders, HttpStatus.CREATED)
참고
스프링 MVC 내부에서 HTTP 메시지 body를 읽어 문자나 객체로 변환해서 전달해주는데, 이때 HTTP Messgae Converter라는 기능을 사용합니다. 추후에 설명하겠습니다.
6.4) @RequestBody
HttpEntity보다 더 간단하게 사용할 수 있는 애너테이션이 있습니다.
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
log.info("messageBody={}", messageBody);
return "ok";
}
그냥 @RequestBody 애너테이션을 파라미터로 넣어서 HTTP 메시지 바디 정보를 편리하게 조회할 수 있습니다.
참고로 헤더 정보가 필요하다면 HttpEntity를 사용하거나 @RequestHeader를 사용하면 됩니다.
*메시지 바디를 직접 조회하는 기능은 요청 파라미터를 조회하는 @RequestParam, @ModelAttribute와 전혀 관계가 없다.
- 요청 파라미터를 조회하는 기능: @RequestParam , @ModelAttribute
- HTTP 메시지 바디를 직접 조회하는 기능: @RequestBody
7. HTTP 요청 메시지 - JSON
7.1) ObjectMapper
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
HelloData data = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={}, age={}", data.getUsername(), data.getAge());
response.getWriter().write("ok");
이렇게 사용할 수 있습니다.
HttpServletRequest를 사용해 직접 HTTP 메시지 바디에서 데이터를 읽어와서, 문자로 변환하는 방식입니다.
문자로 된 JSON 데이터를 Jackson 라이브러리인 ObjectMapper를 사용해서 자바 객체로 변환합니다.
이 방식 또한 복잡하고 길어집니다..
7.2) @RequestBody
@ResponseBody
@PostMapping("/request-body-json-v2")
public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException {
HelloData data = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
그렇다면 이전에 배웠던 @RequestBody를 사용해서 HTTP 메시지에서 데이터를 꺼내고 messageBody에 저장하면 됩니다.
문자로 된 JSON 데이터인 messageBody를 objectMapper를 통해 자바 객체로 변환합니다.
아니 그럼, 문자로 변환하고 다시 json으로 변환하는 과정이 불편한데?
@ModelAttribute처럼 한번에 객체로 변환할 순 없나?
7.3) @RequestBody 객체 변환
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData data) {
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
그냥 @RequestBody에 냅다 객체를 꼬라박으면 됩니다.
앞전에 계속 사용한 HelloData 객체를 위처럼 @RequestBody 파라미터로 넣은 것을 확인할 수 있습니다.
HttpEntity,@RequestBody를 사용하면 HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 우리가 원하는 문자나 객체 등으로 변환해줍니다.
HTTP 메시지 컨버터는 문자 뿐만 아니라 JSON도 객체로 변환해줍니다.
위 경우엔 @RequestBody를 생략해버리면 @ModelAttibute가 적용되어 요청 파라미터를 처리하게 되어버립니다..
생략하면 안됩니다.
7.4) HttpEntuty
앞서 단순 텍스트에서 사용했듯이 HttpEntity를 활용해도 됩니다.
@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";
}
7.5) 객체 직접 반환
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
log.info("username={}, age={}", data.getUsername(), data.getAge());
return data;
}
위 처럼, 객체를 HTTP 메시지 바디에 직접 넣어줄 수도 있습니다. (해당 경우에도 HttpEntity를 사용해도 됨)
@RequestBody 요청 -> JSON 요청 HTTP 메시지 컨버터 객체
'Spring' 카테고리의 다른 글
[Thymeleaf] 타임리프 알아보기 (0) | 2024.12.27 |
---|---|
[SpringMVC] Spring MVC 파헤치기 | 응답편 (2) | 2024.12.26 |
[Spring MVC] MVC 프레임워크 <2> (1) | 2024.12.19 |
[Spring MVC] MVC 프레임워크 <1> (1) | 2024.12.19 |
[Spring MVC] 웹 시스템 | 서블릿 | HTML | HTTP API (0) | 2024.12.15 |