페이지 불러오는 중
Spring MVC는 프론트 컨트롤러 패턴(Front Controller Pattern) 기반의 DispatcherServlet을 중심으로 작동한다.
클라이언트의 HTTP 요청 수신부터 컨트롤러 파라미터 자동 파싱(ArgumentResolver), 비즈니스 로직 수행, 그리고 표준 HTTP 응답 생성(HttpMessageConverter)까지의 전 과정을 통합 관리한다.
본 가이드는 DispatcherServlet의 처리 흐름, 요청 데이터 타입별 파싱 규칙과 QUERY 등 최신 HTTP 스펙 추세, 그리고 ResponseEntity<T> 및 공통 응답 Wrapper(ApiResponse<T>) 기반의 응답 모범 사례를 종합적으로 다룬다.
Spring Web MVC 환경에서 클라이언트와 컨트롤러 사이의 모든 통신은 DispatcherServlet을 거친다.
[Client Request]
│
▼
1. DispatcherServlet ────> 2. HandlerMapping (요청 URL 및 Method 매핑 Controller/Handler 탐색)
│ │
│ <── (Handler Execution Chain) ──┘
▼
3. HandlerAdapter (Controller 호출 어댑터 선택)
│
├───────> ArgumentResolver (`@RequestBody`, `@RequestParam` 등 파라미터 바인딩)
│ │
├───────> 4. Controller (Service/Repository 비즈니스 로직 처리)
│ │
│ <────── (반환 객체 또는 ResponseEntity) ┘
▼
5. 응답 처리 메커니즘 선택
├── A. SSR (View 기반): 6. ViewResolver ──> 7. View Render (HTML) ──> Client
└── B. REST API (JSON): 6. HttpMessageConverter (Jackson Write) ─────> ClientDispatcherServlet: HTTP 요청을 최초로 수신하고 전체 응답 흐름을 총괄하는 중앙 프론트 컨트롤러.HandlerMapping: URL, HTTP Method, Header 정보를 확인하여 해당 요청을 처리할 Controller 메서드를 찾는다. (RequestMappingHandlerMapping 사용)HandlerAdapter: 컨트롤러 메서드의 시그니처에 맞추어 ArgumentResolver를 통해 파라미터를 생성 및 변환 후 컨트롤러를 실제로 실행한다.HttpMessageConverter:MappingJackson2HttpMessageConverter).@RestController
@RequestMapping("/api/v1/users")
public class UserController{
// 1. PathVariable & RequestParam (경로 변수 및 쿼리 파라미터)
@GetMapping("/{userId}")
public ResponseEntity<UserResponseDto> getUser(
@PathVariable("userId")Long userId, @RequestParam(name="includeDetail", defaultValue="false")boolean includeDetail) {
UserResponseDto dto= userService.findUser(userId, includeDetail);
return ResponseEntity.ok(dto);
}
// 2. RequestBody (JSON 요청 본문 역직렬화)
@PostMapping
public ResponseEntity<Long>createUser(
@Valid @RequestBody CreateUserRequestDto requestDto){
Long createdId= userService.createUser(requestDto);
return ResponseEntity.status(HttpStatus.CREATED).body(createdId);
}
// 3. ModelAttribute (Form Data 및 Query String 객체 바인딩)
@GetMapping("/search")
public ResponseEntity<List<UserResponseDto>>searchUsers(
@ModelAttribute UserSearchCondition condition){
return ResponseEntity.ok(userService.search(condition));
}
}GET: 리소스 조회 (Safe, Idempotent). Body를 포함하지 않음.POST: 리소스 생성 또는 처리 요청 (Unsafe, Non-idempotent).PUT: 리소스 전체 대체 (Idempotent).PATCH: 리소스 부분 수정 (Non-idempotent/Idempotent).DELETE: 리소스 삭제 (Idempotent).QUERY (HTTP SEARCH / QUERY) MethodGET 메서드를 쓰면 URI 길이 제한(2048자) 및 보안 민감 정보의 URL 노출 문제가 생김. 이를 피하려고 POST를 쓰면, RESTful 관점에서 상태 변경 요청으로 오인되어 HTTP Caching 불가능 및 멱등성 보장 유실 문제가 발생.QUERY (IETF Draft): Request Body에 JSON 검색 조건을 포함하면서도, 조회 전용(Safe & Idempotent) 멱등성을 보장하는 신규 표준 HTTP Method.QUERY 지원 확대 중.임의로 FETCH_DATA 등 비표준 커스텀 Method를 만들면 네트워크 통신 계층에서 심각한 단점이 발생함:
405 Method Not Allowed 또는 403 Forbidden으로 즉시 차단.OPTIONS) 검증에 실패함.Map<String, Object> 반환을 금지해야 하는 3가지 이유 (안티 패턴)Object 타입이므로, 오타가 나거나 잘못된 타입 값이 들어가도 컴파일 시점에 감지할 수 없다.object 형태로 노출되어 프론트엔드와 협업 시 혼선을 초래한다.ApiResponse<T>)API 응답 구조의 일관성을 유지하기 위해 표준 Wrapper 클래스를 사용한다.
@Getter
@AllArgsConstructor
public class ApiResponse<T>{
private final boolean success;
private final T data;
private final ErrorDetail error;
// 성공 응답 팩토리 메서드
public static <T> ApiResponse <T> success(T data){
return newApiResponse<> (true, data,null);
}
// 실패 응답 팩토리 메서드
public static <T> ApiResponse <T> fail(String errorCode,String message){
return new ApiResponse<>(false,null, new ErrorDetail(errorCode, message));
}
}@GetMapping("/{orderId}")
public ResponseEntity<ApiResponse<OrderResponseDto>> getOrder(
@PathVariable("orderId")Long orderId){
OrderResponseDto dto= orderService.findOrder(orderId);
return ResponseEntity.ok(ApiResponse.success(dto));
}@RequestBody) 대상 DTO에는 기본 생성자가 존재하는가?@ModelAttribute) 대상 DTO에는 Setter 또는 생성자 바인딩 설정이 있는가?@RequestParam) 수신 시 defaultValue를 적절히 설정하였는가?GET, 생성은 POST, 수정은 PUT/PATCH, 삭제는 DELETE 표준을 준수하는가?Map<String, Object> 대신 명확한 타입의 DTO 객체를 작성했는가?ResponseEntity<ApiResponse<T>> 포맷을 사용했는가?
댓글
GitHub 계정으로 의견이나 질문을 남길 수 있습니다.