Controller에 도착하기 전, 요청은 어디를 지날까?
DispatcherServlet, HandlerMapping, HandlerAdapter를 HTTP 요청 한 건의 여정으로 연결합니다.
사이트 초기 콘텐츠로 작성한 샘플 글입니다. 프로젝트 수치는 작성자 제공 자료이며, Java 코드는 원리를 보여주는 예제입니다.
Controller 메서드는 요청의 출발점이 아닙니다. 이미 Servlet 컨테이너와 Spring MVC가 요청을 해석하고 적절한 메서드를 고른 뒤에야 호출됩니다.
큰 흐름은 요청을 받을 창구 → 담당자 찾기 → 실행 준비 → 실제 업무 → 응답 포장입니다. 이 흐름을 알면 404와 인자 바인딩 오류, 서비스 예외를 서로 다른 단계의 문제로 볼 수 있습니다.
1. 중앙 창구와 담당자 찾기
DispatcherServlet은 중앙 창구입니다. HandlerMapping에게 경로와 HTTP 메서드에 맞는 핸들러를 물어봅니다. @GetMapping이 붙은 메서드가 어디에 있는지 찾는 단계입니다.
담당자를 찾았다고 바로 호출할 수는 없습니다. URL의 42를 Long으로 바꾸고, 요청 본문을 객체로 읽어 메서드 인자를 준비해야 합니다. HandlerAdapter가 이 실행 규약을 연결합니다.
2. 요청을 한 단계씩 따라가기
SPRING MVC / REQUEST TRACE
CONCEPT MODEL요청이 출발할 준비가 되었습니다.
자동 재생, 다음 단계, 또는 위의 컴포넌트를 눌러 요청 흐름을 따라가세요.
동기식 REST 요청의 대표 경로를 단순화했습니다. Servlet Filter·Interceptor·보안 필터·예외 처리·비동기 요청과 ViewResolver는 생략했습니다. Database 이후에는 호출 스택을 거슬러 응답합니다.
이 흐름은 동기식 REST 예제입니다. 실제 요청에는 보안 필터, 인터셉터, 예외 처리기가 끼어들 수 있습니다. 모든 요청이 Repository나 Database까지 도달하는 것은 아닙니다.
3. 계층을 나누는 이유
Controller에 HTTP 처리와 비즈니스 규칙, SQL을 모두 넣으면 작은 변경도 여러 관심사를 건드립니다. 계층은 파일을 예쁘게 나누기 위한 것이 아니라 변경되는 이유를 분리하기 위한 것입니다.
@GetMapping("/api/albums/{id}")
public AlbumResponse find(@PathVariable Long id) {
return albumService.findById(id);
}@Transactional(readOnly = true)
public AlbumResponse findById(Long id) {
Album album = albumRepository.findById(id)
.orElseThrow(AlbumNotFoundException::new);
return AlbumResponse.from(album);
}Controller는 HTTP 계약을, Service는 업무 규칙과 트랜잭션 경계를, Repository는 데이터 접근을 담당합니다. AlbumResponse와 예외 클래스는 설명을 위한 도메인 타입입니다.
4. 돌아오는 길에도 처리가 있다
Database가 반환한 데이터는 Repository, Service, Controller를 거슬러 올라갑니다. Controller가 DTO를 반환하면 HttpMessageConverter가 JSON으로 직렬화합니다.
엔티티를 그대로 반환할 때 직렬화 중 지연 로딩이 발생할 수 있습니다. 요청 흐름과 N+1 문제는 여기에서 연결됩니다. DTO로 변환하는 위치와 트랜잭션 범위를 함께 검토해야 합니다.
5. 오류를 단계로 나누어 조사하기
- 핸들러를 못 찾았다면 경로와 HTTP 메서드부터 확인합니다.
- Controller 진입 전에 실패했다면 인자 변환과 검증을 확인합니다.
- 서비스에서 실패했다면 업무 규칙과 트랜잭션 로그를 봅니다.
- 반환 중 실패했다면 JSON 직렬화와 연관 데이터 접근을 확인합니다.
전체 경로를 알면 “Spring이 안 된다”는 막연한 문제를, 확인 가능한 작은 질문으로 바꿀 수 있습니다.