Spring MVC

Spring MVC 작동 원리
스프링은 클라이언트로부터 받은 요청을 DispatcherServlet에서 먼저 받는다.
사용자의 요청에 따라 어떤 핸들러를 사용해야하는지 HandlerMapping을 이용해 찾게 된다. 찾았다면 실제 핸들러와 Dispatcher 사이의 호환을 담당하는 HandlerAdapter가 존재하는데, 여기서 사용할 어댑터를 또한 찾아야 한다.
올바른 HandlerAdapter를 찾았다면 이제 HandlerAdapter를 사용해 실제 핸들러를 실행한다. 핸들러는 실행한 결과는 ModelAndView 타입으로 반환하게 된다.
DispatcherServlet은 ModelAndView를 받아 ViewResolver를 이용해 View를 만들어낸다. 마지막으로 View를 렌더링해 response하게 된다.
@Controller
@Controller 애노테이션을 달아주게 되면, 스프링 빈으로 인식된다. 이러한 이유는 @Controller 인터페이스 안에 @Component 애노테이션이 있기 때문이다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
/**
* Alias for {@link Component#value}.
*/
@AliasFor(annotation = Component.class)
String value() default "";
}그리고 또 다른 역할이 있는데, RequestMappingHandlerMapping의 스캔 대상이 될 수 있게 등록되어 꺼내 사용할 수 있게 해준다.
@RequestMapping
RequestMappingHandlerMappingRequestMappingHandlerAdapter
위의 HandlerMapping과 HandlerAdapter를 이용해서 Controller를 사용한다.
DispatcherServlet은ModelAndView를 받아ViewResolver를 이용해View를 만들어낸다.
지금까지는 ModelAndView를 직접 생성하고 반환해야 했기 때문에 힘들었다.
// 파라미터 없이 ModelAndView를 반환하는 경우
@RequestMapping("/map1")
public ModelAndView mapping1() {
return new ModelAndView("map1-result");
}// Servlet의 request과 response를 파라미터로 받는 경우
@RequestMapping("/map2")
public ModelAndView mapping2(HttpServletRequest request, HttpServletResponse response) {
String param1 = request.getParameter("param1");
int param2 = Integer.parseInt(request.getParameter("param2"));
SomeObject someObject = new SomeObject(param1, param2);
objectRepository.save(someObject);
ModelAndView mv = new ModelAndView("save-map2");
mv.addObject("someObject", someObject);
return mv;
}그러나 좀 더 쉽고 간단하게 사용하는 방법이 있다.
// 파라미터 없이 ModelAndView를 반환하는 경우 -> String으로 반환 가능
@RequestMapping("/map1")
public String mapping1() {
return "map1-result"
}위의 코드와 같이 ModelAndView를 반환하는 것이 아니라 String으로 View 이름 그 자체를 반환할 수 있다.
/** Servlet의 request과 response를 파라미터로 받는 경우
HttpServletRequest와 HttpServletResponse를 @RequestParam으로 받을 수 있음
또한 파라미터로 Model도 받을 수 있음
*/
@RequestMapping("/map2")
public String mapping2(@RequestParam("param1") param1,
@RequestParam("param2") param2,
Model model) {
SomeObject someObject = new SomeObject(param1, param2);
objectRepository.save(someObject);
/** 직접 ModelAndView를 반환하지 않아도 됨
ModelAndView mv = new ModelAndView("save-map2");
mv.addObject("someObject", someObject);
*/
model.setAttribute("someObject", someObject);
return "save-map2";
}HttpServlet의 요청 파라미터를 request, response 대신에 @RequestParam으로 받을 수 있다.
@RequestMapping(method = “RequestMethod.XXX”)
그러나 위의 코드의 경우 GET이나 POST 등 다른 메서드로 요청이 들어올 수 있기 때문에 서비스 로직에 따라 구분할 필요가 있다.
// 파라미터 없이 ModelAndView를 반환하는 경우 -> String으로 반환 가능
//@RequestMapping("/map1")
@RequestMapping(value = "/map1", method = RequestMethod.GET)
public String mapping1() {
return "map1-result"
}@RequestMapping에 method = RequestMethod.GET를 지정하여 http GET 메서드에 대한 요청만 받아 들이게 된다.
/** Servlet의 request과 response를 파라미터로 받는 경우
HttpServletRequest와 HttpServletResponse를 @RequestParam으로 받을 수 있음
또한 파라미터로 Model도 받을 수 있음
*/
//@RequestMapping("/map2")
@RequestMapping(value = "/map2", method = RequestMethod.POST)
public String mapping2(@RequestParam("param1") param1,
@RequestParam("param2") param2,
Model model) {
SomeObject someObject = new SomeObject(param1, param2);
objectRepository.save(someObject);
/** 직접 ModelAndView를 반환하지 않아도 됨
ModelAndView mv = new ModelAndView("save-map2");
mv.addObject("someObject", someObject);
*/
model.setAttribute("someObject", someObject);
return "save-map2";
}마찬가지로 method = RequestMethod.POST처럼 메서드를 POST로 지정할 수도 있다.
위와 같은 패턴을 좀 더 간단하게 사용할 수 있는 애노테이션이 존재하는데 @GetMapping과 @PostMapping 등등이 있다.
@GetMapping
@RequestMapping(value = "/new-form", method = RequestMethod.GET)와 똑같은 기능을 수행한다.
참고로 @GetMapping 안에는 method = RequestMethod.GET가 지정되어 있다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET) /** <<<< */
public @interface GetMapping {
...
}@GetMapping 뿐만 아니라 Post, Put, Delete, Patch 등등의 다른 http 메서드도 지원한다.
@RequestParam
@RequestMapping("/test/param1")
public String reqParam(@RequestParam("param1") String param1) {
...
}사용자가 GET이나 POST로 요청을 보낼 때 파라미터를 같이 넘길 때 사용할 수 있다.
이때 http 파라미터와 변수의 이름이 같다면 @RequestParam의 name 옵션을 생략할 수 있다.
// http 요청 파라미터가 변수의 이름과 같은 경우
@RequestMapping("/test/param2")
public String reqParam(@RequestParam String param1) {
...
}@RequestMapping("/test/param3")
public String reqParam(String param1) {
...
}만약 파라미터의 타입이 String, int와 같은 단순 타입이면 @RequestParam을 작성하지 않아도 마치 작성한 것처럼 이루어진다.
옵션
@RequestParam(required = true)- 해당 파라미터가 필수로 들어와야 하는지 아니면 없어도 되는지 지정할 수 있다. 기본값으로
required = true이다.
- 해당 파라미터가 필수로 들어와야 하는지 아니면 없어도 되는지 지정할 수 있다. 기본값으로
@RequestParam(defaultValue = value)defaultValue는 넘어온 파라미터의 값이 없거나 공백일 경우value로 지정한 값으로 대체된다.
@ModelAttribute
@RequestMapping("/test/attribute")
public String reqAttribute(@ModelAttribute SomeObject somObject) {
System.out.println(somObject.getId());
}http 요청 파라미터로 넘어온 값을 객체로 생성해준다.
이때 @RequestParam과 같이 @ModelAttribute 어노테이션을 생략할 수 있다.
@RequestParamString,int과 같은 단순 타입
@ModelAttribute- 단순 타입을 제외한 개발자가 직접 만든 객체와 같은 타입
Argument resolver로 지정한 타입은 제외
Http body 요청 메세지
HttpEntity
@PostMapping("/test/http-entity")
public HttpEntity<String> reqHttpEntity(HttpEntity<String> httpEntity) {
String value = httpEntity.getBody();
return new HttpEntity<>("OK");
}HttpEntity<T>의 경우 @RequestParam이나 @ModelAttribute처럼 파라미터로 넘어온 값을 받는 것이 아니라, http body의 내용 그 자체를 받아들인다. HttpEntity는 request 뿐만 아니라 reponse에서도 사용될 수 있다.
이때 스프링은 http Body를 읽어 문자나 객체로 변환해주는데, 이것은 httpMessageConverter라는 기능을 사용하여 변환해준다.
@RequestBody
@ResponseBody
@PostMapping("/test/request-body")
public String reqRequestBody(@RequestBody String body) {
...
return "OK";
}http body를 그대로 읽는다. 만약 여기서 http 헤더 정보가 필요하다면 @RequestHeader를 사용할 수 있다.
@ResponseBody
@ResponseBody를 사용하면 반환 값을 http body에 바로 넘길 수 있다. 이때 반환 값은 View의 논리적인 이름이 아니다.
위의 예제에서 보면 return "OK";으로 끝나는 것을 볼 수 있는데, 요청을 보낸 클라이언트는 문자 그 자체인 OK를 받게 된다.
Json
@RequestHeader와 @ResponseBody를 사용하여 Json의 형식으로 파라미터를 받고 클라이언트에게 반환도 할 수 있다.
@ResponseBody
@RequestMapping("/test/json")
public String reqJson(@RequestBody String jsonBody) {
SomeObject data = objectMapper.readValue(jsonBody, SomeObject.class);
return "OK";
}위의 코드와 같이 ObjectMapper를 이용해서 Json을 파싱하여 객체에 담을 수 있다.
@ResponseBody
@RequestMapping("/test/json2")
public String reqJson(@RequestBody SomeObject data) {
...
}ObjectMapper를 쓰지 않으면 파라미터로 객체를 받을 수 있다. 이 변환 해주는 작업은 httpMessageConverter가 수행한다.