🎯 15주차 학습 커리큘럼 — Spring MVC 완전 정복

F-lab 1~14주차 이후 Claude가 임의로 구성한 학습 경로.
F-lab에서 다루지 않은 가장 큰 공백 영역인 Spring MVC의 내부 메커니즘 을 정복하고, 8-9주차 AOP와의 관계를 명확히 정리한다.

  • DispatcherServlet 동작 원리 (9단계 요청 처리 흐름)
  • 요청/응답 데이터 바인딩 메커니즘
  • Filter vs Interceptor vs AOP (면접 단골)
  • 예외 처리와 Validation 표준 패턴
  • REST API 설계와 구현

4년 차 Spring Boot 개발자가 "매일 쓰지만 내부는 모르는" 영역을 채우는 주차다.


🤔 왜 15주차에 Spring MVC인가

1~14주차의 가장 큰 공백:

영역주차상태
Java 언어1~3주차
동시성4주차
Spring IoC/DI5주차
웹 인프라 입문 (Servlet/JSP)6주차
JPA + 트랜잭션7주차
Spring AOP8-9주차
트랜잭션 정리 + 격리 수준10주차
JPA 깊이11-12주차
DB 펀더멘털 + 운영13-14주차
Spring MVC 내부 메커니즘미등장

왜 이 주제가 결정적인가:
1. 면접 단골 — DispatcherServlet 9단계, Filter/Interceptor/AOP 비교는 거의 100%
2. 8-9주차 AOP의 완성 — Filter/Interceptor와 비교해야 AOP의 진짜 위치가 보임
3. 6주차 Servlet의 자연스러운 확장 — Front Controller 패턴으로 통합
4. 실무 디버깅 능력 — 요청이 어디서 어떻게 처리되는지 알아야 문제 추적 가능


📊 학습 경로 한눈에 보기

[Phase 1] 웹 요청의 여정 — Servlet에서 Spring MVC까지
   ↓
[Phase 2] DispatcherServlet 9단계 처리 흐름 ◄ 정점 1
   ↓
[Phase 3] 요청 데이터 바인딩 (@RequestBody 등의 내부)
   ↓
[Phase 4] 응답 처리와 ViewResolver
   ↓
[Phase 5] Filter vs Interceptor vs AOP ◄ 정점 2 (★ 면접 단골)
   ↓
[Phase 6] 예외 처리와 Validation
   ↓
[Phase 7] REST API 설계와 실전

총 7 Phase × 24 Unit — 정점 2개를 가진 단일 주차.

🔗 1~15주차 흐름 정리

주차주제의미
1~3주차Java 언어와 표현력기초
4주차동시성·멀티스레딩동시성
5주차Spring IoC/DISpring 입문
6주차웹 인프라 + DB 접근실전 환경 입문
7주차JPA + 트랜잭션 입문데이터 추상화
8-9주차AOP 메커니즘 + 트랜잭션 전파AOP 정복
10주차트랜잭션 정리 + 격리 수준트랜잭션 마무리
11-12주차JPA 영속성 + 연관관계 + N+1JPA 정복
13-14주차DB 펀더멘털 + 운영DB 정복
15주차 (지금)Spring MVC 내부 + REST API웹 계층 정복

이제 백엔드 풀스택의 모든 영역을 본 셈.


🗓️ 권장 학습 일정 (압축 7일)

DayPhase학습 목표
1일차Phase 1Servlet → Spring MVC 진화
2-3일차Phase 2DispatcherServlet 9단계 (★)
4일차Phase 3요청 바인딩 메커니즘
5일차Phase 4응답과 ViewResolver
6일차Phase 5Filter vs Interceptor vs AOP (★)
7일차Phase 6 + 7예외 처리 + REST API + 종합 자기 점검

여유 일정 (10일): Phase 2, 5에 각 +1일. 두 정점은 직접 디버거로 step-through 권장.


📚 Phase 1 — 웹 요청의 여정 (Servlet에서 Spring MVC까지)

목표: 6주차의 Servlet/JSP 학습을 다시 짚고, Spring MVC가 어떤 진화의 결과인지 이해한다.

Unit 1.1 — Servlet과 Servlet Container 복습

선수 지식: 6주차 Phase 7

핵심 복습

Servlet:

  • 자바 표준 웹 컴포넌트 인터페이스
  • HTTP 요청을 받아 응답을 만드는 자바 클래스
  • HttpServletRequest, HttpServletResponse 사용

Servlet Container (Tomcat 등):

  • Servlet의 생명주기 관리
  • 요청 → Servlet으로 라우팅
  • 응답 → 클라이언트에게 전송

전형적인 Servlet 코드 (옛날 방식):

@WebServlet("/users/*")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) {
        String path = req.getPathInfo();  // "/123"
        // 분기 처리...
        // JSON 직접 작성...
        res.getWriter().write(json);
    }
}

Servlet의 한계:
1. URL마다 Servlet을 만들면 Servlet 폭증
2. 공통 처리 (인증, 로깅) 반복 코드
3. URL 라우팅 분기 수동 작성
4. JSON 변환 수동 작성

Front Controller 패턴 필요

자기 점검

  • Servlet 1000개를 만들면 어떤 문제? (힌트: 매핑 관리, 공통 처리)
  • 6주차의 어떤 문제와 연결되는가? (힌트: 반복 코드의 분리)

Unit 1.2 — Front Controller 패턴

선수 지식: Unit 1.1, 8주차 Phase 6 (디자인 패턴)

핵심 개념

Front Controller 패턴:

"모든 요청을 한 곳에서 받아서 적절한 처리기로 위임"

구조:

Before:                          After (Front Controller):
[Client] → [Servlet A]           [Client] → [Front Controller]
[Client] → [Servlet B]                            ↓ (분기)
[Client] → [Servlet C]                      [Handler A/B/C]

효과:

  • 공통 처리 한 곳에 (인증, 로깅, 변환)
  • URL 라우팅 중앙화
  • 확장성

비유:

"전화 교환원 — 모든 전화가 한 명에게 와서, 그 사람이 적절한 부서로 연결"

8주차 Phase 1과 연결:

  • 변하는 것 (각 비즈니스 로직) vs 변하지 않는 것 (공통 처리)
  • → 분리

Spring MVC의 DispatcherServlet 이 정확히 이 역할

자기 점검

  • 5주차 Spring IoC와 어떻게 연결되는가? (힌트: 모두 "공통 추상화")
  • Front Controller가 단일 장애점(SPOF)이 되지 않을까? (힌트: 무상태 + 가벼움 + 다중 인스턴스)

Unit 1.3 — Spring MVC 아키텍처와 DispatcherServlet의 위치

선수 지식: Unit 1.2

핵심 그림

[Client]
    ↓ HTTP Request
[Servlet Container (Tomcat)]
    ↓
[Filter Chain]            ← Servlet 표준
    ↓
[DispatcherServlet]       ← Spring의 Front Controller ⭐
    ↓
[Interceptor Chain]       ← Spring 제공
    ↓
[Controller (@Controller)]
    ↓
[Service / Repository]    ← AOP 적용
    ↓
[Database]

DispatcherServlet의 정체:

  • HttpServlet을 상속한 평범한 Servlet
  • 그러나 Spring의 Front Controller
  • Spring Boot가 자동 등록 (/ 경로 매핑)

Spring Boot의 자동 설정:

// 자동으로 동작 (개발자가 직접 등록 X)
@Bean
public DispatcherServlet dispatcherServlet() {
    return new DispatcherServlet();
}

핵심 통찰:

"DispatcherServlet도 결국은 하나의 Servlet 일 뿐이다.
그러나 그 안에서 Spring의 모든 마법이 일어난다."

ILIC 관점:

  • 431개 API 모두 → DispatcherServlet 1개가 처리
  • 컨트롤러로 라우팅 → 각 Service 호출
  • 응답을 JSON으로 변환 → Vue 3 클라이언트로

자기 점검

  • DispatcherServlet 안에 Vue가 보내는 모든 요청이 들어오는가? (힌트: YES, 정적 리소스 제외)
  • 8-9주차의 자동 프록시 생성기와 어떤 관계? (힌트: 둘 다 빈 후처리기 결과)

📚 Phase 2 — DispatcherServlet 9단계 처리 흐름 (★ 정점 1)

목표: 면접 단골 — Spring MVC가 요청을 어떻게 처리하는지 9단계로 완벽히 이해한다.

Unit 2.1 — 9단계 요청 처리 흐름 ⭐⭐⭐

선수 지식: Phase 1

핵심 9단계 (외워야 함):

1. [DispatcherServlet] HTTP 요청 수신
        ↓
2. [HandlerMapping] 어떤 컨트롤러 메서드인지 찾기
        ↓
3. [HandlerAdapter] 그 컨트롤러 메서드를 호출
        ↓
4. [Interceptor preHandle] 컨트롤러 호출 전 처리
        ↓
5. [Controller] 비즈니스 로직 실행 → ModelAndView 반환
        ↓
6. [Interceptor postHandle] 컨트롤러 호출 후 처리
        ↓
7. [ViewResolver] View 이름 → 실제 View 객체로 변환
        ↓
8. [View.render()] HTML 또는 JSON 응답 생성
        ↓
9. [Interceptor afterCompletion] 응답 완료 후 처리
        ↓
[Client]

REST API 흐름 (View 단계 단순화):

1~6. (위와 동일)
        ↓
7. [HttpMessageConverter] 객체 → JSON 변환
        ↓
8. JSON 응답
        ↓
9. afterCompletion

핵심 통찰:

  • 모든 단계가 확장 가능
  • 각 단계마다 인터페이스가 있고 빈으로 등록 가능
  • → Spring의 진정한 강력함

자기 점검

  • Step 1과 Step 9의 차이를 한 문장씩으로?
  • Filter는 이 9단계 중 어디에 있는가? (힌트: 1단계 이전 + 9단계 이후 — Servlet 표준)

Unit 2.2 — HandlerMapping (어떤 컨트롤러를?)

선수 지식: Unit 2.1

핵심 역할:

"URL → Controller 메서드 매핑 정보를 관리"

대표적 HandlerMapping:

구현체매핑 방식
RequestMappingHandlerMapping@RequestMapping, @GetMapping
BeanNameUrlHandlerMapping빈 이름이 URL인 경우
SimpleUrlHandlerMapping명시적 URL-빈 매핑

현대 Spring Boot에서는 RequestMappingHandlerMapping 이 사실상 표준.

동작 예시:

@RestController
@RequestMapping("/api/bookings")
public class BookingController {
    @GetMapping("/{id}")
    public Booking getBooking(@PathVariable Long id) { ... }
}

요청: GET /api/bookings/123

RequestMappingHandlerMappingBookingController.getBooking 메서드를 반환

내부 구조:

  • 빈 등록 시 @RequestMapping 어노테이션 스캔
  • URL 패턴 + HTTP 메서드 → 메서드 매핑 테이블 구축
  • 요청 시 매칭

디버깅 팁:

  • 매핑 정보 확인: Actuator의 /actuator/mappings 엔드포인트
  • 부팅 로그에 등록된 매핑 출력 가능

자기 점검

  • 같은 URL에 두 컨트롤러가 매핑되면? (힌트: AmbiguousMappingException)
  • ILIC의 431 API가 모두 같은 HandlerMapping에서 관리되는가? (YES)

Unit 2.3 — HandlerAdapter (어떻게 호출할까)

선수 지식: Unit 2.2

핵심 개념

문제: Controller는 다양한 형태로 작성 가능

  • 어노테이션 기반 (@Controller)
  • HttpRequestHandler 인터페이스 기반
  • 옛날 Servlet 스타일

호출 방법이 다 다름

HandlerAdapter의 해결:

"다양한 핸들러를 통일된 방식으로 호출"

대표적 HandlerAdapter:

구현체대상
RequestMappingHandlerAdapter@RequestMapping 메서드
HttpRequestHandlerAdapterHttpRequestHandler
SimpleControllerHandlerAdapter옛 Controller 인터페이스

디자인 패턴:

  • Adapter Pattern (5주차 디자인 패턴 추가)
  • 다양한 인터페이스를 한 인터페이스로 통일

핵심 동작 — 어노테이션 기반의 마법:

@GetMapping("/users/{id}")
public User getUser(
    @PathVariable Long id,
    @RequestParam String type,
    HttpServletRequest req,
    @AuthenticationPrincipal User currentUser
) { ... }

→ HandlerAdapter가:
1. @PathVariable → URL에서 추출
2. @RequestParam → 쿼리 스트링에서 추출
3. HttpServletRequest → 요청 객체 그대로 전달
4. @AuthenticationPrincipal → Security Context에서 추출

ArgumentResolver:

  • 각 파라미터 타입별 추출 로직
  • RequestParamMethodArgumentResolver, PathVariableMethodArgumentResolver
  • 확장 가능 → 커스텀 어노테이션 만들기

자기 점검

  • 8-9주차 ProxyFactory와 어떤 패턴이 같은가? (힌트: Adapter — JDK/CGLIB 통합)
  • ILIC에서 커스텀 ArgumentResolver를 만든다면? (힌트: 인증된 사용자 정보 자동 주입)

Unit 2.4 — HandlerInterceptor (전후 처리)

선수 지식: Unit 2.3

핵심 개념

HandlerInterceptor:

"Controller 호출 전·후·완료 후 가로채기"

3가지 메서드 ⭐ :

public interface HandlerInterceptor {
    boolean preHandle(req, res, handler);     // Controller 호출 전
    void postHandle(req, res, handler, mav);  // Controller 호출 후, View 렌더링 전
    void afterCompletion(req, res, handler, ex);  // 응답 완료 후 (예외 포함)
}

예시 — 인증 체크:

public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
        String token = req.getHeader("Authorization");
        if (!isValid(token)) {
            res.setStatus(401);
            return false;  // 컨트롤러 호출 안 함!
        }
        return true;
    }
}

등록:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor())
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/auth/**");
    }
}

preHandle false 반환 시:

  • Controller 호출 안 됨
  • postHandle, afterCompletion도 호출 안 됨
  • 다음 Interceptor도 호출 안 됨

활용 예시:

  • 인증/인가 검증
  • 요청 로깅
  • 성능 측정 (Phase 5에서 AOP와 비교)
  • 비즈니스 통계 수집

자기 점검

  • Interceptor와 8-9주차 AOP는 어떻게 다른가? (Phase 5에서 자세히)
  • 컨트롤러 메서드 단위로 적용하려면? (힌트: 패턴 매칭 또는 어노테이션 기반)

📚 Phase 3 — 요청 데이터 바인딩

목표: @RequestBody, @RequestParam, @PathVariable 등이 어떻게 동작하는지 이해한다.

Unit 3.1 — 4가지 파라미터 어노테이션 비교

선수 지식: Phase 2

핵심 비교 ⭐ :

어노테이션어디서 추출타입
@PathVariableURL 경로/users/{id}
@RequestParam쿼리 스트링 / 폼?name=Alice
@RequestBodyHTTP bodyJSON, XML
@ModelAttribute쿼리 + 폼 → 객체 바인딩(자동)

예시 종합:

// 1. PathVariable
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) { ... }
// 요청: GET /users/42

// 2. RequestParam
@GetMapping("/users")
public List<User> searchUsers(
    @RequestParam(required = false) String name,
    @RequestParam(defaultValue = "10") int size
) { ... }
// 요청: GET /users?name=Alice&size=20

// 3. RequestBody
@PostMapping("/users")
public User createUser(@RequestBody UserDto dto) { ... }
// 요청: POST /users
// Body: {"name": "Alice", "email": "..."}

// 4. ModelAttribute
@GetMapping("/search")
public List<User> search(@ModelAttribute SearchCriteria criteria) { ... }
// 요청: GET /search?name=Alice&age=25
// → SearchCriteria 객체에 자동 바인딩

언제 무엇을 ⭐ :

상황추천
리소스 식별자 (id)@PathVariable
검색 조건, 필터@RequestParam
복잡한 객체 (POST/PUT)@RequestBody
GET의 다중 검색 조건 → 객체@ModelAttribute

자기 점검

  • ILIC의 운임 검색 6개 조건은 어떤 어노테이션이 적합? (힌트: ModelAttribute)
  • POST 요청에 @RequestParam을 쓸 수 있는가? (힌트: form-encoded면 가능)

Unit 3.2 — HttpMessageConverter (객체 ↔ JSON 변환)

선수 지식: Unit 3.1

핵심 역할:

"HTTP body(JSON/XML) ↔ 자바 객체 변환"

@RequestBody / @ResponseBody 의 진짜 정체:

  • 단순 어노테이션이 아닌 → HttpMessageConverter 트리거

대표 Converter:

Converter처리
MappingJackson2HttpMessageConverterJSON ↔ 객체
StringHttpMessageConvertertext/plain
Jaxb2RootElementHttpMessageConverterXML
ByteArrayHttpMessageConverterbyte[]

동작 흐름 (@RequestBody):

[HTTP Body]
{ "name": "Alice", "age": 25 }
        ↓
[MappingJackson2HttpMessageConverter.read()]
        ↓
[Jackson ObjectMapper]
        ↓
[자바 객체]
new UserDto("Alice", 25)
        ↓
[Controller 메서드 파라미터로 주입]

@ResponseBody (정확히 반대):

[Controller 반환값]
        ↓
[Jackson ObjectMapper.writeValueAsString()]
        ↓
[JSON 문자열]
        ↓
[HTTP Response Body]

커스터마이징:

@Configuration
public class JacksonConfig {
    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper()
            .registerModule(new JavaTimeModule())  // LocalDateTime
            .setDateFormat(new SimpleDateFormat("yyyy-MM-dd"))
            .setSerializationInclusion(Include.NON_NULL);
    }
}

ILIC 활용:

  • 운임 견적 객체 → JSON 자동 변환
  • Vue 3에서 받는 JSON → 자바 객체 자동 변환

자기 점검

  • 11-12주차의 JPA Lazy 프록시를 JSON 변환 시 어떤 문제? (힌트: LazyInitializationException, 무한 루프)
  • 해결책 3가지는? (힌트: DTO 변환, @JsonIgnore, FetchType.EAGER 또는 fetch join)

Unit 3.3 — Content Negotiation (Accept 헤더)

선수 지식: Unit 3.2

핵심 개념

Content Negotiation:

"클라이언트가 요청한 응답 형식 에 맞춰 응답"

HTTP Accept 헤더:

GET /api/users/1 HTTP/1.1
Accept: application/json

→ 서버: JSON으로 응답

GET /api/users/1 HTTP/1.1
Accept: application/xml

→ 서버: XML로 응답 (해당 Converter 등록 시)

Spring의 결정 흐름:
1. 컨트롤러 메서드 반환 타입 확인
2. 응답 가능한 Converter 목록 확인
3. Accept 헤더와 매칭 되는 Converter 선택
4. 변환

produces 명시:

@GetMapping(value = "/users/{id}", produces = "application/json")
public User getUser(@PathVariable Long id) { ... }

→ JSON만 응답 (그 외 Accept는 406 Not Acceptable)

consumes 명시:

@PostMapping(value = "/users", consumes = "application/json")
public User createUser(@RequestBody UserDto dto) { ... }

→ Content-Type이 JSON일 때만 처리

ILIC 시나리오:

  • API는 모두 JSON → produces = "application/json" 명시 가능
  • 그러나 보통 Spring Boot 기본값으로 충분

자기 점검

  • Vue 3 클라이언트가 보내는 요청의 Content-Type은 보통? (힌트: application/json)
  • 같은 URL이 JSON/XML 둘 다 응답 가능하게 하려면? (힌트: produces 다중 지정 또는 둘 다 등록)

📚 Phase 4 — 응답 처리와 ViewResolver

목표: REST API 시대의 ViewResolver 의미와 ResponseEntity 활용을 정리한다.

Unit 4.1 — ViewResolver의 역할

선수 지식: Phase 3

핵심 개념

ViewResolver:

"Controller가 반환한 View 이름실제 View 객체 변환"

전통적 흐름 (View 기반):

@Controller
public class HomeController {
    @GetMapping("/")
    public String home(Model model) {
        model.addAttribute("user", currentUser);
        return "home";  // View 이름 반환
    }
}

→ ViewResolver: "home" → home.html 또는 home.jsp 찾아서 렌더링

대표 ViewResolver:

구현체처리 대상
ThymeleafViewResolverThymeleaf 템플릿
InternalResourceViewResolverJSP
BeanNameViewResolver빈 이름이 View

현대 Spring Boot — REST API 시대:

  • Vue/React 등 SPA 사용
  • 서버는 JSON만 반환
  • ViewResolver 거의 안 씀

@RestController 의 동작:

@RestController
public class UserController {
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.find(id);  // 객체 반환
    }
}

→ ViewResolver를 거치지 않고 HttpMessageConverter로 직접 JSON 변환

ILIC 시나리오:

  • 모든 컨트롤러 → @RestController
  • ViewResolver 미사용
  • Vue 3가 Frontend 렌더링 담당

자기 점검

  • ViewResolver가 정말 사라진 건가? (힌트: 서버 사이드 렌더링 SSR 시 여전히 사용)
  • Thymeleaf와 React는 어떤 차이? (힌트: 서버 vs 클라이언트 렌더링)

Unit 4.2 — @RestController vs @Controller

선수 지식: Unit 4.1

핵심 차이 ⭐ :

@Controller@RestController
정의View 이름 반환데이터 직접 반환
응답보통 HTML보통 JSON
@ResponseBody메서드마다 명시 필요자동 적용
용도SSR, 전통 웹REST API

실제 정의:

@Controller
@ResponseBody  // ← 합친 게 RestController
public @interface RestController { ... }

예시 비교:

@Controller  // 전통 (SSR)
public class TraditionalController {
    @GetMapping("/users")
    public String list(Model model) {
        model.addAttribute("users", users);
        return "user-list";  // View 이름
    }
    
    @GetMapping("/api/users")
    @ResponseBody  // 명시 필요
    public List<User> apiList() {
        return userService.findAll();
    }
}

@RestController  // 현대 (REST API) ⭐
public class ApiController {
    @GetMapping("/users")
    public List<User> list() {
        return userService.findAll();  // 자동 JSON 변환
    }
}

ILIC: 모든 컨트롤러가 @RestController

자기 점검

  • 한 컨트롤러에서 SSR과 REST를 섞으려면? (힌트: @Controller + 선택적 @ResponseBody)
  • 보통 그렇게 섞는가? (힌트: 분리 권장)

Unit 4.3 — ResponseEntity와 HTTP 상태 코드

선수 지식: Unit 4.2

핵심 개념

ResponseEntity:

"HTTP 상태 코드, 헤더, 바디 를 직접 제어"

기본 반환 vs ResponseEntity:

// 기본: 항상 200 OK
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
    return userService.find(id);
}

// ResponseEntity: 상태 코드 명시 가능
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    User user = userService.find(id);
    if (user == null) {
        return ResponseEntity.notFound().build();  // 404
    }
    return ResponseEntity.ok(user);  // 200
}

자주 쓰는 패턴:

// 201 Created (POST)
return ResponseEntity.status(HttpStatus.CREATED)
    .header("Location", "/users/" + user.getId())
    .body(user);

// 400 Bad Request
return ResponseEntity.badRequest().body(errorMessage);

// 204 No Content (DELETE 후)
return ResponseEntity.noContent().build();

// 400대 with body
return ResponseEntity.status(409)  // Conflict
    .body(Map.of("error", "이미 존재합니다"));

REST API 표준 상태 코드 ⭐ :

코드의미사용 시점
200 OK성공일반 조회/수정
201 Created생성됨POST 후
204 No Content성공 (응답 X)DELETE 후
400 Bad Request잘못된 요청Validation 실패
401 Unauthorized인증 필요로그인 안 됨
403 Forbidden권한 없음인증은 OK, 권한 X
404 Not Found리소스 없음잘못된 ID
409 Conflict충돌중복 등록
500 Internal Server Error서버 오류예외 발생

ILIC 적용:

@PostMapping("/bookings")
public ResponseEntity<Booking> create(@RequestBody BookingDto dto) {
    Booking saved = bookingService.create(dto);
    return ResponseEntity
        .status(HttpStatus.CREATED)
        .header("Location", "/bookings/" + saved.getId())
        .body(saved);
}

자기 점검

  • 모든 응답을 200으로만 보내면 어떤 문제? (힌트: REST 원칙 위반, 클라이언트 처리 어려움)
  • 401과 403의 차이를 한 문장으로? (힌트: 인증 vs 권한)

📚 Phase 5 — Filter vs Interceptor vs AOP (★ 정점 2 — 면접 단골)

목표: 가장 자주 묻는 면접 질문 — 세 가지 횡단 관심사 처리 도구의 차이를 명확히 잡는다.

Unit 5.1 — Servlet Filter

선수 지식: Phase 1

핵심 개념

Servlet Filter:

"DispatcherServlet 도달 전·후 에서 동작 — Servlet 표준"

위치:

[Client] → [Filter] → [DispatcherServlet] → [Interceptor] → [Controller]

구현:

@Component
public class LoggingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 
            throws IOException, ServletException {
        // 요청 전
        long start = System.currentTimeMillis();
        
        chain.doFilter(req, res);  // 다음 Filter / DispatcherServlet
        
        // 응답 후
        long duration = System.currentTimeMillis() - start;
        log.info("Request took {}ms", duration);
    }
}

특징:

  • Servlet 표준 — Spring 없이도 동작
  • request/response 자체 변경 가능 (Wrapping)
  • DispatcherServlet 도달 전 → Spring 빈 정보 모름
  • 그러나 @Component + Filter Bean 등록은 가능

활용 사례:

  • 요청/응답 로깅
  • 인코딩 설정 (CharacterEncodingFilter)
  • Body 캐싱 (ContentCachingRequestWrapper)
  • CORS 처리
  • Security 인증 (Spring Security Filter Chain)

자기 점검

  • Spring Security가 왜 Filter 기반인가? (힌트: 가장 외곽에서 차단)
  • Filter에서 Spring 빈을 사용할 수 있는가? (힌트: @Component 사용 시 가능)

Unit 5.2 — Spring Interceptor (복습)

선수 지식: Unit 2.4, 5.1

핵심 차이:

Interceptor:

"DispatcherServlet 안에서 Controller 전·후 동작 — Spring 제공"

위치:

[Client] → [Filter] → [DispatcherServlet] → [Interceptor] → [Controller]

Filter와의 결정적 차이 ⭐ :

  • DispatcherServlet 이후 동작
  • Spring 빈 정보 활용 가능
  • HandlerMethod 정보 (어떤 컨트롤러 메서드인지) 알 수 있음
@Override
public boolean preHandle(req, res, handler) {
    if (handler instanceof HandlerMethod) {
        HandlerMethod method = (HandlerMethod) handler;
        // 어노테이션 확인 가능
        if (method.hasMethodAnnotation(Auditable.class)) {
            // 감사 로그
        }
    }
    return true;
}

활용 사례:

  • 인증/인가 (Spring Security 안 쓸 때)
  • 요청 로깅 (Filter보다 풍부한 정보)
  • API 호출 횟수 제한 (Rate Limiting)
  • 타이밍 측정

Unit 5.3 — Spring AOP (8-9주차 복습)

선수 지식: 8-9주차, Unit 5.2

핵심 위치:

[Client] → [Filter] → [DispatcherServlet] → [Interceptor] → [Controller] → [Service (AOP)]

AOP의 위치:

  • Interceptor 안쪽 → 메서드 호출 단위
  • 컨트롤러도, 서비스도 모두 적용 가능

8-9주차 복습:

  • @Aspect + @Around
  • ProceedingJoinPoint
  • Pointcut으로 어디에, Advice로 무엇을

활용 사례:

  • 트랜잭션 (@Transactional) — 7주차
  • 로깅 — 메서드 단위
  • 캐싱 — 메서드 단위
  • 보안 메서드 (@PreAuthorize)

Unit 5.4 — 비교 매트릭스와 선택 기준 (★★★ 면접 단골)

선수 지식: Unit 5.1~5.3

완전 비교 ⭐ :

FilterInterceptorAOP
위치DispatcherServlet 외부DispatcherServlet 내부메서드 단위
표준Servlet 표준SpringSpring
Spring 빈 접근제한적가능가능
Handler 정보X
req/res 변경✅ (Wrapper)어렵메서드 인자/반환
메서드 단위 적용XURL 단위메서드 단위
예외 처리 위치외곽컨트롤러 전후메서드 호출 시
대표 활용Security, CORS, 인코딩인증, 로깅트랜잭션, 비즈니스 로깅

선택 기준 ⭐:

상황선택
모든 요청의 인코딩 / CORSFilter
Spring SecurityFilter (Security가 Filter 기반)
Request/Response 자체 조작Filter
URL 패턴별 인증Interceptor
컨트롤러 메서드 진입 전 권한 확인Interceptor
메서드의 트랜잭션AOP (@Transactional)
비즈니스 로직 로깅AOP
메서드 인자/반환값 변경AOP

호출 순서 (디버거로 확인 권장):

[Filter] preHandle
    ↓
[Interceptor] preHandle
    ↓
[AOP] before
    ↓
[Controller method]
    ↓
[AOP] after
    ↓
[Interceptor] postHandle
    ↓
[Interceptor] afterCompletion
    ↓
[Filter] postHandle (역순)

ILIC 적용:

  • CORS, 인코딩 → Filter
  • 사용자 활동 로깅 → Interceptor
  • @Transactional, 비즈니스 감사 로그 → AOP
  • ILIC의 변경 이력 시스템(이전 컨텍스트) 은 AOP가 자연스러운 선택

면접 모의 답변:

"Filter는 Servlet 표준으로 DispatcherServlet 외곽에서 동작합니다. Interceptor는 Spring 제공으로 컨트롤러 전후를 가로채며 Handler 정보를 알 수 있습니다. AOP는 메서드 단위로 동작하며 가장 세밀한 제어가 가능합니다. Spring Security처럼 강력한 외곽 차단이 필요하면 Filter, 컨트롤러 단위 인증/로깅이면 Interceptor, 메서드 단위 횡단 관심사면 AOP 를 사용합니다."

자기 점검

  • "왜 Spring Security는 Filter 기반인가?"
  • ILIC에서 새로운 횡단 관심사 (예: API 호출 통계)가 필요하면 셋 중 무엇? (힌트: 메서드 단위면 AOP, URL 단위면 Interceptor)

📚 Phase 6 — 예외 처리와 Validation

목표: REST API의 표준 에러 응답 패턴과 Bean Validation 활용을 마스터한다.

Unit 6.1 — HandlerExceptionResolver

선수 지식: Phase 2

핵심 개념

HandlerExceptionResolver:

"Controller에서 던진 예외를 처리 하는 컴포넌트"

기본 동작:

  • Controller에서 예외 발생
  • DispatcherServlet이 HandlerExceptionResolver 들에게 위임
  • 해결되면 → 적절한 응답
  • 안 되면 → 500 Internal Server Error

대표 구현체:

구현체처리
ExceptionHandlerExceptionResolver@ExceptionHandler 메서드
ResponseStatusExceptionResolver@ResponseStatus 어노테이션
DefaultHandlerExceptionResolverSpring 기본 예외 (404, 405 등)

자기 점검

  • 컨트롤러 안의 try-catch와 비교해 어떤 장점? (힌트: 중앙화, 재사용)

Unit 6.2 — @ExceptionHandler / @ControllerAdvice ⭐

선수 지식: Unit 6.1

핵심 패턴

컨트롤러 단위 처리 (@ExceptionHandler):

@RestController
public class UserController {
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.find(id);  // 없으면 UserNotFoundException
    }
    
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ErrorResponse> handle(UserNotFoundException ex) {
        return ResponseEntity.status(404)
            .body(new ErrorResponse(ex.getMessage()));
    }
}

전역 처리 (@ControllerAdvice 또는 @RestControllerAdvice) ⭐ :

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(EntityNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(EntityNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(ErrorResponse.of("NOT_FOUND", ex.getMessage()));
    }
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
        // Validation 에러 처리
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleAll(Exception ex) {
        log.error("Unexpected error", ex);
        return ResponseEntity.status(500)
            .body(ErrorResponse.of("INTERNAL_ERROR", "서버 오류"));
    }
}

@ControllerAdvice 의 강점:

  • 모든 컨트롤러 의 예외를 한 곳에서
  • 예외별 표준 응답 보장
  • 비즈니스 로직과 예외 처리 분리

표준 에러 응답 설계 ⭐ :

public record ErrorResponse(
    String code,           // "USER_NOT_FOUND"
    String message,        // 사람이 읽을 메시지
    LocalDateTime timestamp,
    String path,           // 요청 URL
    List<FieldError> errors  // Validation 에러 (있을 때)
) {}

@Getter @AllArgsConstructor
class FieldError {
    private String field;
    private String message;
}

예시 응답:

{
    "code": "USER_NOT_FOUND",
    "message": "User with id 42 not found",
    "timestamp": "2026-05-04T10:30:00",
    "path": "/api/users/42",
    "errors": []
}

ILIC 표준화:

  • 모든 API가 같은 에러 응답 구조
  • Vue 3 클라이언트가 일관되게 처리

자기 점검

  • @ControllerAdvice@RestControllerAdvice 의 차이는? (힌트: 후자는 @ResponseBody 자동)
  • Validation 에러는 어떤 예외 타입? (힌트: MethodArgumentNotValidException)

Unit 6.3 — Bean Validation (@Valid)

선수 지식: Unit 6.2

핵심 개념

Bean Validation (JSR-380):

"어노테이션 기반 검증 — 자동화된 Validation"

기본 어노테이션:

어노테이션검증
@NotNullNULL 아님
@NotEmptyNULL 아님 + 비어있지 않음 (String/Collection)
@NotBlankNULL 아님 + 공백 아님 (String)
@Size(min, max)길이/크기 범위
@Min / @Max숫자 범위
@Pattern(regexp)정규표현식
@Email이메일 형식
@Past / @Future날짜

예시:

public record UserCreateRequest(
    @NotBlank(message = "이름은 필수입니다")
    @Size(min = 2, max = 50)
    String name,
    
    @NotBlank
    @Email
    String email,
    
    @Min(0) @Max(150)
    int age
) {}

Controller에서 사용 ⭐ :

@PostMapping("/users")
public ResponseEntity<User> create(
    @Valid @RequestBody UserCreateRequest request  // ← @Valid 필수
) {
    return ResponseEntity.ok(userService.create(request));
}

@Valid 가 없으면 검증 안 됨!

Validation 실패 시:

  • MethodArgumentNotValidException 발생
  • @RestControllerAdvice 에서 처리

중첩 검증 (@Valid 재귀):

public record OrderRequest(
    @NotNull
    @Valid  // 중첩 검증
    AddressDto address,
    
    @NotEmpty
    @Valid  // 리스트의 각 요소 검증
    List<OrderItemDto> items
) {}

커스텀 Validator (참고):

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
public @interface PhoneNumber {
    String message() default "유효하지 않은 전화번호";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

ILIC 활용:

  • 모든 DTO에 Validation 어노테이션
  • 표준 에러 응답으로 일관성

자기 점검

  • @Valid@Validated 의 차이는? (힌트: 후자는 그룹 지정 가능)
  • 폼 validation을 컨트롤러 외에 서비스 레이어에서도 해야 하는가? (힌트: 도메인 무결성은 항상 검증)

Unit 6.4 — 표준 에러 응답 설계

선수 지식: Unit 6.2~6.3

핵심 원칙

1. 일관된 구조:

  • 모든 에러가 같은 JSON 스키마
  • 클라이언트의 일관된 처리 가능

2. 에러 코드 + 메시지 분리:

  • 코드: 프로그래밍적 식별 (USER_NOT_FOUND)
  • 메시지: 사람이 읽을 설명

3. 민감 정보 노출 X:

  • 스택 트레이스 → 운영 환경에서 숨김
  • DB 에러 메시지 → 일반 메시지로 변환

전역 예외 처리 템플릿:

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    
    // 1. 비즈니스 예외 (사용자 입력 문제)
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusiness(BusinessException ex) {
        log.warn("Business error: {}", ex.getMessage());
        return ResponseEntity.status(ex.getStatus())
            .body(ErrorResponse.of(ex.getCode(), ex.getMessage()));
    }
    
    // 2. Validation 실패
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
        List<FieldError> errors = ex.getBindingResult().getFieldErrors().stream()
            .map(e -> new FieldError(e.getField(), e.getDefaultMessage()))
            .toList();
        return ResponseEntity.badRequest()
            .body(ErrorResponse.builder()
                .code("VALIDATION_FAILED")
                .message("입력값이 올바르지 않습니다")
                .errors(errors)
                .build());
    }
    
    // 3. 데이터 없음
    @ExceptionHandler({EntityNotFoundException.class, NoSuchElementException.class})
    public ResponseEntity<ErrorResponse> handleNotFound(Exception ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(ErrorResponse.of("NOT_FOUND", ex.getMessage()));
    }
    
    // 4. 모든 예외의 최종 처리 (Fallback)
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleAll(Exception ex) {
        log.error("Unexpected error", ex);
        return ResponseEntity.status(500)
            .body(ErrorResponse.of("INTERNAL_ERROR", "서버 오류가 발생했습니다"));
    }
}

ILIC 적용 권장:

  • 표준 ErrorResponse 클래스
  • BusinessException 계층 구조
  • 운영/개발 환경별 상세 정보 노출 차이

자기 점검

  • 운영 환경에서 스택 트레이스를 응답으로 노출하면? (힌트: 보안 사고)
  • 에러 코드를 만들 때 권장 패턴은? (힌트: 도메인_상태 — USER_NOT_FOUND)

📚 Phase 7 — REST API 설계와 실전

목표: 4년 차 풀스택 개발자가 알아야 할 REST API 설계 원칙과 페이징/문서화를 정리한다.

Unit 7.1 — RESTful 원칙

선수 지식: 6주차 Phase 7

핵심 6가지 원칙:

  1. Client-Server 분리: UI와 비즈니스 분리
  2. Stateless: 서버는 상태 저장 X
  3. Cacheable: 응답을 캐시 가능
  4. Uniform Interface: 일관된 인터페이스 ⭐
  5. Layered System: 계층 구조 (LB, CDN 등)
  6. Code-on-Demand: 클라이언트 코드 동적 다운로드 (Optional)

REST API URL 설계 원칙 ⭐ :

좋은 예나쁜 예
GET /usersGET /getUsers (동사 X)
GET /users/123GET /user?id=123
POST /usersPOST /createUser
DELETE /users/123POST /deleteUser/123
GET /users/123/ordersGET /userOrders?userId=123

핵심:

  • 명사 사용 (동사는 HTTP 메서드)
  • 복수형 권장 (/users)
  • 계층 구조 (/users/{id}/orders)
  • 소문자 + 하이픈 (/order-items)

자기 점검

  • ILIC의 어떤 API URL이 RESTful 원칙에 어긋날 수 있을까?
  • "Stateless"는 세션을 쓰면 안 된다는 뜻인가? (힌트: 서버에서 클라이언트 상태 추측 X)

Unit 7.2 — HTTP 메서드와 상태 코드

선수 지식: Unit 7.1, 4.3

핵심 매핑 ⭐ :

작업HTTP 메서드URL성공 코드
목록 조회GET/users200 OK
단건 조회GET/users/{id}200 OK
생성POST/users201 Created
전체 수정PUT/users/{id}200 또는 204 No Content
부분 수정PATCH/users/{id}200 또는 204
삭제DELETE/users/{id}204 No Content

Spring 어노테이션:

@GetMapping("/users")          // GET
@PostMapping("/users")         // POST
@PutMapping("/users/{id}")     // PUT
@PatchMapping("/users/{id}")   // PATCH
@DeleteMapping("/users/{id}")  // DELETE

PUT vs PATCH ⭐ :

  • PUT: 전체 교체 (모든 필드 보내야 함)
  • PATCH: 부분 수정 (변경 필드만)

예시 — PATCH의 효율성:

// PUT — 사용자 정보 전체
PUT /users/1
{
    "name": "Alice",
    "email": "alice@example.com",
    "age": 25,
    "address": "Seoul"
    // ... 모든 필드
}

// PATCH — 변경된 부분만
PATCH /users/1
{
    "email": "alice.new@example.com"
}

Idempotent (멱등성) ⭐ :

  • 같은 요청을 여러 번 보내도 결과가 같음
  • GET, PUT, DELETE → 멱등
  • POST → 비멱등 ❌ (호출마다 새 리소스 생성)

왜 중요한가:

  • 네트워크 재시도 시 안전
  • API 클라이언트의 신뢰성

자기 점검

  • PATCH에 멱등성이 보장되는가? (힌트: 구현에 따라 — 절대값 vs 증감)
  • POST를 여러 번 호출하면 어떤 사고? (힌트: 중복 생성 — Idempotency Key 패턴)

Unit 7.3 — 페이징 (Pageable, Page, Slice)

선수 지식: 11-12주차 Phase 9 (페이징 함정)

핵심 개념

Spring Data JPA의 페이징 추상화:

// Repository
public interface BookingRepository extends JpaRepository<Booking, Long> {
    Page<Booking> findByCustomerId(Long customerId, Pageable pageable);
}

// Controller
@GetMapping("/bookings")
public Page<Booking> list(
    @RequestParam Long customerId,
    Pageable pageable  // 자동 파라미터 바인딩
) {
    return bookingRepository.findByCustomerId(customerId, pageable);
}

요청 예시:

GET /bookings?customerId=1&page=0&size=20&sort=createdAt,desc

Pageable 자동 파싱:

  • page: 페이지 번호 (0부터)
  • size: 페이지 크기
  • sort: 정렬 (field,asc/desc)

Page vs Slice ⭐ :

PageSlice
전체 개수✅ (count 쿼리 추가)
다음 페이지 존재 여부있음 (전체로 계산)있음 (size+1 조회)
사용 사례페이지 번호 UI무한 스크롤
성능약간 느림 (count 추가)빠름

Slice 활용:

public interface BookingRepository extends JpaRepository<Booking, Long> {
    Slice<Booking> findByCustomerId(Long customerId, Pageable pageable);
}

→ 모바일 무한 스크롤에 적합


Cursor-based Pagination (참고):

  • offset 기반의 단점 (대용량 시 느림) 해결
  • 마지막 ID를 전달
GET /bookings?afterId=100&size=20

ILIC 시나리오:

  • 운임 검색 결과 → Page (전체 개수 표시)
  • 알림 목록 → Slice 또는 Cursor (무한 스크롤)

12주차 N+1 함정 주의 ⭐ :

  • 페이징 + fetch join + OneToMany → 메모리 페이징 위험
  • → @BatchSize 사용

자기 점검

  • 1000만 건 테이블에서 page=999, size=20 요청 시 어떤 문제? (힌트: OFFSET 비용)
  • ILIC의 운임 검색에서 Page를 쓰면 매번 COUNT 실행 — 어떻게 최적화? (힌트: 첫 페이지만 COUNT, 캐싱)

Unit 7.4 — API 문서화 (Swagger/SpringDoc)

선수 지식: 전체

핵심 도구

SpringDoc OpenAPI (Swagger 후속):

  • Spring Boot 3와 호환
  • 자동으로 OpenAPI 3 문서 생성
  • 인터랙티브 UI 제공

의존성:

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'

자동 생성:

  • http://localhost:8080/swagger-ui.html → UI
  • http://localhost:8080/v3/api-docs → JSON

어노테이션 추가 (선택):

@RestController
@Tag(name = "Booking", description = "예약 관리 API")
public class BookingController {
    
    @Operation(summary = "예약 생성", description = "새 운임 예약을 등록합니다")
    @ApiResponses({
        @ApiResponse(responseCode = "201", description = "성공"),
        @ApiResponse(responseCode = "400", description = "잘못된 요청")
    })
    @PostMapping("/bookings")
    public ResponseEntity<Booking> create(@RequestBody BookingRequest request) { ... }
}

좋은 API 문서의 요소:
1. 명확한 설명 (Operation, Parameter)
2. 요청/응답 예시
3. 에러 응답 예시
4. 인증 방법
5. 페이징 규칙


API 버저닝 전략:

방식
URL Path/api/v1/users, /api/v2/users
헤더X-API-Version: 2
쿼리 스트링/api/users?version=2

URL Path 방식이 가장 명확

ILIC 권장:

  • SpringDoc 도입 (자동 문서화)
  • v1 → v2 마이그레이션 시 URL 버저닝
  • Vue 3 팀과 공유할 명확한 문서 (Frontend 협업)

자기 점검

  • API 문서를 자동 생성과 수동 작성 중 무엇이 더 좋은가? (힌트: 자동 + 보강)
  • ILIC의 431 API 문서화가 안 되어 있다면 어떤 비용? (힌트: Frontend 팀 커뮤니케이션, 신규 개발자 온보딩)

🎓 종합 자기 점검 (15주차 졸업 시험)

DispatcherServlet 흐름

  1. DispatcherServlet 9단계를 순서대로 나열하라
  2. HandlerMapping과 HandlerAdapter의 역할 차이는?
  3. RequestMappingHandlerAdapter가 어노테이션을 어떻게 해석하는가?
  4. HandlerInterceptor의 3가지 메서드와 호출 시점은?

요청/응답 처리

  1. @RequestParam, @PathVariable, @RequestBody, @ModelAttribute의 차이는?
  2. @RequestBody가 동작하는 메커니즘 (HttpMessageConverter 흐름)은?
  3. @Controller와 @RestController의 차이를 코드로 설명하라
  4. ResponseEntity를 사용하는 이유 3가지는?

Filter vs Interceptor vs AOP (★)

  1. 세 가지의 위치와 차이를 다이어그램으로 그려라
  2. Spring Security가 Filter 기반인 이유는?
  3. URL 패턴 인증은 Filter/Interceptor 중 어디에? 메서드 권한은 Filter/Interceptor/AOP 중 어디에?
  4. 세 도구의 호출 순서는?

예외 처리와 Validation

  1. @ExceptionHandler와 @ControllerAdvice의 차이는?
  2. @Valid가 없으면 어떻게 되는가?
  3. 표준 에러 응답에 포함시켜야 할 5가지 필드는?
  4. MethodArgumentNotValidException은 언제 발생하는가?

REST API 설계

  1. RESTful URL 설계의 핵심 원칙 3가지는?
  2. PUT과 PATCH의 차이는?
  3. 멱등성(Idempotent)이란? GET/POST/PUT/DELETE 중 멱등인 것은?
  4. Page와 Slice의 차이와 각각의 사용 사례는?

면접 모의 답변

  1. "DispatcherServlet의 동작 원리를 설명해주세요" (3분)
  2. "Filter, Interceptor, AOP의 차이는?" (2분)
  3. "REST API 설계 시 가장 중요하다고 생각하는 것은?" (2분)

📌 학습 운영 팁

9-섹션 마스터 프롬프트로 깊이 파야 할 Unit

★★★ 면접 단골 (반드시):

  • Unit 2.1 — DispatcherServlet 9단계
  • Unit 5.4 — Filter vs Interceptor vs AOP 비교
  • Unit 6.2 — @ControllerAdvice
  • Unit 7.2 — HTTP 메서드와 상태 코드 + 멱등성

★★ 매우 권장:

  • Unit 2.3 — HandlerAdapter와 ArgumentResolver
  • Unit 3.2 — HttpMessageConverter
  • Unit 4.3 — ResponseEntity와 상태 코드
  • Unit 6.4 — 표준 에러 응답 설계

두 정점 — Phase 2와 Phase 5

Phase 2 (DispatcherServlet 9단계):

  • 모든 Spring Boot 개발자가 매일 사용하는 흐름
  • 그러나 9단계를 정확히 외우는 사람은 적음
  • 면접 차별화 포인트

Phase 5 (Filter/Interceptor/AOP 비교):

  • 8-9주차 AOP 학습의 진짜 의미를 보는 지점
  • 면접에서 거의 100% 출제
  • 실무 디버깅 능력의 척도

1~15주차 학습 여정의 마무리

이제 자바 백엔드 풀스택 개발자가 알아야 할 거의 모든 것 을 본 셈:

영역주차
Java 언어1-3주차
동시성4주차
Spring Core (IoC, AOP)5, 8-9주차
트랜잭션7, 10주차
JPA7, 11-12주차
데이터베이스13-14주차
Spring MVC + REST API15주차

다음 추천 주제 (자기 학습용):

  • Spring Security (인증/인가의 깊은 이해)
  • 테스트 심화 (MockMvc, Testcontainers, ArchUnit)
  • 분산 시스템 / 시스템 디자인 (Kafka, Redis, MSA)
  • Observability (Prometheus, Grafana, OpenTelemetry)
  • CI/CD 파이프라인 (GitHub Actions, ArgoCD)

학습 시 주의 — 디버거 활용

이번 주차는 디버거 step-through 가 가장 빠른 학습법입니다:

  1. Phase 2 — DispatcherServlet:

    • DispatcherServlet.doDispatch 에 브레이크포인트
    • 요청을 보내고 단계별로 따라가기
    • HandlerMapping → HandlerAdapter 흐름 직접 확인
  2. Phase 3 — HttpMessageConverter:

    • MappingJackson2HttpMessageConverter.read() 에 브레이크
    • JSON 파싱 과정 관찰
  3. Phase 5 — 호출 순서:

    • Filter, Interceptor, AOP에 모두 브레이크
    • 호출 순서가 정말 위에서 설명한 대로인지 확인

profile
Software Developer

0개의 댓글