@RequestMapping
URL 매핑
@RequestMapping(”/hello-basic”)처럼 URL을 매핑하여, 해당 URL로 호출이 오면 애노테이션이 달린 메서드를 실행한다.
@RequestMapping({”/hello-basic”, "/hello-home"})처럼 배열을 통해 여러 URL을 매핑 할 수도 있다. 스프링 부트 3.0 이후 버전에서는 URL 맨 뒤의 slash(/)를 유지하기 때문에, /hello-basic과 /hello-basic/을 서로 다른 URL로 구분한다.
Method 지정
@RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)와 같이 Http Method를 특정하여 요청을 받을 수 있다. 이를 간편하게 @GetMapping(”/mapping-get-v1”)으로 사용할 수 있고, 이 방식이 더 간결하고 직관적이다.
@GetMapping("")
@PostMapping("")
@PatchMapping("")
@DeleteMapping("")
Java
복사
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";
}
Java
복사
이와 같이 URL의 경로에 변수를 받는 PathVariable을 사용할 수 있다. PathVariable의 이름과 변수명이 서로 같은 경우에는 위처럼 생략이 가능하고, 다른 경우에는 PathVariable(”userId”) String data와 같이 지정해주어야 한다.
조건 매핑
/**
* 파라미터로 추가 매핑
* params="mode",
* params="!mode"
* params="mode=debug"
* params="mode!=debug" (! = )
* params = {"mode=debug","data=good"} */
@GetMapping(value = "/mapping-param", params = "mode=debug")
/**
* 특정 헤더로 추가 매핑
* headers="mode",
* headers="!mode"
* headers="mode=debug"
* headers="mode!=debug" (! = ) */
@GetMapping(value = "/mapping-header", headers = "mode=debug")
/**
* Content-Type 헤더 기반 추가 매핑 Media Type * consumes="application/json"
* consumes="!application/json"
* consumes="application/*"
* consumes="*\/*"
* MediaType.APPLICATION_JSON_VALUE
*/
@PostMapping(value = "/mapping-consume", consumes = "application/json")
/**
* Accept 헤더 기반 Media Type * produces = "text/html"
* produces = "!text/html"
* produces = "text/*"
* produces = "*\/*"
*/
@PostMapping(value = "/mapping-produce", produces = "text/html")
Java
복사
이처럼 조건에 맞지 않으면 호출되지 않도록, 파라미터나 헤더 등을 조건으로 매핑할 수도 있다.
HTTP 헤더 조회
@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";
}
Java
복사
•
HttpServletRequest와 HttpServletResponse를 통해 헤더를 확인하거나 헤더에 값을 추가할 수 있다.
•
HttpMethod를 통해 HTTP 메서드를 조회할 수 있다.
•
Locale을 통해 Locale 정보를 조회한다.
•
@RequestHeader MultiValueMap<String, String> headerMap 형태를 통해 HTTP 요청의 모든 헤더 정보를 받아올 수 있다.
•
@RequestHeader("host") String host와 같이 특정 헤더값만 조회할 수 있다.
•
@CookieValue(value = "myCookie", required = false) String cookie를 통해 특정 쿠키를 조회할 수 있다.
HTTP 요청과 응답 파라미터
@RequestParam(쿼리 파라미터)
// v1 : HttpServletRequest를 통해 전달받기
@RequestMapping("/request-param-v1")
public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
log.info("username={}, age={}", username, age);
response.getWriter().write("ok");
}
Java
복사
// v2 : @RequestParam 애노테이션으로 전달받기
@RequestMapping("/request-param-v2")
public void requestParamV2(@RequestParam("username") String username,
@RequestParam("age") int age) {
log.info("username={}, age={}", username, age);
response.getWriter().write("ok");
}
Java
복사
// v3 : @RequestParam 애노테이션에서 쿼리 파라미터와 변수명이 같으면 생략 가능
@RequestMapping("/request-param-v3")
public void requestParamV3(@RequestParam String username,
@RequestParam int age) {
log.info("username={}, age={}", username, age);
response.getWriter().write("ok");
}
Java
복사
// v4 : String, int, long 등 단순 타입이면 애노테이션도 생략 가능
@RequestMapping("/request-param-v4")
public void requestParamV4(String username, int age) {
log.info("username={}, age={}", username, age);
response.getWriter().write("ok");
}
Java
복사
이와 같이 사용이 가능하며, V4 방식은 직관적이지 않아 권장되지 않는다.
@RequestParam(required = true), @RequestParam(required = true)처럼 값을 반드시 받지 않아도 되도록 설정할 수 있다(default true). 또한 defaultValue = “guest”처럼 해당 값이 안들어온 경우에 자동으로 채워줄 default 값을 지정해줄 수 있다.
@RequestParam Map<String, Object> paramMap 형태로 받아서 모든 파라미터들을 한 번에 받는 것도 가능하다.
@ModelAttribute
@Getter @Setter
public class HelloData {
private String username;
private int age;
}
@RequestParam String username;
@RequestParam int age;
HelloData data = new HelloData();
data.setUsername(username);
data.setAge(age);
Java
복사
일반적으로 각각 받든, paramMap으로 받든, 위처럼 매개변수로 받아 별도의 객체를 생성해 값을 넣어주는 과정이 필요하다. 스프링에는 이 과정을 자동화 해주는 @ModelAttribute 애노테이션을 제공한다.
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
return "ok";
}
Java
복사
스프링은 @ModelAttribute를 만나면 해당 객체를 생성하고, 수정자(setter)를 통해 프로퍼티의 값을 넣어준다.
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(HelloData helloData) {
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
return "ok";
}
Java
복사
이처럼 @ModelAttribute 애노테이션은 생략이 가능한데, @RequestParm도 생략이 가능하기 때문에 혼선에 주의해야 한다. 스프링은 이처럼 생략된 매개변수를 만나면 다음과 같은 순서로 적용한다.
1.
String, int, Integer, … 등 단순 타입 → @RequestParam
2.
위 1번을 제외한 나머지 → @ModelAttribute
@RequestBody, @ResponseBody
HTTP message body에 JSON이나 TEXT를 직접 담아 보내는 경우, 받는 방식들을 알아보자.
// v1 : HttpServletRequset + ServletInputStream 방식
@PostMapping("/request-body-string-v1")
public void requestBodyStringV1(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");
}
Java
복사
// v2 : InputStream + OutputStream으로 message body에 직접 읽고 쓰기
@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");
}
Java
복사
// v3 : HttpEntity를 통해 Http header, body 정보 조회 및 응답
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {
String messageBody = httpEntity.getBody();
log.info("messageBody={}", messageBody);
return new HttpEntity<>("ok");
}
Java
복사
// v4 : @RequestBody + @ResponseBody
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
log.info("messageBody={}", messageBody);
return "ok";
}
Java
복사
위처럼 @RequestBody를 사용하면 간편하게 HTTP 메세지의 body를 조회할 수 있다. 또한 @RequestBody 애노테이션을 달고 String을 반환하면, 해당 문자열에 매칭되는 View를 찾는게 아니라 문자열 그대로 HTTP message body에 담아 응답을 보낸다.
@RequestBody와 @RequestBody 어노테이션은 JSON을 주고받을 때도 사용할 수 있다.
// v1 : HttpServletRequest + HttpServletResponse
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 data = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={}, age={}", data.getUsername(), data.getAge());
response.getWriter().write("ok");
}
Java
복사
// v2 : @RequestBody 적용
private ObjectMapper objectMapper = new ObjectMapper();
@PostMapping("/request-body-json-v2")
public void requestBodyJsonV2(@RequestBody String messageBody) throws IOException {
HelloData data = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={}, age={}", data.getUsername(), data.getAge());
response.getWriter().write("ok");
}
Java
복사
// v3 : HttpEntity로 받기
@PostMapping("/request-body-json-v3")
public void requestBodyJsonV3(HttpEntity<HelloData> httpEntity) {
HelloData data = httpEntity.getBody();
log.info("username={}, age={}", data.getUsername(), data.getAge());
response.getWriter().write("ok");
}
Java
복사
// v4 : @RequestBody 적용 + 객체에 직접 받기
@PostMapping("/request-body-json-v4")
public void requestBodyJsonV4(@RequestBody HelloData data) {
log.info("username={}, age={}", data.getUsername(), data.getAge());
response.getWriter().write("ok");
}
Java
복사
아래처럼 @ResponseBody와 객체를 함께 사용하여 JSON 형태로 응답을 보내는 것도 가능하다.
// @ResponseBody로 객체를 JSON으로 message body에 넣기
@ResponseBody
@PostMapping("/request-body-json-v4")
public HelloData requestBodyJsonV4(@RequestBody HelloData data) {
log.info("username={}, age={}", data.getUsername(), data.getAge());
return data;
}
Java
복사
이처럼 @RequestBody와 @ResponseBody를 통해 HTTP message body에 있는 데이터를 읽고 쓰는게 가능한 이유는 스프링에 있는 핸들러에서 HTTP 메세지 컨버터를 거쳐 메세지를 가져오기 때문이다.
HTTP 메세지 컨버터
HTTP 메세지 컨버터는 @RequestMapping을 처리하는 핸들러 어댑터인 RequestMappingHandlerAdapter에서 호출된다.
애노테이션 기반의 컨트롤러가 HttpServletRequest, Model, @RequestParam, @ModelAttribute, @RequestBody, HttpEntity와 같이 다양한 파라미터를 처리할 수 있는 이유는 위에 나와있는 ArgumentResolver 덕분이다.
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
@Nullable WebDataBinderFactory binderFactory) throws Exception;
}
Java
복사
ArgumentResolver는 이처럼 해당 파라미터를 resolve 할 수 있는지 확인하는 supportsParameter() 메서드와 파라미터를 실제 객체로 생성해 가져오는 resolveArgument() 메서드로 이루어져 있다.
스프링에는 위 인터페이스를 구현하는 30개가 넘는 HandlerMethodArgumentResolver 구현체를 가지고 있어, 다양한 파라미터를 호출하여 사용할 수 있는 것이다. 또한 인터페이스를 직접 확장해 나만의 ArgumentResolver를 만들 수도 있다.
ReturnValue 또한 마찬가지로 ModelAndView , @ResponseBody, HttpEntity, String 등 10개가 넘는 ReturnValueHandler를 지원한다.
HTTP 메세지 컨버터는 이처럼 ArgumentResolver나 ReturnValueHandler에서 호출되어 필요한 객체를 생성하는데 사용된다.
스프링 MVC는 메세지 컨버터를 아래와 같은 경우에 적용한다.
•
HTTP 요청 : @RequestBody, HttpEntity(RequestEntity)
•
HTTP 응답 : @ResponseBody, HttpEntity(ResponseEntity)
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
}
Java
복사
HTTP 메세지 컨버터 인터페이스는 이와 같이 구성되어있는데, canRead() , canWrite() 메서드를 통해 해당 클래스나 미디어 타입을 지원하는지 체크하고 read(), write() 메서드를 통해 메세지
객체 변환을 수행한다.
•
요청 과정
◦
HTTP 요청이 오고 파라미터에 @RequestBody, HttpEntity
◦
canRead()를 호출하여 메세지를 읽을 수 있는지 확인
◦
read()를 호출해서 객체를 생성 후 반환
•
응답 과정
◦
컨트롤러에서 @ResponseBody, HttpEntity 로 값이 반환
◦
canWrite()를 호출하여 메세지를 쓸 수 있는지 확인
◦
write()를 호출해서 객체를 메세지로 변환해 HTTP 응답 body에 저장
•
ByteArrayHttpMessageConverter
◦
클래스 타입 : byte[], 미디어 타입 : */*
◦
요청 예) @RequestBody byte[] data
◦
응답 예) @ResponseBody return byte[] 쓰기 미디어타입 application/octet-stream
•
StringHttpMessageConverter
◦
클래스 타입: String , 미디어타입: */*
◦
요청 예) @RequestBody String data
◦
응답 예) @ResponseBody return "ok" 쓰기 미디어타입 text/plain
•
MappingJackson2HttpMessageConverter
◦
클래스 타입: 객체 또는 HashMap , 미디어타입 application/json 관련
◦
요청 예) @RequestBody HelloData data
◦
응답 예) @ResponseBody return helloData 쓰기 미디어타입 application/json 관련