스프링 MVC 기본 기능 정리
#Abstract/SpringMVC
스프링 MVC 구조와 동작원리, 스프링 MVC가 제공하는 기능의 기본적인 사용법을 정리했습니다.
- /컨트롤러 등록 - Controller 인터페이스 구현(거의 안씀)
- /컨트롤러 등록 - HttpRequestHandler 인터페이스 구현(거의 안씀)
- /컨트롤러 등록 - @Controller, @RestController + @RequestMapping, @XXXMapping
스프링 MVC 구조
스프링 MVC의 큰 강점은 DispatcherServlet
코드의 변경 없이, 원하는 기능을 변경하거나 확장할 수 있다는 점이다. 지금까지 설명한 대부분을 확장 가능할 수 있게 인터페이스로 제공한다.
ArgumentResolver와 HTTP 메시지 컨버터
스프링 MVC는 다음의 경우에 ArgumentResolver를 적용한다.
- HTTP 요청:
HttpServletRequest
,Model
,@RequestParam
,@ModelAttribute
,@RequestBody
,HttpEntity
(RequestEntity
) - HTTP 응답:
ModelAndView
@ResponseBody
,HttpEntity
(ResponseEntity
) - 다형성으로 여러 종류가 있다.
이후 스프링 MVC는 다음의 경우에 HTTP 메시지 컨버터를 적용한다.
- HTTP 요청:
@RequestBody
,HttpEntity
(RequestEntity
) - HTTP 응답:
@ResponseBody
,HttpEntity
(ResponseEntity
) - 다형성으로 여러 종류가 있다.
ArgumentResolver
RequestMappingHandlerAdapter 동작 방식
ArgumentResolver
생각해보면, 애노테이션 기반의 컨트롤러는 매우 다양한 파라미터를 사용할 수 있었다.
HttpServletRequest
, Model
은 물론이고, @RequestParam
, @ModelAttribute
같은 애노테이션 그리고 @RequestBody
, HttpEntity
같은 HTTP 메시지를 처리하는 부분까지 매우 큰 유연함을 보여주었다.
이렇게 파라미터를 유연하게 처리할 수 있는 이유가 바로 ArgumentResolver
덕분이다.
애노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdapter
는 바로 이 ArgumentResolver
를 호출해서 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터의 값(객체)을 생성한다. 그리고 이렇게 파라미터의 값이 모두 준비되면 컨트롤러를 호출하면서 값을 넘겨준다.
정확히는 HandlerMethodArgumentResolver
인데 줄여서 ArgumentResolver
라고 부른다.
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory
binderFactory) throws Exception;
}
동작 방식ArgumentResolver
의 supportsParameter()
를 호출해서 해당 파라미터를 지원하는지 체크하고, 지원하면 resolveArgument()
를 호출해서 실제 객체를 생성한다. 그리고 이렇게 생성된 객체가 컨트롤러 호출 시에 넘어가는 것이다.
그리고 원한다면 여러분이 직접 이 인터페이스를 확장해서 원하는 ArgumentResolver
를 만들 수도 있다.
ReturnValueHandler
HandlerMethodReturnValueHandler
를 줄여서 ReturnValueHandler
라고 부른다.ArgumentResolver
와 비슷한데, 이것은 응답 값을 변환하고 처리한다.
컨트롤러에서 String
으로 뷰 이름을 반환해도, 동작하는 이유가 바로 ReturnValueHandler
덕분이다.
HTTP 메시지 컨버터
HTTP 메시지 컨버터를 사용하는 @RequestBody
도 컨트롤러가 필요로 하는 파라미터의 값에 사용된다.@ResponseBody
의 경우도 컨트롤러의 반환 값을 이용한다.
요청의 경우 @RequestBody
를 처리하는 ArgumentResolver
가 있고, HttpEntity
를 처리하는 ArgumentResolver
가 있다(다형성). 이 ArgumentResolver
들이 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성 하는 것이다. (어떤 종류가 있는지 코드로 살짝 확인해보자)
응답의 경우 @ResponseBody
와 HttpEntity
를 처리하는 ReturnValueHandler
가 있다. 그리고 여기에서 HTTP 메시지 컨버터를 호출해서 응답 결과를 만든다.
스프링 MVC는@RequestBody
@ResponseBody
가 있으면 RequestResponseBodyMethodProcessor(ArgumentResolver, ReturnValueHandler 둘다 구현)
HttpEntity
가 있으면 HttpEntityMethodProcessor(ArgumentResolver, ReturnValueHandler 둘다 구현)
를 사용한다.
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;
}
HTTP 메시지 컨버터는 HTTP 요청, HTTP 응답 둘 다 사용된다. 양방향이다.
canRead()
,canWrite()
: 메시지 컨버터가 해당 클래스, 미디어타입을 지원하는지 체크read()
,write()
: 메시지 컨버터를 통해서 메시지를 읽고 쓰는 기능
스프링 부트 기본 메시지 컨버터
(일부 생략)
0 = ByteArrayHttpMessageConverter
1 = StringHttpMessageConverter
2 = MappingJackson2HttpMessageConverter
- ByteArrayHttpMessageConverter
byte[]
데이터를 처리한다.- 클래스 타입:
byte[]
, 미디어타입:*/*
- 요청 예)
@RequestBody byte[] data
- 응답 예)
@ResponseBody return byte[]
- 쓰기 미디어타입:
application/octet-stream
- StringHttpMessageConverter
String
문자로 데이터를 처리한다.- 클래스 타입:
String
, 미디어타입:*/*
- 요청 예)
@RequestBody String data
- 응답 예)
@ResponseBody return "ok"
- 쓰기 미디어타입:
text/plain
- MappingJackson2HttpMessageConverter
application/json
- 클래스 타입: 객체 또는
HashMap
, 미디어타입application/json
관련 - 요청 예)
@RequestBody HelloData data
- 응답 예)
@ResponseBody return helloData
- 쓰기 미디어타입:
application/json
관련
? (안 되는 케이스)
content-type: text/html
@RequestMapping
void hello(@RequestBody HelloData data) {}
미디어 타입이 application/json
관련 X 라서 실패한다.
HTTP 요청 데이터 읽기
- HTTP 요청이 오고, 컨트롤러에서
@RequestBody
,HttpEntity
파라미터를 사용한다. - 메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해
canRead()
를 호출한다.- 대상 클래스 타입을 지원하는가.
- 예)
@RequestBody
의 대상 클래스 (byte[]
,String
,HelloData
)
- 예)
- HTTP 요청의
Content-Type
미디어 타입을 지원하는가.- 예)
text/plain
,application/json
,*/*
- 예)
- 대상 클래스 타입을 지원하는가.
canRead()
조건을 만족하면read()
를 호출해서 객체 생성하고, 반환한다.
HTTP 응답 데이터 생성
- 컨트롤러에서
@ResponseBody
,HttpEntity
로 값이 반환된다. - 메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해
canWrite()
를 호출한다.- 대상 클래스 타입을 지원하는가.
- 예)
return
의 대상 클래스 (byte[]
,String
,HelloData
)
- 예)
- HTTP 요청의
Accept
미디어 타입을 지원하는가. (더 정확히는@RequestMapping
의produces
)- 예)
text/plain
,application/json
,*/*
- 예)
- 대상 클래스 타입을 지원하는가.
canWrite()
조건을 만족하면write()
를 호출해서 HTTP 응답 메시지 바디에 데이터를 생성한다.
스프링 MVC 확장
스프링은 다음을 모두 인터페이스로 제공한다. 따라서 필요하면 언제든지 기능을 확장할 수 있다.
HandlerMethodArgumentResolver
HandlerMethodReturnValueHandler
HttpMessageConverter
스프링이 필요한 대부분의 기능을 제공하기 때문에 실제 기능을 확장할 일이 많지는 않다. 기능 확장은 WebMvcConfigurer
를 상속 받아서 스프링 빈으로 등록하면 된다.
컨트롤러 등록
HandlerMapping
0 = RequestMappingHandlerMapping : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = BeanNameUrlHandlerMapping : 스프링 빈의 이름으로 핸들러를 찾는다.
HandlerAdapter
0 = RequestMappingHandlerAdapter : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = HttpRequestHandlerAdapter : HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter : Controller 인터페이스(애노테이션X, 과거에 사용) 처리
ViewResolver
1 = BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환한다. (예: 엑셀 파일 생성 기능 에 사용)
2 = InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환한다.
컨트롤러 등록 - Controller 인터페이스 구현(거의 안씀)
- 스프링 빈의 이름으로 URL을 매핑(스프링 빈 이름으로 핸들러를 찾고, 어댑터가 이 핸들러를 실행해줌)
- HandlerMapping =
BeanNameUrlHandlerMapping
- HandlerAdapter =
SimpleControllerHandlerAdapter
- HandlerMapping =
- Controller 인터페이스 구현
- 로직 구현하고, ModelAndView를 반환
BeanNameViewResolver
탐색 : 스프링 빈으로 등록된 뷰가 없음-
InternalResourceViewResolver
라는 뷰 리졸버가 논리 뷰 이름을 보고 JSP를 처리할 수 있는 뷰를 반환한다. (InternalResourceView
반환, Thymeleaf 뷰 템플릿을 사용하면ThymeleafViewResolver
) view.render()
InternalResourceView
는forward()
를 사용(JSP가 아닌 경우 foward 과정 X)
- 로직 구현하고, ModelAndView를 반환
@Component("/springmvc/old-controller")
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("OldController.handleRequest");
return null;
}
}
컨트롤러 등록 - HttpRequestHandler 인터페이스 구현(거의 안씀)
- 스프링 빈의 이름으로 URL을 매핑(스프링 빈 이름으로 핸들러를 찾고, 어댑터가 이 핸들러를 실행해줌)
- HandlerMapping =
BeanNameUrlHandlerMapping
- HandlerAdapter =
HttpRequestHandlerAdapter
- HandlerMapping =
- HttpRequestHandler 인터페이스 구현
@Component("/springmvc/request-handler")
public class MyHttpRequestHandler implements HttpRequestHandler { // 서블릿과 유사
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("MyHttpRequestHandler.handleRequest");
}
}
컨트롤러 등록 - @Controller, @RestController + @RequestMapping, @XXXMapping
@RequestMapping
RequestMappingHandlerMapping
RequestMappingHandlerAdapter
@Controller
- 스프링이 자동으로 스프링 빈으로 등록한다. (내부에
@Component
애노테이션이 있어서 컴포넌트 스캔의 대상이 됨) - 스프링 MVC에서 애노테이션 기반 컨트롤러로 인식한다.
RequestMappingHandlerMapping
이 꺼내지는 기준이 된다.
@RequestMapping
- 요청 정보를 매핑한다.
- 해당 URL이 호출되면 이 메서드가 호출된다.
- 애노테이션을 기반으로 동작하기 때문에, 메서드의 이름은 임의로 지으면 된다.
RequestMappingHandlerMapping
은 스프링 빈 중에서@Controller
가 클래스 레벨에 붙어 있는 경우에 매핑 정보로 인식한다.
- 클래스 단위 + 메서드 단위로 조합하여 사용할 수 있다.
- ex)
- 클래스 레벨:
@RequestMapping("/springmvc/v2/members")
- 메서드 레벨:
@RequestMapping("/new-form")
@RequestMapping("/save")
- 클래스 레벨:
- ex)
- 대부분의 속성을 배열
[]
로 제공하므로 다중 설정이 가능하다.{"/hello-basic", "/hello-go"}
@Controller
public class SpringMemberFormControllerV1 {
@RequestMapping("/springmvc/v1/members/new-form")
public ModelAndView process() {
return new ModelAndView("new-form");
}
}
@GetMapping
, @PostMapping
@RequestMapping
은 URL만 매칭하는 것이 아니라, HTTP Method도 함께 구분할 수 있다.
예를 들어서 URL이 /new-form
이고, HTTP Method가 GET인 경우를 모두 만족하는 매핑을 하려면 다음과 같이 처리하면 된다.
@RequestMapping(value = "/new-form", method = RequestMethod.GET)
이것을 @GetMapping
, @PostMapping
으로 더 편리하게 사용할 수 있다.
참고로 @Get
, @Post
, @Put
, @Delete
, @Patch
모두 애노테이션이 준비되어 있다.
@GetMapping
public String members(Model model)
@RestController
@Controller
는 반환 값이String
이면 뷰 이름으로 인식된다. 그래서 뷰를 찾고 뷰가 랜더링 된다.@RestController
는 반환 값으로 뷰를 찾는 것이 아니라, HTTP 메시지 바디에 바로 입력한다.
HTTP 조회 방식
HTTP 요청 파라미터 조회
두 가지 방식을 HTTP 요청 파라미터(request parameter) 조회라고 한다.
- GET - 쿼리 파라미터
/url?username=hello&age=20
- 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달
- 예) 검색, 필터, 페이징 등에서 많이 사용하는 방식
- POST - HTML Form
Content-Type: application/x-www-form-urlencoded
- 메시지 바디에 쿼리 파라미터 형식으로 전달
username=hello&age=20
- 예) 회원 가입, 상품 주문, HTML Form 사용
GET 쿼리 파리미터 전송 방식이든, POST HTML Form 전송 방식이든 둘 다 형식이 같으므로 구분 없이 조회할 수 있다.
HTTP 요청 파라미터 조회 - 스프링 MVC
GET: http://localhost:8080/request-param-v1?username=hello&age=20
또는
POST: Form
1. HttpServletRequest에서 request.getParameter()
로 꺼내서 조회
@RequestMapping("/request-param-v1")
public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
String username = request.getParameter("username");
// ...
2. @RequestParam
스프링은 HTTP 요청 파라미터를 @RequestParam
으로 받을 수 있다.
public String requestParamV2(
@RequestParam("username") String memberName,
@RequestParam("age") int memberAge
@RequestParam
: 파라미터 이름으로 바인딩@ResponseBody
: View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력- HTTP 파라미터 이름이 변수 이름과 같으면
@RequestParam(name="xx")
생략 가능public String requestParamV3(@RequestParam String username, @RequestParam int age)
String
,int
등의 단순 타입이면@RequestParam
도 생략 가능- 주의!
@RequestParam
애노테이션을 생략하면 스프링 MVC는 내부에서required=false
를 적용한다. - 개인적으론
@RequestParam
은 생략 안하는게 코드를 이해하는데 더 나은듯.
- 주의!
- 파라미터 필수 여부를 지정할 수 있다.
@RequestParam.required
- 기본값이 파라미터 필수 (
true
)이다. public String requestParamRequired(@RequestParam(required = true) String username)
- 주의! - 파라미터 이름만 사용
/request-param-required?username=
- 파라미터 이름만 있고 값이 없는 경우 빈문자로 통과. null이 아니다.
- 주의! - 기본형(primitive)에 null 입력
/request-param-required
@RequestParam(required = false) int age
- null을
int
에 입력하는 것은 불가능(500 예외 발생)
- null을
- 기본값을 지정할 수 있다.
- 파라미터에 값이 없는 경우
defaultValue
를 사용하면 기본 값을 적용할 수 있다. - 이미 기본 값이 있기 때문에
required
는 의미가 없다. defaultValue
는 빈 문자의 경우에도 설정한 기본 값이 적용된다./request-param-default?username=
- 파라미터에 값이 없는 경우
public String requestParamDefault(
@RequestParam(required = true, defaultValue = "guest") String username,
@RequestParam(required = false, defaultValue = "-1") int age
3. Map으로 조회
파라미터를 Map
, MultiValueMap
으로 조회할 수 있다.
@RequestParam Map
Map(key=value)
@RequestParam MultiValueMap
MultiValueMap(key=[value1, value2, ...])
- 예)
key=userIds, value=[id1, id2]
- 파라미터의 값이 1개가 확실하다면
Map
을 사용해도 되지만, 그렇지 않다면MultiValueMap
을 사용하자.
@RequestMapping("/request-param-map")
public String requestParamMap(@RequestParam Map<String, Object> paramMap)
4. @ModelAttribute
요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어주는 작업을 @ModelAttribute
를 통해 자동화한다.
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData)
- 스프링 MVC는
@ModelAttribute
가 있으면 다음을 실행한다.HelloData
객체를 생성한다.- 요청 파라미터의 이름으로
HelloData
객체의 프로퍼티를 찾는다. 그리고 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩)한다.- 예) 파라미터 이름이
username
이면setUsername()
메서드를 찾아서 호출하면서 값을 입력한다. - 프로퍼티
- getXxx xxx
- setXxx xxx
- 예) 파라미터 이름이
age=abc
처럼 숫자가 들어가야 할 곳에 문자를 넣으면BindException
이 발생한다.@ModelAttribute
는 생략할 수 있다.- 그런데
@RequestParam
도 생략할 수 있으니 혼란이 발생할 수 있다. 개인적으론 생략 안하는게 좋은듯
- 그런데
- 스프링은 해당 생략 시 다음과 같은 규칙을 적용한다.
- String, int, Integer 같은 단순 타입 =
@RequestParam
- 나머지 =
@ModelAttribute
(argument resolver로 지정해둔 타입은 ModelAttribute가 적용되지 않는다. e.g. HttpServletRequest. 추가로 지정할 수 있음)
- String, int, Integer 같은 단순 타입 =
Model에 자동으로 추가되는 기능
model.addAttribute(helloData)
코드도 함께 자동 적용된다.- ModelAttribute로 받은 객체는 view에서 다시 사용할 확률이 높기 때문에 자동으로 model에 넣어주는 것이다.
- Model에는 key-value로 저장하는데, key로는 @ModelAttribute(“item”)에서 지정한 “item”이, 그리고 value 객체로는 ModelAttribute의 대상이 되는 item 객체가 들어간다. 만약 @ModelAttribute(“item2”)라면 item 객체의 key는 “item2”가 된다.
- 모델에 데이터를 담을 때는 이름이 필요하다. 이름은
@ModelAttribute
에 지정한name(value)
속성을 사용한다. 만약 다음과 같이@ModelAttribute
의 이름을 다르게 지정하면 다른 이름으로 모델에 포함된다. @ModelAttribute("hello") Item item
- 이름을
hello
로 지정 model.addAttribute("hello", item);
- 모델에
hello
이름으로저장
- 모델에
- 이름을
@ModelAttribute
의 이름을 생략할 수 있다. 이 때 item 객체의 key로는 어떤 규칙에 따라 사용될까?- 클래스 이름의 첫 글자만 소문자로 바꿔서 key로 사용된다. 객체의 이름으로 key가 사용될 것이라고 헷갈리지 말자.
Item
item
HelloWorld
helloWorld
- 클래스 이름의 첫 글자만 소문자로 바꿔서 key로 사용된다. 객체의 이름으로 key가 사용될 것이라고 헷갈리지 말자.
@ModelAttribute
자체도 생략 가능하다. 대상 객체는 모델에 자동 등록된다. 나머지 사항은 기존과 동일하다.
HTTP 메시지 바디 조회
HTTP message body에 데이터를 직접 담아서 요청하는 경우
- HTTP API에서 주로 사용, JSON, XML, TEXT
- 데이터 형식은 주로 JSON 사용
POST
,PUT
,PATCH
보통 메시지 Body에 데이터를 담아서 요청하는 경우 응답도 Body에 담아서 보내주는 경우가 많다.
HTTP 메시지 바디 텍스트 조회 - 스프링 MVC
요청 파라미터와 다르게, HTTP 메시지 바디를 통해 데이터가 직접 넘어오는 경우는 @RequestParam
, @ModelAttribute
를 사용할 수 없다. (물론 HTML Form 형식으로 전달되는 경우는 요청 파라미터로 인정된다.)
1. HTTP 메시지 바디의 데이터를 HttpServletRequest InputStream
꺼내고, 사용해서 직접 읽기
@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);
// ...
}
2. Input, Output 스트림 파라미터 바로 사용
public void requestBodyStringV2(InputStream inputStream, Writer responseWriter)
스프링 MVC는 다음 파라미터를 지원한다.
InputStream
(Reader
): HTTP 요청 메시지 바디의 내용을 직접 조회OutputStream
(Writer
): HTTP 응답 메시지의 바디에 직접 결과 출력
3. HttpEntity 파라미터 사용
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {
String messageBody = httpEntity.getBody();
스프링 MVC는 다음 파라미터를 지원한다.
HttpEntity
: HTTP header, body 정보를 편리하게 조회- 스프링이 HttpBody가 바디가 문자임을 확인 HttpMessageConverter가 동작해서 HttpBody에 있는 걸 문자로 바꿔서 HttpEntity에다가 넣어준다.
- InputStream을 String으로 변환하는 과정을 스프링이 해준다.
- HttpEntity는 Http 스펙을 객체화 해둔거라 보면 된다.
- 메시지 바디 정보를 직접 조회
- 요청 파라미터를 조회하는 기능과 관계 없음
@RequestParam
X,@ModelAttribute
X- Get에 쿼리 스트링, Post Form 데이터 전송만 위 두 개를 사용한다.
- 스프링이 HttpBody가 바디가 문자임을 확인 HttpMessageConverter가 동작해서 HttpBody에 있는 걸 문자로 바꿔서 HttpEntity에다가 넣어준다.
HttpEntity
는 응답에도 사용 가능- 메시지 바디 정보 직접 반환
- 헤더 정보 포함 가능
- view 조회X
4. HttpEntity 상속받은 객체 사용
@PostMapping("/request-body-string-v3")
public ResponseEntity<String> requestBodyStringV3(RequestEntity<String> httpEntity) throws IOException {
String messageBody = httpEntity.getBody();
return new ResponseEntity<>(HttpStatus.CREATED);
HttpEntity
를 상속받은 다음 객체들도 같은 기능을 제공한다.
RequestEntity
HttpMethod
,url
정보가 추가, 요청에서 사용
ResponseEntity
- HTTP 상태 코드 설정 가능, 응답에서 사용
return new ResponseEntity<String>("Hello World", responseHeaders,HttpStatus.CREATED)
5. @RequestBody 사용
@RequestBody
를 사용하면 HTTP 메시지 바디 정보를 편리하게 조회할 수 있다. 참고로 헤더 정보가 필요하다면 HttpEntity
를 사용하거나 @RequestHeader
를 사용하면 된다.
이렇게 메시지 바디를 직접 조회하는 기능은 요청 파라미터를 조회하는 @RequestParam
, @ModelAttribute
와는 전혀 관계가 없다.
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
@ResponseBody@ResponseBody
를 사용하면 응답 결과를 HTTP 메시지 바디에 직접 담아서 전달할 수 있다. 물론 이 경우에도 view를 사용하지 않는다.
HTTP 메시지 바디 JSON 조회 - 스프링 MVC
HTTP API에서 주로 사용하는 JSON 데이터 형식을 사용한다.
1. 문자열 꺼낸 뒤 ObjectMapper 사용
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);
HelloData data = objectMapper.readValue(messageBody, HelloData.class);
HttpServletRequest
를 사용해서 직접 HTTP 메시지 바디에서 데이터를 읽어와서, 문자로 변환한다.- 문자로 된 JSON 데이터를 Jackson 라이브러리인
objectMapper
를 사용해서 자바 객체로 변환한다.
문자열 꺼내는 과정을
- 직접 파라미터에
InputStream
사용해도 된다. HttpEntity.getBody()
사용HttpEntity
상속받은RequestEntity
사용@RequestBody
문자변환
으로 간소화할 수 있다.
그리고 @RequestBody
, @HttpEntity
는 문자 변환 말고도 객체변환을 지원한다.
2. 객체 변환(@RequestBody, HttpEntity)
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData data) {
@RequestBody 객체 파라미터
@RequestBody
에 직접 만든 객체를 지정할 수 있다.
@RequestBody
생략 불가능- 생략하면
@ModelAttribute
가 적용되어 버림 - HTTP 메시지 바디가 아니라 요청 파라미터를 처리하게 된다.
- 생략하면
HttpMessageConverter
사용MappingJackson2HttpMessageConverter (content-type: application/json)
HttpEntity
,@RequestBody
를 사용하면 HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 우리가 원하는 문자나 객체 등으로 변환해준다.- HTTP 메시지 컨버터는 문자 뿐만 아니라 JSON도 객체로 변환해주는데(MappingJackson2HttpMessageConverter), 우리가 방금 V2에서 했던 작업을 대신 처리해준다.
- HTTP 요청 시에
content-type
이application/json
인지 꼭! 확인해야 한다. 그래야 JSON을 처리할 수 있는 HTTP 메시지 컨버터가 실행된다.
HttpEntity 객체 파라미터
@ResponseBody
@PostMapping("/request-body-json-v4")
public HelloData requestBodyJsonV4(HttpEntity<HelloData> httpEntity) {
HelloData data = httpEntity.getBody();
return data;
}
@ResponseBody
응답의 경우에도 @ResponseBody
를 사용하면 해당 객체를 HTTP 메시지 바디에 직접 넣어줄 수 있다. 물론 이 경우에도 HttpEntity
를 사용해도 된다.
@RequestBody 요청
- JSON 요청 HTTP 메시지 컨버터 객체
@ResponseBody 응답
- 객체 HTTP 메시지 컨버터 JSON 응답
경로변수: @PathVariable
@RequestMapping
은 URL 경로를 템플릿화 할 수 있는데,@PathVariable
을 사용하면 매칭 되는 부분을 편리하게 조회할 수 있다.
- 파라미터 이름이 경로 변수 이름(@PathVariable의 이름)과 같으면 생략 가능
@PathVariable("userId") String userId
@PathVariable String userId
- 주의!
@PathVariable
자체를 생략할 수는 없다.
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable String userId) {
- PathVariable을 다중으로 사용할 수도 있다.
@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long orderId) {
특정 조건 매핑(잘 안씀)
특정 파라미터 정보가 있어야 호출 되도록 한다.
파라미터로 추가 매핑
- 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")
미디어 타입 조건 매핑 - HTTP 요청 Content-Type, consume
클라이언트한테 받아서 서버에서 사용 consumes
- consumes="application/json"
- consumes="!application/json"
- consumes="application/*"
- consumes="/"
- consumes = MediaType.APPLICATION_JSON_VALUE
- consumes = "text/plain"
- consumes = {"text/plain", "application/*"}
- consumes = MediaType.TEXT_PLAIN_VALUE
@PostMapping(value = "/mapping-consume", consumes = "application/json")
미디어 타입 조건 매핑 - HTTP 요청 Accept, produce
서버가 만들어서 반환해주는 것 : produces
- HTTP 요청의
Accept
헤더를 기반으로 미디어 타입으로 매핑한다. - 만약 맞지 않으면 HTTP 406 상태코드(Not Acceptable)을 반환한다.
Accept
헤더는 보통 */*인데, 클라이언트가 헤더에 application/json을 넣으면 나는 응답으로 application/json 밖에 못 받는다는 말이다. 그래서 text/html을 반환해주는 컨트롤러에서 406 에러를 반환한다.
- produces = "text/html"
- produces = "!text/html"
- produces = "text/*"
- produces = "/"
- produces = "text/plain"
- produces = {"text/plain", "application/*"}
- produces = MediaType.TEXT_PLAIN_VALUE
- produces = "text/plain;charset=UTF-8"
@PostMapping(value = "/mapping-produce", produces = "text/html")
HTTP 헤더 정보 파라미터
- HttpServletRequest
- HttpServletResponse
- HttpMethod: HTTP 메서드를 조회한다.
org.springframework.http.HttpMethod
- Locale: Locale 정보를 조회한다.
- @RequestHeader MultiValueMap<String, String> headerMap
- 모든 HTTP 헤더를
MultiValueMap
형식으로 조회한다.
- 모든 HTTP 헤더를
- @RequestHeader("host") String host
- 특정 HTTP 헤더를 조회한다.
- 속성
- 필수 값 여부:
required
- 기본 값 속성:
defaultValue
- 필수 값 여부:
- @CookieValue(value = "myCookie", required = false) String cookie
- 특정 쿠키를 조회한다.
- 속성
- 필수 값 여부:
required
- 기본 값:
defaultValue
- 필수 값 여부:
@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
)
HTTP 응답 방식
HTTP 응답 - 정적 리소스, 뷰 템플릿
- 정적 리소스
- 정적 리소스 경로:
src/main/resources/static
- 리소스를 보관하는 곳이고, 또 클래스패스의 시작 경로이다. 따라서 위 디렉토리에 리소스를 넣어두면 스프링 부트가 정적 리소스로 서비스를 제공한다.
- 스프링 부트는 클래스패스의 다음 디렉토리에 있는 정적 리소스를 제공한다.
/static
,/public
,/resources
,/META-INF/resources
- 예) 웹 브라우저에 정적인 HTML, CSS, JS를 제공할 때는, 정적 리소스를 사용한다.
- 정적 리소스 경로:
- 뷰 템플릿 사용
- 뷰 템플릿 경로:
src/main/resources/templates
- 뷰 템플릿을 거쳐서 HTML이 생성되고, 뷰가 응답을 만들어서 전달한다.
- 일반적으로 HTML을 동적으로 생성하는 용도로 사용하지만, 다른 것들도 가능하다.
- 예) 웹 브라우저에 동적인 HTML을 제공할 때는 뷰 템플릿을 사용한다.
- 뷰 템플릿 경로:
HTTP 응답 - 정적 리소스, 뷰 템플릿: 스프링 MVC 뷰 템플릿 호출
1. ModelAndView 반환
@RequestMapping("/response-view-v1")
public ModelAndView responseViewV1() {
ModelAndView mav = new ModelAndView("response/hello")
.addObject("data", "hello!");
return mav;
}
스프링이 제공하는 ModelAndView
를 통해 Model 데이터를 추가할 때는 addObject()
를 사용하면 된다. 이 데이터는 이후 뷰를 렌더링 할 때 사용된다.
- ex)
mv.addObject("member", member);
mv.addObject("members", members);
2. 논리 이름 반환
@RequestMapping("/response-view-v2")
public String responseViewV2(Model model) {
model.addAttribute("data", "hello!!");
return "response/hello";
}
Model
파라미터에 모델 넣으면 됨.
@GetMapping
public String members(Model model) {
ViewName 직접 반환
뷰의 논리 이름을 반환
@GetMapping
public String members(Model model) {
// ...
return "members";
}
String을 반환하는 경우 - View or HTTP 메시지@ResponseBody
가 없으면 response/hello
로 뷰 리졸버가 실행되어서 뷰를 찾고, 렌더링 한다.@ResponseBody
가 있으면 뷰 리졸버를 실행하지 않고, HTTP 메시지 바디에 직접 response/hello
라는 문자가 입력된다.
3. void 반환
@Controller
를 사용하고, HttpServletResponse
, OutputStream
(Writer
) 같은 HTTP 메시지 바디를 처리하는 파라미터가 없으면 요청 URL을 참고해서 논리 뷰 이름으로 사용
- 요청 URL:
/response/hello
- 실행:
templates/response/hello.html
- 참고로 이 방식은 명시성이 너무 떨어지고 이렇게 딱 맞는 경우도 많이 없어서, 권장하지 않는다.
HTTP 응답 - HTTP 메시지 바디(HTTP API)
HTTP 메시지 사용
- HTTP API를 제공하는 경우에는 HTML이 아니라 데이터를 전달해야 하므로, HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 실어 보낸다.
HTTP 응답 - HTTP 메시지 문자열: Spring MVC
1. HttpServletResponse의 writer에 문자열 전달
@GetMapping("/response-body-string-v1")
public void responseBodyV1(HttpServletResponse response) throws IOException {
response.getWriter().write("ok");
}
2. HttpEntity, ResponseEntity(Http Status 추가)
ResponseEntity
엔티티는 HttpEntity
를 상속받았는데, HttpEntity
는 HTTP 메시지의 헤더, 바디 정보를 가지고 있다. ResponseEntity
는 여기에 더해서 HTTP 응답 코드를 설정할 수 있다.
HttpStatus.CREATED
로 변경하면 201 응답이 나가는 것을 확인할 수 있다.
@GetMapping("/response-body-string-v2")
public ResponseEntity<String> responseBodyV2() {
return new ResponseEntity<>("ok", HttpStatus.OK);
}
return new ResponseEntity<String>("Hello World", responseHeaders,
HttpStatus.CREATED)
3. @ResponseBody 문자열 반환
@ResponseBody
@GetMapping("/response-body-string-v3")
public String responseBodyV3() {
return "ok";
}
@ResponseBody
를 사용하면 view를 사용하지 않고, HTTP 메시지 컨버터를 통해서 HTTP 메시지를 직접 입력할 수 있다. ResponseEntity
도 동일한 방식으로 동작한다.
HTTP 응답 - HTTP 메시지 JSON: Spring MVC
1. ResponseEntity 객체 JSON으로 바로 변환
@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);
}
ResponseEntity
를 반환한다. HTTP 메시지 컨버터를 통해서 JSON 형식으로 변환되어서 반환된다. 동적으로 응답 코드를 바꿀 수 있다.
2. @RequestBody: 객체 JSON으로 바로 변환
@ResponseStatus(HttpStatus.OK) // 단순히 객체를 반환하면 상태 코드 지정이 안 되니, 애노테이션을 사용한다
// 애노테이션 방식이므로 동적으로 응답 상태 코드를 바꿀 수 없다.
@ResponseBody
@GetMapping("/response-body-json-v2")
public HelloData responseBodyJsonV2() {
HelloData helloData = new HelloData();
helloData.setUsername("userA");
helloData.setAge(20);
return helloData;
}
(클래스 레벨에 @RestController가 아니라 @Controller가 붙어있으면 @ResponseBody를 붙여줘야 객체가 바로 변환된다)
ResponseEntity
는 HTTP 응답 코드를 설정할 수 있는데, @ResponseBody
를 사용하면 이런 것을 설정하기는 어렵다.@ResponseStatus(HttpStatus.OK)
애노테이션을 사용하면 응답 코드도 설정할 수 있다.
물론 애노테이션이기 때문에 응답 코드를 동적으로 변경할 수는 없다. 프로그램 조건에 따라서 동적으로 변경하려면 ResponseEntity
를 사용하면 된다.
@RestController@Controller
대신에 @RestController
애노테이션을 사용하면, 해당 컨트롤러에 모두 @ResponseBody
가 적용되는 효과가 있다. 따라서 뷰 템플릿을 사용하는 것이 아니라, HTTP 메시지 바디에 직접 데이터를 입력한다. 이름 그대로 REST API(HTTP API)를 만들 때 사용하는 컨트롤러이다.
참고로 @ResponseBody
는 클래스 레벨에 두면 전체 메서드에 적용되는데, @RestController
애노테이션 안에 @ResponseBody
가 적용되어 있다.
'Spring > MVC' 카테고리의 다른 글
[Spring MVC] 스프링 MVC Redirect 처리 방법 (0) | 2025.03.09 |
---|---|
[Spring MVC] 타임리프 문법 정리 (0) | 2025.02.13 |
[Spring/MVC] 12. Bean Validation (0) | 2025.01.19 |
[Spring/MVC] 11. Validation (0) | 2025.01.19 |
[Spring/MVC] 10. 메시지 국제화 (0) | 2025.01.19 |