김영한 강사님의 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술을 듣고 정리한 내용입니다. 자세한 내용은 강의를 참고해주세요
http에 url로 요청이 오면, 어떻게 컨트롤러와 매칭을 하는지 알아보겠다
Controller
RestController
- 스프링 MVC에서 에노테이션 기반의 컨트롤러로 인식한다. Controller와 ResponseBody역할을 같이 한다
RequestMapping(url)
method = Request.GET이렇게 인자값을 넣어서 요청 헤더를 지정할 수 있다GetMapping, PostMapping등등이 있다@RequestMapping은 URL 경로를 템플릿화 할 수 있는데, @PathVariable을 사용하면 매칭 되는 부분을 편리하게 조회할 수 있다@RestController
public class MappingController {
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data){
log.info("mappingPath userId = {}", data);
return "ok";
}
}
@PathVariable("userId") 인자로 메서드에서 받았다다음은 요청메핑에 들어가는 파라미터들을 봐보겠다
params = "mode=debug" : 파라미터에 꼭들어와야 된다. 특정 파라미터가 있어야 매핑이 된다. /mapping-param?mode=debugheaders = "mode=debug" : 요청 http 메시지 header를 사용해야 한다consumes = "application/json" : Http 요청의 Content-type 헤더기반 타입으로 매핑한다. 맞지 않으면 HTTP 415 상태코드 반환produces = "text/html" : 클라이언트 요청의, Accept 헤더를 기반의 미디어 타입으로 매핑한다. 맞지 않으면 HTTP 406 상태코드 반환회원관리 API라고 해보자
@RestController
@RequestMapping("/mapping/users")
public class MappingClassController {
@GetMapping
public String users(){
return "get users";
}
@PostMapping
public String addUsers(){
return "post user";
}
@GetMapping("/{userId}")
public String findUser(@PathVariable("userId") String userId){
return "get userId" + userId;
}
@PatchMapping("/{userId}")
public String updateIser(@PathVariable("userId") String userId){
return "update userId" + userId;
}
@DeleteMapping("/{userId}")
public String deleteUser(@PathVariable("userId") String userId){
return "delete userId" + userId;
}
}
@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";
}
}
HttpServletRequestHttpServletResponseHttpMethod : HTTP 메서드를 조회한다. org.springframework.http.HttpMethod -> GETLocale : Locale 정보를 조회한다. 언어 정보!@RequestHeader MultiValueMap<String, String> headerMap, 요청 헤더의 모든 HTTP 헤더를 MultiValueMap 형식으로 조회한다.@RequestHeader("host") String hostrequireddefaultValue@CookieValue(value = "myCookie", required = false) String cookierequireddefaultValue-> 정말 많은 타입의 파라미터를 받을 수 있다... 에토이션 기반이 사기다,,,
cf) MultiValueMap
클라이언트에서 서버로 요청 데이터를 전달할 때에는 다음의 3가지 방법을 사용한다
1. GET - 쿼리 파라미터
2. POST - HTML Form
3. HTTP message body에 데이터 담아서 전달

HttpServletRequest의 getParameter() 메서드를 사용하는 방법이다@Slf4j
@Controller
public class RequestParamController {
@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");
}
@RequestParam : 쿼리 파라미터 방식의 http 요청에서, 바로 쿼리 파라미터를 파라미터로 받는방법@ResponseBody // ok이라는 반환값을 가지고 view를 찾는게 아니라 http응답 메시지 body에 넣어서 반환
@RequestMapping("/request-param-v2")
public String requestParamV2(
@RequestParam("username") String memberName,
@RequestParam("age") int memberAge) {
log.info("username={}, age={}", memberName, memberAge);
return "ok";
}
required : 우리가 파라티터를 받을 때, 필수 값을 설정하거나 받지 않아도 되는 값을 설정할 수 있다defaultValue : 만약 파라미터의 값이 들어오지 않을 때, 기본값을 넣어서 파라미터를 받을 수 있다 @ResponseBody
@RequestMapping("/request-param-default")
public String requestParamDefault(
@RequestParam(name = "username",required = true, defaultValue = "guest") String username,
@RequestParam(name = "age",required = false, defaultValue = "-1") int age){
// null, "" 는 다른거인거 인지하기!
// 원시형 자료 age,float,byte등등에는 null들어가지 못함 ->Integer,이렇게 wrapper클래스로 바꿔줘야한다
log.info("username={}, age={}", username, age);
return "ok";
}
@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";
}
@ModelAttribute : 기본적으로 @RequestParam과 비슷하다. 하지만 @ModelAttribute는 객체를 요청 파라미터로 받는 것이 차이점이다@ModelAttribute의 실행 과정이다@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData){
log.info("username={}, age={}", helloData.getUsername(),helloData.getAge());
log.info("hellomodel={}",helloData);
return "ok";
}
@RequestParam@ModelAttribute로 객체 자체를 파라미터로 받기argument resolver로 지정된 타입은 제외다@RequestParam, @ModerAttribute를 사용할 수 없다InputStream이 먼저 떠오른다InputStream@Slf4j
@Controller
public class RequestBodyStringController {
@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");
}
InputStream, OutputStream, Writer을 지원@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");
}
HttpEntity<T> : 이렇게 가져와지네...???@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");
}
가져온 httpBody에 getBody해서 message Body부분 빼서 사용, getHeader도 된다
HttpEntity: HTTP header, body 정보를 편리하게 조회
@RequestParam X, @ModelAttribute XHttpEntity<T>는 응답에도 사용 가능
http message converter : HttpEntitiy<String>
httpmessageconverter가 저렇게 동작해버린다HttpEntity<T> 를 상속받은 다음 객체들도 같은 기능을 제공한다.
RequestEntity
HttpMethod, url 정보가 추가, 요청에서 사용
ResponseEntity
HTTP 상태 코드 설정 가능, 응답에서 사용
return new ResponseEntity("Hello World", responseHeaders,
HttpStatus.CREATED)
참고
스프링MVC 내부에서 HTTP 메시지 바디를 읽어서 문자나 객체로 변환해서 전달해주는데, 이때 HTTP 메시지컨버터(HttpMessageConverter)라는 기능을 사용한다. 이것은 조금 뒤에 HTTP 메시지 컨버터에서 자세히 설명한다.
@RequestBody@RequestParam , @ModelAttribute 와는 전혀 관계가 없다.ObjectMapper을 이용해 객체로 변환해줘야 한다@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");
}
objectMapper를 사용해서 자바 객체로 변환한다.@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";
}
@ModelAttribute처럼 한번에 객체로 변환할 수는 없을까?@ResponseBody
@PostMapping("request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData helloData) {
log.info("username={}, age={}",helloData.getUsername(),helloData.getAge());
return "ok";
}
HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 우리가 원하는 문자나 객체 등으로 변환해준다.HTTPEntity<T>를 사용해도 된다@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@ResponseBody
@PostMapping("request-body-json-v5")
public HelloData requestBodyJsonV5(HttpEntity<HelloData> httpEntity) {
HelloData data = httpEntity.getBody();
log.info("username={}, age={}",data.getUsername(),data.getAge());
return data;
}
@ResponseBody를 사용해서 HelloData 타입을 바로 http message body에 담아서 응답 http 메시지를 반환해버린다!정적 리소스는 스프링 부트 다음과 같은 클래스패스 디렉토리에서 제공된다
/static , /public , /resources , /META-INF/resources
@RequestMapping("/response-view-v1")
public ModelAndView responseViewV1(){
ModelAndView mav = new ModelAndView("response/hello")
.addObject("data","hello!");
return mav;
}
ModelAndView를 생성후, addObject로 데이터를 추가하고, 반환타입을 다시 ModelAndView로 반환한다@RequestMapping("/response-view-v2")
public String responseViewV2(Model model){
model.addAttribute("data","hello!");
return "response/hello";
}
@ResponseBody가 없으면 -> 문자열의 view파일을 찾아 렌더링 한다@ResponseBody가 있으면 -> http message body에 리턴값을 넣어서 뷰 리졸버를 사용하지 않고 바로 반환한다cf) @ResponseBody , @HttpEntity를 사용하면 뷰 템플릿을 사용하는 것이 아니라 Http message body에 직접 응답 데이터를 넣어 출력할 수 있다
@Slf4j
@Controller
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(23);
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(23);
return helloData;
}
}
responseBodyV1responseBodyV2responseBodyV3@ResponseBody 를 사용하면 view를 사용하지 않고, HTTP 메시지 컨버터를 통해서 HTTP 메시지를 직접 입력할수 있다.responseBodyJsonV1ResponseEntity 를 반환한다. HTTP 메시지 컨버터를 통해서 JSON 형식으로 변환되어서 반환된다.responseBodyJsonV2@ResponseBody 를 사용하면 이런 것을 설정하기 까다롭다.@ResponseStatus(HttpStatus.OK) 애노테이션을 사용하면 응답 코드도 설정할 수 있다.ResponseEntity 를 사용하면 된다.@RestController 애노테이션을 사용하면, 해당 컨트롤러에 모두 @ResponseBody가 적용되는 효과가 있다Rest API(HTTP API)를 만들 때 사용하는 컨트롤러이다@ResponseBody 는 클래스 레벨에 두면 전체 메서드에 적용되는데, @RestController 에노테이션 안에 @ResponseBody 가 적용되어 있다.HTTP Message Converter을 이용한다
@ResponseBody를 사용했기 때문에 return 값에 대해 viewResolver 적 용대신에 Http Message Converter가 적용이 된다@RequestBody , HttpEntity(RequestEntity) ,@ResponseBody , HttpEntity(ResponseEntity) ,0 = ByteArrayHttpMessageConverter
1 = StringHttpMessageConverter
2 = MappingJackson2HttpMessageConverter
ByteArrayHttpMessageConverter : byte[] 데이터를 처리한다.StringHttpMessageConverter : String 문자로 데이터를 처리한다.MappingJackson2HttpMessageConverter : application/json
@RequestMapping을 처리하는 핸들러 어댑터인 RequestMappingHandlerAdapter즉 요청 매핑 헨들러 어뎁터이다
지금까지 에노티이션의 엄청난 유연성으로, 여러 파라미터를 마음대로 받아서 사용할 수 있었다
HttpServletRequest , Model 은 물론이고, @RequestParam , @ModelAttribute 같은 애노테이션 그리고 @RequestBody , HttpEntity 같은 HTTP 메시지를 처리하는 부분까지 매우 큰 유연함을 보여주었다.
이것이 바로 Argument Resolver 덕분이다
에노테이션 기반 컨트롤러를 처리하는 RequstMappingHandlerAdapter가, Argument Resolver을 호출해 컨트롤러가 원하는 파라미터를 생성하게 요청한다
Argument Resolver가 요청받은 파라미터를 생성하고, 이를 가지고 핸들러(컨트롤러)를 호출하면서 값을 넘겨준다!!!
이 Argument Reoslver 인터페이스를 구현하는 새로운 클래스를 만들어서, 확장할 수 있다
ReturnValueHandeler
우리가 반환값을 String으로 해도 되고, ModelAndView로 해도 되고... 이것 또한 에노테이션 기반의 유연성인데, Argument Resolver과 비슷한 ReturnValueHandler덕분이다
이것은 응답 값을 변환하고 처리한다

@RequestBody로 핸들러가 필요로 하는 파라미터 값을 변환할 때 사용한다Argument Reolver한테 핸들러 어뎁터가 요청을 하면, HTTP message Converter가 그 값으로 변환해서 반환해준다@ResponseBody의 경우 'ReturnValueHandler'가 HTTP message Converter를 호출해서 원하는 결과를 만들어 반환해준다RequestResponseBodyMethodProcessor()HttpEntityMethodProcessor() 를 사용한다.