Search

스프링 MVC 기능

Created
2024/11/06 05:03
상태
Done
태그
spring
web service

@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
복사
HttpServletRequestHttpServletResponse를 통해 헤더를 확인하거나 헤더에 값을 추가할 수 있다.
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 메세지 컨버터는 이처럼 ArgumentResolverReturnValueHandler에서 호출되어 필요한 객체를 생성하는데 사용된다.
스프링 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 관련

참고