<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Aop on kastori</title><link>http://blog.kastori.dev/tags/aop/</link><description>Recent content in Aop 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/aop/index.xml" rel="self" type="application/rss+xml"/><item><title>[Spring 완전 정복 #2] Spring Core — IoC, DI, AOP를 코드로 이해하기</title><link>http://blog.kastori.dev/tech/2026-05-19-spring-02-core-ioc-di-aop/</link><pubDate>Tue, 19 May 2026 00:00:00 +0000</pubDate><guid>http://blog.kastori.dev/tech/2026-05-19-spring-02-core-ioc-di-aop/</guid><description>&lt;h2 id="service만-붙이면-왜-되는-걸까"&gt;&lt;a href="#service%eb%a7%8c-%eb%b6%99%ec%9d%b4%eb%a9%b4-%ec%99%9c-%eb%90%98%eb%8a%94-%ea%b1%b8%ea%b9%8c" class="header-anchor"&gt;&lt;/a&gt;&amp;ldquo;@Service만 붙이면 왜 되는 걸까?&amp;rdquo;
&lt;/h2&gt;&lt;p&gt;Spring을 처음 쓸 때 누구나 한 번쯤 갖는 의문이다. 클래스에 어노테이션 하나 달았을 뿐인데 의존성이 주입되고, &lt;code&gt;@Transactional&lt;/code&gt;만 붙이면 트랜잭션이 처리된다.&lt;/p&gt;
&lt;p&gt;이게 &amp;ldquo;마법&amp;quot;처럼 느껴지는 건, 내부 동작을 모르기 때문이다. 이 글에서는 Spring의 핵심 메커니즘 세 가지 — &lt;strong&gt;IoC, DI, AOP&lt;/strong&gt; — 를 코드 수준에서 풀어낸다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="어노테이션은-라벨일-뿐이다"&gt;&lt;a href="#%ec%96%b4%eb%85%b8%ed%85%8c%ec%9d%b4%ec%85%98%ec%9d%80-%eb%9d%bc%eb%b2%a8%ec%9d%bc-%eb%bf%90%ec%9d%b4%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;어노테이션은 라벨일 뿐이다
&lt;/h2&gt;&lt;p&gt;먼저 오해를 하나 짚고 시작한다. &lt;code&gt;@Service&lt;/code&gt;, &lt;code&gt;@Autowired&lt;/code&gt; 같은 어노테이션은 &lt;strong&gt;그 자체로 아무 일도 하지 않는다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Java 어노테이션은 코드에 붙이는 메타데이터다. Spring Container가 없다면 &lt;code&gt;@Service&lt;/code&gt;를 붙여도 그냥 라벨이 붙은 일반 클래스일 뿐이다.&lt;/p&gt;
&lt;p&gt;Spring이 하는 일은 애플리케이션 시작 시 리플렉션으로 클래스를 스캔해서 어노테이션을 읽고, 그에 따라 동작을 결정하는 것이다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;애플리케이션 시작
 → @ComponentScan이 지정 패키지 하위를 전부 탐색
 → 각 클래스를 리플렉션으로 읽음
 → @Service/@Repository 발견 → &amp;#34;Bean으로 등록&amp;#34;
 → @Autowired 발견 → &amp;#34;등록된 Bean 중 타입 맞는 것을 주입&amp;#34;
 → @Transactional 발견 → &amp;#34;CGLIB 프록시로 감싸기&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Spring이 이 과정을 대신 처리해준다. 개발자가 직접 구현하면 아래처럼 된다.&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:#75715e"&gt;// Spring 내부에서 실제로 일어나는 일 (단순화)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (Class&lt;span style="color:#f92672"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; clazz : scannedClasses) {
&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; (clazz.&lt;span style="color:#a6e22e"&gt;isAnnotationPresent&lt;/span&gt;(Component.&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; Object instance &lt;span style="color:#f92672"&gt;=&lt;/span&gt; clazz.&lt;span style="color:#a6e22e"&gt;getDeclaredConstructor&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;newInstance&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; beanContainer.&lt;span style="color:#a6e22e"&gt;register&lt;/span&gt;(clazz, instance);
&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;이걸 자동으로 해주는 것이 Spring의 핵심이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="ioc--제어권을-spring에게-넘기다"&gt;&lt;a href="#ioc--%ec%a0%9c%ec%96%b4%ea%b6%8c%ec%9d%84-spring%ec%97%90%ea%b2%8c-%eb%84%98%ea%b8%b0%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;IoC — 제어권을 Spring에게 넘기다
&lt;/h2&gt;&lt;p&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:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OrderService&lt;/span&gt; {
&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 style="color:#66d9ef"&gt;private&lt;/span&gt; PaymentService paymentService &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; PaymentService();
&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;OrderService&lt;/code&gt;가 &lt;code&gt;PaymentService&lt;/code&gt;의 구체적인 구현에 묶여있다는 점이다. &lt;code&gt;PaymentService&lt;/code&gt; 구현을 &lt;code&gt;KakaopayService&lt;/code&gt;로 바꾸려면 &lt;code&gt;OrderService&lt;/code&gt;도 수정해야 한다.&lt;/p&gt;
&lt;p&gt;**IoC(Inversion of Control, 제어의 역전)**는 이 제어권을 개발자에서 Spring Container로 넘기는 것이다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;전통적: 개발자 → 객체 생성 → 의존성 연결
IoC: Spring Container → 객체 생성 → 의존성 연결 → 개발자에게 제공
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;개발자는 &amp;ldquo;무엇이 필요한지&amp;quot;만 선언하면 된다. &amp;ldquo;어떻게 만들지&amp;quot;는 Spring이 결정한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="di--ioc를-구현하는-방법"&gt;&lt;a href="#di--ioc%eb%a5%bc-%ea%b5%ac%ed%98%84%ed%95%98%eb%8a%94-%eb%b0%a9%eb%b2%95" class="header-anchor"&gt;&lt;/a&gt;DI — IoC를 구현하는 방법
&lt;/h2&gt;&lt;p&gt;DI(Dependency Injection, 의존성 주입)는 IoC를 실현하는 구체적인 방법이다. Spring이 의존성을 주입하는 방식은 세 가지가 있다.&lt;/p&gt;
&lt;h3 id="생성자-주입-권장"&gt;&lt;a href="#%ec%83%9d%ec%84%b1%ec%9e%90-%ec%a3%bc%ec%9e%85-%ea%b6%8c%ec%9e%a5" class="header-anchor"&gt;&lt;/a&gt;생성자 주입 (권장)
&lt;/h3&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;@Service&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;OrderService&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; &lt;span style="color:#66d9ef"&gt;final&lt;/span&gt; PaymentService paymentService;
&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;@Autowired&lt;/span&gt; &lt;span style="color:#75715e"&gt;// 생성자가 1개면 생략 가능&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:#a6e22e"&gt;OrderService&lt;/span&gt;(PaymentService paymentService) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;paymentService&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; paymentService;
&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;h3 id="필드-주입-비권장"&gt;&lt;a href="#%ed%95%84%eb%93%9c-%ec%a3%bc%ec%9e%85-%eb%b9%84%ea%b6%8c%ec%9e%a5" class="header-anchor"&gt;&lt;/a&gt;필드 주입 (비권장)
&lt;/h3&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;@Service&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;OrderService&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Autowired&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; PaymentService paymentService; &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;/code&gt;&lt;/pre&gt;&lt;/div&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;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;불변성&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;final&lt;/code&gt; 선언 가능 — 런타임 중 변경 불가&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;필수 의존성 명시&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;컴파일 시점에 의존성 누락 감지&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;테스트 용이&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;new OrderService(mockPaymentService)&lt;/code&gt;로 직접 주입 가능&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;순환 의존성 감지&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;앱 시작 시 즉시 오류 발생&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;필드 주입의 치명적 단점은 테스트다. Spring Container 없이 &lt;code&gt;new OrderService()&lt;/code&gt;를 하면 &lt;code&gt;paymentService&lt;/code&gt;가 null이다. 반면 생성자 주입은 테스트 코드에서도 의존성을 명확히 넘길 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="bean--spring이-관리하는-객체"&gt;&lt;a href="#bean--spring%ec%9d%b4-%ea%b4%80%eb%a6%ac%ed%95%98%eb%8a%94-%ea%b0%9d%ec%b2%b4" class="header-anchor"&gt;&lt;/a&gt;Bean — Spring이 관리하는 객체
&lt;/h2&gt;&lt;p&gt;Bean은 Spring Container가 생성하고 관리하는 객체다. 개발자가 &lt;code&gt;new&lt;/code&gt;로 직접 생성하지 않는다.&lt;/p&gt;
&lt;h3 id="등록-방법"&gt;&lt;a href="#%eb%93%b1%eb%a1%9d-%eb%b0%a9%eb%b2%95" class="header-anchor"&gt;&lt;/a&gt;등록 방법
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;컴포넌트 스캔&lt;/strong&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 style="color:#75715e"&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;@Service&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 style="color:#a6e22e"&gt;@Repository&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 style="color:#a6e22e"&gt;@Controller&lt;/span&gt; &lt;span style="color:#75715e"&gt;// Spring MVC 컨트롤러&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;자바 설정&lt;/strong&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;@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;AppConfig&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Bean&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; PaymentService &lt;span style="color:#a6e22e"&gt;paymentService&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;new&lt;/span&gt; KakaopayService(); &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;h3 id="같은-타입-bean이-여러-개일-때"&gt;&lt;a href="#%ea%b0%99%ec%9d%80-%ed%83%80%ec%9e%85-bean%ec%9d%b4-%ec%97%ac%eb%9f%ac-%ea%b0%9c%ec%9d%bc-%eb%95%8c" class="header-anchor"&gt;&lt;/a&gt;같은 타입 Bean이 여러 개일 때
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;PaymentService&lt;/code&gt; 구현체가 &lt;code&gt;KakaopayService&lt;/code&gt;, &lt;code&gt;NaverPayService&lt;/code&gt; 두 개라면 &lt;code&gt;@Autowired&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:#75715e"&gt;// @Primary — 기본 Bean 지정&lt;/span&gt;
&lt;/span&gt;&lt;/span&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:#a6e22e"&gt;@Primary&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;KakaopayService&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;implements&lt;/span&gt; PaymentService { ... }
&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:#75715e"&gt;// @Qualifier — 이름으로 명시적 선택&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@Autowired&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@Qualifier&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;naverpay&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; PaymentService paymentService;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="singleton-bean의-함정"&gt;&lt;a href="#singleton-bean%ec%9d%98-%ed%95%a8%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;Singleton Bean의 함정
&lt;/h3&gt;&lt;p&gt;Spring Bean의 기본 스코프는 &lt;strong&gt;Singleton&lt;/strong&gt;이다. Container당 인스턴스가 1개이고, 모든 요청에서 이 인스턴스를 공유한다.&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;@Service&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;OrderService&lt;/span&gt; {
&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 style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; orderCount &lt;span style="color:#f92672"&gt;=&lt;/span&gt; 0;
&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;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;createOrder&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; orderCount&lt;span style="color:#f92672"&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;Singleton Bean은 **무상태(stateless)**로 설계해야 한다. 인스턴스 변수에 요청별 데이터를 저장하면 안 된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="aop--비즈니스-로직에서-공통-코드를-분리하다"&gt;&lt;a href="#aop--%eb%b9%84%ec%a6%88%eb%8b%88%ec%8a%a4-%eb%a1%9c%ec%a7%81%ec%97%90%ec%84%9c-%ea%b3%b5%ed%86%b5-%ec%bd%94%eb%93%9c%eb%a5%bc-%eb%b6%84%eb%a6%ac%ed%95%98%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;AOP — 비즈니스 로직에서 공통 코드를 분리하다
&lt;/h2&gt;&lt;p&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:#75715e"&gt;// AOP 없이 — 모든 메서드마다 반복&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; Order &lt;span style="color:#a6e22e"&gt;createOrder&lt;/span&gt;(...) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; log.&lt;span style="color:#a6e22e"&gt;info&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;createOrder 시작&amp;#34;&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; checkAuth(); &lt;span style="color:#75715e"&gt;// 인증&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; startTransaction(); &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; Order order &lt;span style="color:#f92672"&gt;=&lt;/span&gt; doCreateOrder(); &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; commitTransaction();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; log.&lt;span style="color:#a6e22e"&gt;info&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;createOrder 완료&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;return&lt;/span&gt; order;
&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;AOP는 이 **횡단 관심사(Cross-cutting Concerns)**를 별도 모듈로 분리해서, 비즈니스 코드가 핵심 로직에만 집중하게 한다.&lt;/p&gt;
&lt;h3 id="프록시가-핵심이다"&gt;&lt;a href="#%ed%94%84%eb%a1%9d%ec%8b%9c%ea%b0%80-%ed%95%b5%ec%8b%ac%ec%9d%b4%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;프록시가 핵심이다
&lt;/h3&gt;&lt;p&gt;Spring AOP는 &lt;strong&gt;프록시 패턴&lt;/strong&gt;으로 동작한다. 실제 Bean을 직접 주입하는 대신, Bean을 감싼 프록시 객체를 주입한다. 메서드 호출이 들어오면 프록시가 먼저 받아서 부가 로직을 실행하고, 실제 Bean으로 넘긴다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;호출자 → [프록시] → 실제 Bean
 ↑
 Advice(부가 로직) 실행
&lt;/code&gt;&lt;/pre&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;@Aspect&lt;/span&gt;
&lt;/span&gt;&lt;/span&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;LoggingAspect&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:#75715e"&gt;// service 패키지의 모든 메서드 실행 전후&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Around&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;execution(* com.example.service.*.*(..))&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; Object &lt;span style="color:#a6e22e"&gt;around&lt;/span&gt;(ProceedingJoinPoint pjp) &lt;span style="color:#66d9ef"&gt;throws&lt;/span&gt; Throwable {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;long&lt;/span&gt; start &lt;span style="color:#f92672"&gt;=&lt;/span&gt; System.&lt;span style="color:#a6e22e"&gt;currentTimeMillis&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; Object result &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pjp.&lt;span style="color:#a6e22e"&gt;proceed&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; log.&lt;span style="color:#a6e22e"&gt;info&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;{} — {}ms&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pjp.&lt;span style="color:#a6e22e"&gt;getSignature&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;getName&lt;/span&gt;(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; System.&lt;span style="color:#a6e22e"&gt;currentTimeMillis&lt;/span&gt;() &lt;span style="color:#f92672"&gt;-&lt;/span&gt; start);
&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; result;
&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;h3 id="transactional도-aop다"&gt;&lt;a href="#transactional%eb%8f%84-aop%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;@Transactional도 AOP다
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;@Transactional&lt;/code&gt;이 붙은 Bean에는 &lt;strong&gt;CGLIB 프록시&lt;/strong&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;@Service&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;OrderService&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Transactional&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; Order &lt;span style="color:#a6e22e"&gt;createOrder&lt;/span&gt;(...) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 트랜잭션 begin → 프록시가 처리&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Order order &lt;span style="color:#f92672"&gt;=&lt;/span&gt; orderRepository.&lt;span style="color:#a6e22e"&gt;save&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; order;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 트랜잭션 commit → 프록시가 처리&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;여기서 하나의 함정이 있다. &lt;strong&gt;같은 클래스 내부에서 &lt;code&gt;@Transactional&lt;/code&gt; 메서드를 직접 호출하면 트랜잭션이 적용되지 않는다.&lt;/strong&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;@Service&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;OrderService&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;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;process&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; createOrder(); &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;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Transactional&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;createOrder&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;this.createOrder()&lt;/code&gt;와 같아서 프록시를 건너뛴다.&lt;/p&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;Spring의 핵심은 결국 세 가지다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IoC&lt;/strong&gt;: 객체 생성·의존성 연결의 제어권을 Spring에게 넘긴다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DI&lt;/strong&gt;: Spring이 필요한 의존성을 주입해준다. 생성자 주입을 쓰자.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AOP&lt;/strong&gt;: 횡단 관심사를 프록시로 분리한다. &lt;code&gt;@Transactional&lt;/code&gt;이 대표적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 구조를 이해하면 &lt;code&gt;@Transactional&lt;/code&gt; 자기 호출 버그나 Singleton Bean 상태 문제 같은 Spring의 흔한 함정을 미리 피할 수 있다.&lt;/p&gt;
&lt;p&gt;다음 편에서는 Spring MVC — DispatcherServlet 내부 구조와 요청 처리 흐름을 정리한다.&lt;/p&gt;</description></item><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>