우아한테크코스 레벨 2에서 학습한 내용을 정리한 글입니다.
💭 들어가며
레벨 2에 들어가면서 Spring에 대해 깊이 있게 학습할 수 있는 시간이 주어졌다. 이전에도 서버를 직접 배포하고 운영해 본 경험은 있었지만, 정작 어노테이션의 역할을 묻는 질문에는 쉽게 대답하지 못했다. 주어진 코드를 이해하지 않고 그대로 사용하기만 했다는 사실을 깨달았다. 그래서 이번 기회에 Spring MVC의 핵심 어노테이션들과 요청 흐름에 대해 정리해보고자 한다.
✅ Annotated Controllers
Spring MVC는 어노테이션 기반의 프로그래밍 모델을 제공하여, 개발자가 요청 매핑, 요청 데이터 처리, 예외 처리 등을 어노테이션을 통해 간결하게 구현할 수 있도록 지원한다.
이러한 방식에서는 @Controller 또는 @RestController와 같은 어노테이션을 사용하여 컨트롤러를 정의하고, 다양한 HTTP 요청을 메서드 단위로 처리할 수 있다.
▶ @Controller
@Controller는 주로 View 이름을 반환하여 템플릿을 렌더링하는 용도로 사용되며, 템플릿 엔진(Thymeleaf, JSP 등)과 함께 사용한다.
Spring MVC는 내부적으로 ViewResolver를 통해 문자열 형태의 반환값을 실제 템플릿 경로로 해석하고 렌더링한다.
🔽 예시 코드
@Controller
@RequestMapping("/hello")
public class HelloController {
@GetMapping
public String handle(Model model) {
model.addAttribute("message", "Hello Spring!");
return "index"; // templates/index.html 렌더링
}
}
▶ @RestController
@RestController는 @Controller와 @ResponseBody를 합친 어노테이션이다. 반환값을 View로 해석하지 않고, HTTP 응답 본문(Response Body)에 바로 데이터를 출력한다. 주로 JSON 형태로 객체를 반환할 때 사용한다.
최근에는 프론트엔드 개발이 분리되는 구조가 일반화되면서, 화면 구성은 프론트에서 담당하고 백엔드는 RESTful API를 제공하는 형태가 많아졌다. 이런 흐름 속에서 @RestController를 가장 많이 사용한다.
🔽 예시 코드
@RestController
@RequestMapping("/hello")
public class HelloController {
@GetMapping
public String sayHello() {
return "Hello, Spring!";
}
}
✅ Mapping Requests
컨트롤러 클래스가 선언되면, 이제는 각각 메서드가 어떤 요청을 처리할지 지정해줘야 한다. 이를 위해 Spring MVC는 다양한 요청 매핑 어노테이션을 제공한다. 이 어노테이션들을 사용하면 URL 경로와 HTTP 메서드(GET, POST 등)를 기준으로 어떤 메서드가 호출되어야 할지를 선언적으로 지정할 수 있다.
이 요청 매핑 어노테이션들은 보통 클래스 레벨과 메서드 레벨에 함께 사용되며, 다음과 같은 방식으로 경로를 조합한다.
- 클래스 레벨: 공통으로 사용되는 URL prefix를 지정
- 메서드 레벨: 실제 요청을 처리할 세부 경로와 HTTP 메서드를 지정
▶ @RequestMapping
@RequestMapping 어노테이션은 요청을 컨트롤러 메서드에 매핑하기 위해 사용된다. 이 어노테이션에서 파생된 형태로, @GetMapping, @PostMapping 등 특정 HTTP 메서드에 특화된 어노테이션들도 함께 제공된다. 하지만 공통된 URL 경로를 클래스 단위로 지정할 때는 여전히 @RequestMapping이 유용하게 사용된다.
🔽 종류
어노테이션 | 설명 |
@GetMapping | GET 요청 처리 |
@PostMapping | POST 요청 처리 |
@PutMapping | PUT 요청 처리 |
@PatchMapping | PATCH 요청 처리 |
@DeleteMapping | DELETE 요청 처리 |
🔽 예시 코드
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// ...
}
@PostMapping
public void add(@RequestBody Person person) {
// ...
}
}
✅ Handler Methods
Spring MVC에서는 컨트롤러 메서드(= Handler Method)에서 다양한 어노테이션을 통해 요청 데이터를 처리할 수 있다.
이 밖에도 Handler Methods는 여러 가지가 있으며, 자세한 내용은 Spring 공식 문서를 참고하면 좋을 것 같다.
▶ @RequestParam
요청의 쿼리 파라미터 또는 HTML 폼 파라미터를 메서드 인자에 바인딩할 때 사용한다.
🔽 코드 예시
@GetMapping("/search")
public String search(@RequestParam String keyword) {
return "검색어: " + keyword;
}
- 요청 예시: GET /search?keyword=spring
@GetMapping("/filter")
public String filter(@RequestParam(required = false, defaultValue = "all") String type) {
return "필터 타입: " + type;
}
- 옵션 파라미터 처리도 가능하다.
- 요청 예시:
- GET /filter?type=popular -> 필터 타입: popular
- GET /filter -> 필터 타입: all
▶ @PathVariable
URL 경로에 포함된 값을 메서드 인자로 바인딩할 때 사용한다.
🔽 코드 예시
@GetMapping("/users/{id}")
public String getUser(@PathVariable Long id) {
return "유저 ID: " + id;
}
- 요청 예시: GET /users/42
@GetMapping("/items/{itemId}")
public String getItem(@PathVariable(name = "itemId") Long id) {
return "아이템 ID: " + id;
}
- 경로 변수명과 파라미터명이 다를 경우 name 속성으로 지정할 수 있다.
- 요청 예시: GET /items/42
▶ @ModelAttribute
요청 파라미터(쿼리 파라미터, 폼 데이터)를 객체에 바인딩할 때 사용한다. 스프링이 자동으로 객체를 생성하고 값을 주입한다.
🔽 코드 예시
@PostMapping("/register")
public String register(@ModelAttribute UserForm form) {
return "등록된 사용자: " + form.getName();
}
public class UserForm {
private String name;
private int age;
// getter, setter 필수
}
- @ModelAttribute는 생략이 가능하지만, 명확성을 위해 명시적으로 사용하는 것이 권장된다.
- 요청 예시
- POST /register?name=미소&age=10
- POST /register + 폼 데이터
▶ @RequestBody
요청 본문(주로 JSON)을 객체로 변환해 바인딩할 때 사용한다.
🔽 코드 예시
@PostMapping("/users")
public String createUser(@RequestBody UserRequest userRequest) {
return "Created: " + userRequest.getName();
}
public class UserRequest {
private String name;
private int age;
// getter, setter 필수
}
- 요청 예시:
POST /users
{
"name": "미소",
"age": 10
}
▶ @ResponseBody
메서드 반환값을 HTTP 응답 본문에 직접 전달한다. 주로 JSON 응답에 사용된다. @RestController를 사용하면 모든 메서드에 자동으로 적용된다.
🔽 코드 예시
@GetMapping("/ping")
@ResponseBody
public String ping() {
return "pong";
}
- 응답: “pong”
@GetMapping("/user")
public User getUser() {
return new User("미소", 10); // JSON으로 자동 변환
}
public class User {
private String name;
private int age;
// 생성자, getter 필수
}
- 응답:
{
"name": "미소",
"age": 10
}
▶ ResponseEntity
응답 본문뿐만 아니라 HTTP 상태 코드와 헤더까지 세밀하게 제어할 수 있는 가장 유연한 응답 방식이다.
🔽 코드 예시
@GetMapping("/response")
public ResponseEntity<String> respond() {
return ResponseEntity.ok("성공!");
}
- 응답:
200 OK
"성공!"
@PostMapping("/create")
public ResponseEntity<User> create(@RequestBody User user) {
return ResponseEntity
.status(HttpStatus.CREATED)
.body(user);
}
- 응답:
201 Created
{
"name": "미소",
"age": 10
}
@GetMapping("/not-found")
public ResponseEntity<String> notFound() {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body("요청하신 리소스를 찾을 수 없습니다.");
}
- 응답:
404 Not Found
"요청하신 리소스를 찾을 수 없습니다."
✅ Spring MVC 기반 애플리케이션 요청 처리 흐름
Spring MVC는 요청을 다음과 같은 흐름으로 처리한다.
- 사용자가 요청을 보낸다.
- 클라이언트가 HTTP 요청을 보낸다.
- DispatcherServlet이 요청을 받는다.
- DispatcherServlet은 Front Controller로, 모든 요청을 일괄 처리하는 역할을 한다.
- web.xml 또는 SpringBootApplication의 자동 설정에 의해 서블릿으로 등록되어 / 경로 하위 모든 요청을 가로챈다.
- 이 시점에 HttpServletRequest, HttpServletResponse가 DispatcherServlet을 통해 전달된다.
- HandlerMapping이 어떤 컨트롤러가 처리할지 매핑한다.
- Spring 내부에는 여러 HandlerMapping 구현체가 있다. 이들은 요청 URL과 컨트롤러 메서드를 매핑하는 역할을 한다.
- 대표적으로 RequestMappingHandlerMapping이 있으며, @RequestMapping, @GetMapping 등을 분석해 어떤 메서드가 이 요청을 처리할지 결정한다.
- URL과 HTTP 메서드를 기반으로 요청에 알맞은 컨트롤러 메서드(HandlerMethod)가 선택된다.
- HandlerAdapter가 해당 메서드를 호출한다.
- 매핑된 HandlerMethod를 실제로 실행하는 역할은 HandlerAdapter가 맡는다.
- 파라미터 바인딩(@RequestParam, @PathVariable, @RequestBody, @ModelAttribute)도 이 단계에서 처리된다.
- 이 과정에서 컨트롤러 메서드는 실제로 호출되고, 응답값을 반환한다.
- 반환값 처리 (Handler → View or ResponseBody)
- 문자열 View 이름 반환 (@Controller)
- 컨트롤러가 문자열로 View 이름을 반환하면, ViewResolver가 View 이름을 기반으로 실제 템플릿 파일을 찾아 렌더링한다.
- JSON, 텍스트 등 응답 반환 (@RestController, @ResponseBody)
- 반환값 HttpMessageConverter를 통해 직렬화되어 JSON, XML, 문자열 등으로 변환한다.
- 이때 ViewResolver는 동작하지 않고, 반환된 객체는 HttpMessageConverter에 의해 직렬화된다.
- 문자열 View 이름 반환 (@Controller)
- DispatcherServlet이 최종적으로 응답을 클라이언트에게 반환한다.
- 렌더링된 View 또는 직렬화된 데이터가 HttpServletResponse에 담긴다.
- 최종적으로 사용자(브라우저, 클라이언트)는 HTTP 응답을 받는다.
📍 참고 자료
- Spring 공식 문서 - Annoted Controllers
- Spring 공식 문서 - Mapping Requests
- Spring 공식 문서 - Handler Methods
- Spring 공식 문서 - MethodArguments
- Spring 공식 문서 - Return Values
- Spring 공식 문서 - @RequestParam
- Spring 공식 문서 - @ModelAttribute
- Spring 공식 문서 - @RequestBody
- Spring 공식 문서 - @ResponseBody
- Spring 공식 문서 - ResponseEntity
- Spring 공식 문서 - DispatcherServlet
'Programming > Spring' 카테고리의 다른 글
[Spring] 스프링 빈(Spring Bean), Spring Core 어노테이션 (0) | 2025.04.23 |
---|---|
[Spring] IoC 컨테이너, 의존성 주입(DI) (0) | 2025.04.23 |