<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Interceptor on kastori</title><link>http://blog.kastori.dev/tags/interceptor/</link><description>Recent content in Interceptor on kastori</description><generator>Hugo -- gohugo.io</generator><language>ko-kr</language><lastBuildDate>Tue, 19 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="http://blog.kastori.dev/tags/interceptor/index.xml" rel="self" type="application/rss+xml"/><item><title>[Spring 완전 정복 #3] Spring MVC 내부 구조 — DispatcherServlet이 요청을 처리하는 방법</title><link>http://blog.kastori.dev/tech/2026-05-19-spring-03-mvc-dispatcherservlet/</link><pubDate>Tue, 19 May 2026 00:00:00 +0000</pubDate><guid>http://blog.kastori.dev/tech/2026-05-19-spring-03-mvc-dispatcherservlet/</guid><description>&lt;h2 id="모든-요청이-거치는-하나의-관문"&gt;&lt;a href="#%eb%aa%a8%eb%93%a0-%ec%9a%94%ec%b2%ad%ec%9d%b4-%ea%b1%b0%ec%b9%98%eb%8a%94-%ed%95%98%eb%82%98%ec%9d%98-%ea%b4%80%eb%ac%b8" class="header-anchor"&gt;&lt;/a&gt;모든 요청이 거치는 하나의 관문
&lt;/h2&gt;&lt;p&gt;Spring MVC 애플리케이션에서 HTTP 요청은 예외 없이 &lt;code&gt;DispatcherServlet&lt;/code&gt;을 먼저 통과한다. URL이 &lt;code&gt;/users&lt;/code&gt;든 &lt;code&gt;/orders&lt;/code&gt;든 상관없다. 이 &amp;ldquo;하나의 관문&amp;quot;이 무엇이고, 내부에서 무슨 일이 일어나는지 이해하면 Filter, Interceptor, AOP 중 어디서 로직을 처리해야 하는지가 자연스럽게 보인다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="front-controller-패턴--왜-dispatcherservlet이-필요한가"&gt;&lt;a href="#front-controller-%ed%8c%a8%ed%84%b4--%ec%99%9c-dispatcherservlet%ec%9d%b4-%ed%95%84%ec%9a%94%ed%95%9c%ea%b0%80" class="header-anchor"&gt;&lt;/a&gt;Front Controller 패턴 — 왜 DispatcherServlet이 필요한가
&lt;/h2&gt;&lt;p&gt;Spring MVC 이전에는 URL마다 Servlet을 따로 만들었다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;/users → UserServlet
/orders → OrderServlet
/items → ItemServlet
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 방식의 문제는 공통 처리다. 인증 체크, 로깅, 인코딩 설정을 Servlet마다 중복해서 넣어야 했다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Front Controller 패턴&lt;/strong&gt;은 모든 요청을 하나의 진입점이 받아 적절한 핸들러에 위임한다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;모든 요청 (/*) → DispatcherServlet → Controller 위임
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;공통 로직을 &lt;code&gt;DispatcherServlet&lt;/code&gt; 한 곳에서 처리하니 중복이 사라진다. Spring Boot는 이 &lt;code&gt;DispatcherServlet&lt;/code&gt;을 자동으로 등록해 모든 URL(&lt;code&gt;/&lt;/code&gt;)을 받도록 설정한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="dispatcherservlet-내부-구성요소"&gt;&lt;a href="#dispatcherservlet-%eb%82%b4%eb%b6%80-%ea%b5%ac%ec%84%b1%ec%9a%94%ec%86%8c" class="header-anchor"&gt;&lt;/a&gt;DispatcherServlet 내부 구성요소
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;DispatcherServlet&lt;/code&gt;은 모든 일을 혼자 처리하지 않는다. 역할별 전문 컴포넌트에게 위임한다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;DispatcherServlet
 ├─ HandlerMapping : &amp;#34;이 URL → 어떤 Controller?&amp;#34;
 ├─ HandlerAdapter : &amp;#34;이 Controller를 어떻게 실행?&amp;#34;
 ├─ HandlerInterceptor : Controller 실행 전·후 부가 처리
 ├─ ViewResolver : &amp;#34;뷰 이름 → 실제 View 파일?&amp;#34;
 └─ HandlerExceptionResolver : &amp;#34;예외 발생 → 어떻게 응답?&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;HandlerMapping&lt;/strong&gt; — &lt;code&gt;@GetMapping(&amp;quot;/users/{id}&amp;quot;)&lt;/code&gt;처럼 선언된 매핑 정보를 읽어 요청 URL에 맞는 Controller 메서드를 찾는다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HandlerAdapter&lt;/strong&gt; — 찾은 Controller 메서드를 실제로 실행한다. &lt;code&gt;@RequestBody&lt;/code&gt;, &lt;code&gt;@PathVariable&lt;/code&gt;, &lt;code&gt;@ModelAttribute&lt;/code&gt; 파라미터 바인딩도 여기서 처리한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ViewResolver&lt;/strong&gt; — Controller가 문자열 뷰 이름을 반환할 때 실제 파일 경로로 변환한다. &lt;code&gt;@RestController&lt;/code&gt;를 쓰면 이 단계를 건너뛰고 &lt;code&gt;HttpMessageConverter&lt;/code&gt;가 JSON으로 직렬화한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HandlerExceptionResolver&lt;/strong&gt; — &lt;code&gt;@ControllerAdvice&lt;/code&gt; + &lt;code&gt;@ExceptionHandler&lt;/code&gt;가 이 메커니즘으로 동작한다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@RestControllerAdvice&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;GlobalExceptionHandler&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@ExceptionHandler&lt;/span&gt;(UserNotFoundException.&lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; ResponseEntity&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handle&lt;/span&gt;(UserNotFoundException e) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; ResponseEntity.&lt;span style="color:#a6e22e"&gt;status&lt;/span&gt;(404).&lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;(e.&lt;span style="color:#a6e22e"&gt;getMessage&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;단, Filter에서 발생한 예외는 &lt;code&gt;DispatcherServlet&lt;/code&gt; 밖이라 이 핸들러가 잡지 못한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="요청-처리-12단계-흐름"&gt;&lt;a href="#%ec%9a%94%ec%b2%ad-%ec%b2%98%eb%a6%ac-12%eb%8b%a8%ea%b3%84-%ed%9d%90%eb%a6%84" class="header-anchor"&gt;&lt;/a&gt;요청 처리 12단계 흐름
&lt;/h2&gt;&lt;p&gt;HTTP 요청이 응답까지 가는 전체 흐름이다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;① HTTP 요청 → Tomcat
② Filter Chain (전처리)
③ DispatcherServlet 진입
④ HandlerMapping → 처리할 Controller 메서드 조회
⑤ Interceptor.preHandle() — 순서대로 실행
 (false 반환 시 여기서 중단)
⑥ HandlerAdapter → Controller 메서드 실행
 (AOP 프록시를 통해 Before → 메서드 → After)
⑦ ModelAndView 반환
⑧ Interceptor.postHandle() — 역순으로 실행
⑨ ViewResolver → View 렌더링 (REST면 생략)
⑩ Interceptor.afterCompletion() — 역순, 예외 발생해도 항상 실행
⑪ Filter Chain (후처리, 역순)
⑫ HTTP 응답
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;postHandle()&lt;/code&gt;은 Controller에서 예외가 발생하면 실행되지 않는다. &lt;code&gt;afterCompletion()&lt;/code&gt;은 예외 여부와 무관하게 항상 실행된다. 그래서 실행 시간 측정이나 리소스 해제는 &lt;code&gt;afterCompletion()&lt;/code&gt;에 넣어야 한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="interceptor--spring-내부에서-요청을-제어하다"&gt;&lt;a href="#interceptor--spring-%eb%82%b4%eb%b6%80%ec%97%90%ec%84%9c-%ec%9a%94%ec%b2%ad%ec%9d%84-%ec%a0%9c%ec%96%b4%ed%95%98%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;Interceptor — Spring 내부에서 요청을 제어하다
&lt;/h2&gt;&lt;p&gt;Interceptor는 &lt;code&gt;DispatcherServlet&lt;/code&gt; 내부, Spring Context 안에서 동작한다. Spring Bean을 주입받을 수 있고, &lt;code&gt;@ControllerAdvice&lt;/code&gt; 예외 처리도 적용된다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@Component&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AuthInterceptor&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;implements&lt;/span&gt; HandlerInterceptor {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;boolean&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;preHandle&lt;/span&gt;(HttpServletRequest request,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HttpServletResponse response,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Object handler) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; String token &lt;span style="color:#f92672"&gt;=&lt;/span&gt; request.&lt;span style="color:#a6e22e"&gt;getHeader&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Authorization&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (token &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; response.&lt;span style="color:#a6e22e"&gt;setStatus&lt;/span&gt;(401);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;; &lt;span style="color:#75715e"&gt;// 여기서 중단, Controller까지 가지 않음&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;afterCompletion&lt;/span&gt;(HttpServletRequest request,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HttpServletResponse response,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Object handler, Exception ex) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 요청 처리 시간 로깅 — 예외가 나도 실행됨&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;URL 패턴으로 적용 범위를 지정할 수 있다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@Configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;WebConfig&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;implements&lt;/span&gt; WebMvcConfigurer {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Override&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;addInterceptors&lt;/span&gt;(InterceptorRegistry registry) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; registry.&lt;span style="color:#a6e22e"&gt;addInterceptor&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; AuthInterceptor())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;addPathPatterns&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/api/**&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;excludePathPatterns&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/api/login&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;/api/signup&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="filter-vs-interceptor-vs-aop--결론은-레이어-위치"&gt;&lt;a href="#filter-vs-interceptor-vs-aop--%ea%b2%b0%eb%a1%a0%ec%9d%80-%eb%a0%88%ec%9d%b4%ec%96%b4-%ec%9c%84%ec%b9%98" class="header-anchor"&gt;&lt;/a&gt;Filter vs Interceptor vs AOP — 결론은 레이어 위치
&lt;/h2&gt;&lt;p&gt;이 세 가지를 구분하는 가장 명확한 기준은 &lt;strong&gt;어느 레이어에 위치하는가&lt;/strong&gt;다.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;구분&lt;/th&gt;
 &lt;th&gt;위치&lt;/th&gt;
 &lt;th&gt;Spring Bean 접근&lt;/th&gt;
 &lt;th&gt;예외 처리&lt;/th&gt;
 &lt;th&gt;적합한 용도&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Filter&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Servlet Container (Spring 밖)&lt;/td&gt;
 &lt;td&gt;제한적&lt;/td&gt;
 &lt;td&gt;직접 처리&lt;/td&gt;
 &lt;td&gt;CORS, 인코딩, XSS 방어&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Interceptor&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Spring MVC (DispatcherServlet 내)&lt;/td&gt;
 &lt;td&gt;가능&lt;/td&gt;
 &lt;td&gt;@ControllerAdvice&lt;/td&gt;
 &lt;td&gt;로그인 체크, URL별 접근 제어&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;AOP&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Spring Bean&lt;/td&gt;
 &lt;td&gt;가능&lt;/td&gt;
 &lt;td&gt;@ControllerAdvice&lt;/td&gt;
 &lt;td&gt;트랜잭션, 실행 시간 측정, 메서드 로깅&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Filter는 Spring Context가 시작되기 전에 동작하므로 Spring Bean 주입이 어렵고, 예외가 발생해도 &lt;code&gt;@ControllerAdvice&lt;/code&gt;가 잡지 못한다. 반면 Interceptor와 AOP는 Spring 안에서 동작하므로 Bean 주입과 통합 예외 처리가 모두 가능하다.&lt;/p&gt;
&lt;p&gt;Spring Security의 필터체인이 Filter 레이어에 있는 것도 이 때문이다. 인증되지 않은 요청은 &lt;code&gt;DispatcherServlet&lt;/code&gt;까지 도달하지 못하고 Filter에서 차단된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="입력값-검증--controller-진입-전-자동-검증"&gt;&lt;a href="#%ec%9e%85%eb%a0%a5%ea%b0%92-%ea%b2%80%ec%a6%9d--controller-%ec%a7%84%ec%9e%85-%ec%a0%84-%ec%9e%90%eb%8f%99-%ea%b2%80%ec%a6%9d" class="header-anchor"&gt;&lt;/a&gt;입력값 검증 — Controller 진입 전 자동 검증
&lt;/h2&gt;&lt;p&gt;Spring MVC는 &lt;code&gt;@Valid&lt;/code&gt;로 Controller 파라미터를 자동 검증한다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CreateUserRequest&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@NotBlank&lt;/span&gt;(message &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;이름은 필수입니다&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; String name;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Email&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; String email;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Min&lt;/span&gt;(18)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; age;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@RestController&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;UserController&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@PostMapping&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/api/users&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; ResponseEntity&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;UserDto&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;create&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Valid&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;@RequestBody&lt;/span&gt; CreateUserRequest request) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 여기까지 오면 request는 유효한 상태&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; ResponseEntity.&lt;span style="color:#a6e22e"&gt;ok&lt;/span&gt;(userService.&lt;span style="color:#a6e22e"&gt;create&lt;/span&gt;(request));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;검증 실패 시 &lt;code&gt;MethodArgumentNotValidException&lt;/code&gt;이 발생한다. &lt;code&gt;@ControllerAdvice&lt;/code&gt;에서 공통 처리하면 된다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@RestControllerAdvice&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;GlobalExceptionHandler&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@ExceptionHandler&lt;/span&gt;(MethodArgumentNotValidException.&lt;span style="color:#a6e22e"&gt;class&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; ResponseEntity&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;ErrorResponse&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handleValidation&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MethodArgumentNotValidException e) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; List&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;String&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; errors &lt;span style="color:#f92672"&gt;=&lt;/span&gt; e.&lt;span style="color:#a6e22e"&gt;getBindingResult&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;getFieldErrors&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;stream&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;map&lt;/span&gt;(err &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; err.&lt;span style="color:#a6e22e"&gt;getField&lt;/span&gt;() &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;: &amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; err.&lt;span style="color:#a6e22e"&gt;getDefaultMessage&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;toList&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; ResponseEntity.&lt;span style="color:#a6e22e"&gt;badRequest&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ErrorResponse(&lt;span style="color:#e6db74"&gt;&amp;#34;VALIDATION_FAILED&amp;#34;&lt;/span&gt;, errors));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="마치며"&gt;&lt;a href="#%eb%a7%88%ec%b9%98%eb%a9%b0" class="header-anchor"&gt;&lt;/a&gt;마치며
&lt;/h2&gt;&lt;p&gt;DispatcherServlet은 Spring MVC의 모든 요청 처리를 조율하는 중앙 컨트롤러다. 이 구조를 이해하면 Filter와 Interceptor의 차이, &lt;code&gt;@ControllerAdvice&lt;/code&gt;가 Filter 예외를 잡지 못하는 이유, Interceptor 세 메서드의 실행 조건 등이 자연스럽게 설명된다.&lt;/p&gt;
&lt;p&gt;다음 편에서는 Spring Boot — 설정 없이 바로 실행되는 원리(자동설정, 스타터, 내장 서버)를 정리한다.&lt;/p&gt;</description></item></channel></rss>