MVC 프레임워크
Spring MVC 구조
Dispatcher Servlet은 Front Controller와 비슷한 역할을 한다. Dispatcher Servlet은 우선적으로 클라이언트로부터 모든 요청을 받게 되며, 각각의 요청에 따른 처리는 개별 컨트롤러 클래스로 위임 한다. 이러한 개별 컨트롤러 클래스를 핸들러(Handler)라고도 한다. 쉽게 말해서 디스패처 서블릿은 Spring의 가장 앞단에서 요청을 처리할 컨트롤러를 찾아서 위임하고, 그 결과를 받아오는 역할을 한다.
동작 과정
Dispatcher Servlet이 실제 요청 처리를 핸들러에게 위임하기 위해서는 해당 요청을 어떤 컨트롤러에 보내야할 지 판단해야 한다. 이를 판단하기 위해, 다음과 같은 과정을 거친다.
1. 핸들러 조회 Dispatcher Servlet은 핸들러 매핑을 통해 요청된 URL에 매핑된 핸들러를 조회한다. 여기서 핸들러 매핑이란, 스프링 빈의 이름으로 핸들러를 찾을 수 있는 핸들러를 매핑해주는 것을 말한다. 스프링은 핸들러를 조회할 때 우선순위를 두어 조회를 하는데, 가장 우선순위가 높은 핸들러 매핑은 RequestMappingHandlerMapping이다. @RequestMapping 의 앞글자를 따서 만든 이름인데, 최근에는 해당 애노테이션을 기반으로 핸들러를 주로 구성하기 때문에 우선순위에 대해서는 크게 신경쓰지 않아도 될 듯 하다.
2. 핸들러 어댑터 조회 및 핸들러 실행 Dispatcher Servlet은 핸들러를 실행시키기 위해 핸들러 어댑터를 조회하는 과정을 거친다. 즉, 핸들러를 처리할 수 있는 어댑터를 찾는 과정이다. 해당 핸들러 어댑터에 의해 핸들러가 실행된다.
요청 맵핑
@PathVariable
@RequestMapping은 애노테이션 기반의 컨트롤러를 지원하는 핸들러 매핑과 어댑터이다. @RequestMapping은 URL 경로를 템플릿화 할 수 있는데, @PathVariable을 사용하면 매칭되는 부분을 편리하게 조회할 수 있다. 사용법은 아래와 같다.
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {
log.info("mappingPath userId={}", data);
return "ok";
}
이때, @PathVariable의 이름과 파라미터 이름이 같다면 뒤의 변수명 지정을 생략할 수 있다. 즉, 아래와 같이 작성할 수 있다는 뜻이다. 하지만, 이름을 생략하지 않고 항상 적어주는 것을 권장한다.
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable String userId) {
log.info("mappingPath userId={}", data);
return "ok";
}
요청 파라미터
스프링이 제공하는 @RequestParam을 사용하면 요청 파라미터를 매우 편리하게 사용할 수 있다. @RequestParam의 name(value) 속성이 파라미터 이름으로 사용되며 이를 기준으로 바인딩된다.
@RequestParam("username") String memberName
// HTTP 파라미터 이름과 변수 이름이 같다면 변수명 지정 생략 가능
@RequestParam String memberName
이때, @PathVariable 와 마찬가지로, HTTP 파라미터 이름이 변수 이름과 같다면, 변수명 지정을 생략할 수 있다. 하지만, 이름을 생략하지 않고 항상 적어주는 것을 권장한다.
@RequestMapping("/request-param-default")
public String requestParamDefault(@RequestParam(defaultValue = "guest") String username,
@RequestParam(required = false, defaultValue = "-1") int age) {
log.info("username={}, age={}", username, age);
return "ok";
}
@RequestParam 은 기본값이 파라미터 필수이기 때문에, 선택적으로 입력받고 싶다면, 즉 null 값을 허용하려면 원시 타입 대신 래퍼 타입을 사용하거나, 해당 애노테이션의 속성을 required = false로 명시해주어야 한다. 또는 defaultValue 를 통해 기본값을 지정할 수 있다. 이는, 파라미터에 값이 없는 경우 기본값을 설정해준다는 뜻이다. @RequestParam 는 생략할 수 있으나, 생략하지 않는 것을 권장한다.
@ModelAttribute
@Data
public class HelloData {
private String username;
private int age;
}
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
return "ok";
}
스프링이 제공하는 @ModelAttribute 애노테이션은 자동으로 요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어준다. 동작 과정은 다음과 같다.
- 객체를 생성함 → 2. 요청 파라미터의 이름으로 객체의 프로퍼티를 찾는다. → 3. 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 바인딩한다.
@ModelAttribute는 생략할 수 있다. 하지만 방금 전, @RequestParam 도 생략할 수 있다고 했다. 그렇다면 스프링은 이를 어떻게 구분할 수 있을까? 스프링은 해당 애노테이션들이 생략시에 아래와 같은 규칙을 적용하여 처리한다. 먼저, String, int, Integer와 같이 단순 타입일 경우, @RequestParam 으로 처리하고 Argument Resolver로 지정해둔 타입 외의 나머지는 @ModelAttribute 로 처리한다.
메세지 바디
요청 파라미터와 다르게 HTTP 메세지 바디를 통해 데이터가 직접 넘어오는 경우에는 @RequestParam, @ModelAttribute를 사용할 수 없다. 이때는, @RequestBody를 사용하면 HTTP 메세지 바디 정보를 편리하게 조회할 수 있다.
스프링 MVC 내부에서는 HTTP 메세지 바디를 읽어서 문자나 객체로 변환하여 전달해주는데, 이때 **HTTP 메세지 컨버터(HttpMessageConverter)**라는 기능을 사용한다. ****JSON 데이터 형식의 데이터가 HTTP 메세지 바디를 통해 넘어오면, HTTP 메세지 컨버터는 해당 데이터를 Jackson 라이브러리인 objectMapper를 사용해서 자바 객체로 변환한다. 그리고 @RequestBody 를 사용하면, 이 HTTP 메세지 컨버터가 HTTP 메세지 바디의 내용을 우리가 지정한 객체나 원하는 문자 등의 형태로 자동으로 변환해준다. @RequestBody 는 생략이 불가능하다. 애노테이션을 생략하면, @ModelAttribute 가 적용되어 버린다는 점을 주의하자.
요청 매핑 핸들러 어댑터 구조
ArgumentResolver
애노테이션 기반의 컨트롤러는 매우 다양한 파라미터를 사용할 수 있다. 이렇게 다양한 파라미터를 유연하게 처리할 수 있는 이유가 바로 ArgumentResolver 덕분이다. 애노테이션 기반 컨트롤러를 처리하는 핸들러 어댑터는 이 ArgumentResolver를 호출해서 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터의 값(객체)를 생성한다. ArgumentResolver는 파라미터에 대한 어노테이션(@RequestParam, @PathVariable 등)을 기반으로 동작하며 요청의 다양한 요소를 기반으로 메서드 파라미터에 값을 주입하는 것이다. 정리하면, ArgumentResolver는 컨트롤러 메서드의 파라미터를 바인딩하는데 사용된다.
HTTP 메시지 컨버터
메세지 컨버터는 HTTP 요청/응답의 본문을 변환하는데 사용된다.
HTTP 메세지 컨버터를 사용하는 @RequestBody도 컨트롤러가 필요로 하는 파라미터의 값에 사용된다. 쉽게 말해, 메시지 바디의 요청도 컨트롤러 메서드의 파라미터 값이기 때문에 ArgumentResolver를 거쳐야 한다는 것이다. 요청의 경우, @RequestBody와 HttpEntity를 처리하는 ArgumentResolver가 있고 이것들이 HTTP 메세지 컨버터를 사용해서 필요한 객체를 생성하는 것이다. 응답의 경우, @ResponseBody와 HttpEntity 를 처리하는 ReturnValueHandler가 있다. 그리고 여기에서 HTTP 메세지 컨버터를 호출하여 응답 결과를 만든다.
'Spring' 카테고리의 다른 글
스프링 MVC 2편 - 섹션 7~9 (0) | 2025.03.17 |
---|---|
스프링 MVC 2편 - 섹션 4~5 (0) | 2025.03.17 |
스프링 MVC 1편 - 섹션 1~3 (0) | 2025.03.17 |
스프링 핵심 원리 기본편 - 섹션 9~10 (0) | 2025.03.17 |
스프링 핵심 원리 기본편 - 섹션 7~8 (0) | 2025.03.17 |