스프링의 대표적인 컨트롤러는 @RequestMapping 컨트롤러이다.
앞서 Spring MVC 구조에서 살펴본 핸들러 매핑과 핸들러 어댑터에서 우선순위가 제일 높았던 것도 @RequestMapping과 관련된 것이었다.
그리고 @Controller도 @RequestMapping처럼 어노테이션 기반 컨트롤러이다.
@Controller안에 @Component가 있어 컴포넌트 스캔을 통해서 스프링 빈으로 등록된다.
RequestMappingHanderMapping은 스프링 빈 중에서 @RequestMapping뿐만 아니라 @Controller가 클래스단에 붙어 있는 경우 매핑 정보로 인식한다.
그래서 다음과 같이 작성하면 RequestMappingHanderMapping이 인식할 수 있다.
@Controller
pulibc class SpringController {
...
}
@Component
@RequestMapping
pulibc class SpringController {
...
}
그리고 메서드단에 @RequestMapping을 붙여주면 HTTP 메서드가 Get, Post, Put모두 허용해서 만약 메서드마다 다르게 실행하고싶으면 @GetMapping, @PostMapping 등으로 제약을 걸어줄 수 있다.
제약을 거는 것을 추천한다.
요청이 왔을때 어떤 컨트롤러가 호출되는지 매핑하는 방법에는 여러가지가 있다.
대표적으로는 URL로 정할 수 있고, HTTP 메서드에 따라 정할 수 있고, @PathVariable을 사용할 수 있는 등 매우 다양한 방법이 있다.
@RequestMapping("/spring/hello")
public String springHello() {
...
}
/spring/hello이 경로로 들어오면 springHello가 실행된다.
@GetMapping("/spring/hello")
public String springHello() {
...
}
/spring/hello경로로 들어오지면 HTTP 메서드가 Get일 떄 실행된다.
@GetMapping("/spring/hello/{id}")
public String springHello(@PathVariable int age) {
...
}
/spring/hello/{id}은 /spring/hello/1, /spring/hello/2이런식으로 요청이 오는 것이다.
이 뿐만 아니라 더 다양한 방식이 있지만 우선 이정도로만 정리하겠다.
클라이언트에서 서버로 데이터를 보낼 때 크게 3가지 방법이 있다.
@RequestParam("값") 타입 변수명 -> 값과 변수명이 같으면 ("값") 생략가능하다.
@ResponseBody
@RequestMapping("/request-param")
public String requestParam( @RequestParam String username, @RequestParam int age) {
log.info("username={}, age={}", username, age);
return "ok";
}
HTML Form도 requestParam으로 데이터 값을 가져올 수 있다.
회원가입을 예시로 requestParam으로 가져오고 회원 객체랑 매핑하는 것과
@ModelAttribute를 사용해서 자동화된 코드를 함께 보여주겠다
@Data
public class Member {
private String username;
private int age;
}
@ResponseBody
@RequestMapping("/model-attribute")
public String modelAttribute(@RequestParam String username, @RequestParam int age){
Member member = new Member();
member.setUsername(username);
member.setAge(age);
return "ok";
}
@ModelAttribute를 사용하면 자동화가 가능하다.
@ResponseBody
@RequestMapping("/model-attribute")
public String modelAttribute(@ModelAttribute member member){
log.info("username={}, age={}", member.getUsername(), member.getAge());
return "ok";
}
여기서는 @RequestParam, @ModelAttribute를 사용할 수 없다.
inputStream, HttpEntity, @RequestBody 등을 사용할 수 있는데 여기서는 HttpEntity, @RequestBody에 대한 예시만 보여주겠다.
@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");
}
@ResponseBody
@PostMapping("/request-body-string")
public String requestBodyString(@RequestBody String messageBody) throws IOException {
log.info("messageBody={}", messageBody);
return "ok";
@ResponseBody
@RequestMapping("/request-body-json")
public String requestBodyJson(HttpEntity<member> messageBody){
Member member = member.getBody();
log.info("messageBody = {} ", messageBody);
log.info("username={}, age={}", member.getUsername(), member.getAge());
return "ok";
}
@ResponseBody
@RequestMapping("/request-body-json")
public HelloData requestBodyJson(@RequestBody Member member){
log.info("messageBody = {} ", member);
log.info("username={}, age={}", member.getUsername(), member.getAge());
return member;
}
서버에서 클라이언트로 응답을 보낼 때 크게 3가지 방법이 있다.
@GetMapping("/response-body-string")
public ResponseEntity<String> responseBody() {
return new ResponseEntity<>("ok", HttpStatus.CREATED);
}
@ResposeBody를 사용하면 다음과 같이 간단하게 표현 가능
@ResponseBody
@GetMapping("/response-body-string")
public String responseBody() {
return "ok";
}
@ResponseBody
@GetMapping("/response-body-json")
public ResponseEntity<Member> responseBodyJson() {
Member member = new Member();
member.setUsername("userA");
member.setAge(20);
return new ResponseEntity<>(member, HttpStatus.OK);
}
@ResponseBody를 사용하면 다음과 같이 작성할 수 있다.
@ResponseStatus를 사용해서 상태코드 변경도 가능하다.
@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/response-body-json")
public Member responseBodyJson() {
Member member = new Member();
member.setUsername("userA");
member.setAge(20);
return helloData;
}
만약 @ResponseBody를 붙이기 귀찮으면 @RestController를 사용하면된다.
@RestController는 @ResponseBody + @Controller라고 생각하면 된다.
주로 @RestController는 Rest API를 설계할 때 사용하는 어노테이션이다.
HTTP 메시지 컨버터는 감을 잡았을 수도 있지만 요청과 응답의 HTTP Message Body의 형식을 변환해주는 역할을 한다고 생각하면 된다.
그래서 다양한 형식의 데이터를 주고받을 수 있는 것이다.
참고로 스프링 부트는 다양한 메시지 컨버터를 제공해준다.
그러면 Spring MVC에서 HTTP 메시지 컨버터는 언제 사용이 될까?
핸들러 어댑터를 통해서 실제 컨트롤러가 실행이 된다고 Spring MVC 구조 포스팅에서 정리를 했다.
그래서 메시지 컨버터는 핸들러 어댑터와 관련이 있는데 자세히 알아보자.
어노테이션 기반의 컨트롤러인 @RequestMapping을 처리하는 어댑터 RequestMappingHanderAdapter에 대해 알아보겠다.
디스패처 서블릿에서 이러쿵 저러쿵 해서 RequestMappingHanderAdapter를 가져왔다고 치자.
그러면 어댑터가 컨트롤러를 호출할텐데, 우리가 여태 작성했던 코드들을 생각해보면 컨트롤러를 실행할 때 매우 다양한 파라미터를 사용할 수 있다.
A컨트롤러는 InputStream, B컨트롤러는 @RequestParam, C컨트롤러는 @ModelAttribute, @RequestBody 등을 파라미터로 받았다.
그렇다면 무언가가 파라미터의 타입대로 파라미터를 생성 후 던져줘야하는데 그게 바로 Argument Resolver이다.
Argument Resolver덕분에 매우 다양한 파라미터를 사용할 수 있다. 즉 파라미터를 유연하게 처리할 수 있는 것이다.
RequestMappingHanderAdapter는 Argument Resolver호출해서 파라미터를 모두 생성하고 컨트롤러를 호출하면서 값을 넘겨주는 것이다.
스프링은 다양한 Argument Resolver를 제공해준다.
요청 파라미터는 Argument Resolver로 해결하고, 응답 데이터의 타입은 ReturnValueHandler가 해결을 해주는 것이다.
그럼 도대체 HTTP 메시지 컨버터는 어디있는가?
Argument Resolver가 사용하는게 바로 HTTP 메시지 컨버터이다.
ReturnValueHandler역시 HTTP 메시지 컨버터를 사용한다.
예시와 함께 얘기하면,
@RequestBody타입의 파라미터 컨트롤러가 호출되기전에 @RequestBody의 argument를 처리하는 Argument Resolver가 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성하고 생성한 파라미터를 컨트롤러로 넘겨주는 것이다.
혹시 잘못알고있는 부분이 있다면 알려주시면 감사하겠습니다.
해당 포스팅은 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술의 내용을 공부하고 정리했습니다.