<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>limdaeil</title>
    <link>https://limdaeil.tistory.com/</link>
    <description>limdaeil 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Wed, 29 Jul 2026 20:20:09 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>limdaeil</managingEditor>
    <image>
      <title>limdaeil</title>
      <url>https://tistory1.daumcdn.net/tistory/8212046/attach/f49877d0dc9340f0896f00928f497af5</url>
      <link>https://limdaeil.tistory.com</link>
    </image>
    <item>
      <title>Spring: Part 5. Spring Configuration</title>
      <link>https://limdaeil.tistory.com/88</link>
      <description>&lt;h2 data-path-to-node=&quot;0&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Part&amp;nbsp;5.&amp;nbsp;Spring&amp;nbsp;Configuration&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;0&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5.1 Java Config와 XML Config&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;앞에서 Spring은 Bean을 생성하고 관리하며, Scope를 통해 Bean의 생명주기를 제어한다는 것을 살펴보았습니다. 그렇다면 Spring은 이러한 Bean 정보를 어디에 정의할까요? &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring은 Bean을 생성하고 관리하기 위해 설정(Configuration)이 필요합니다. 초기 Spring 프로젝트에서는 XML을 통해 Bean을 등록했지만, 현재는 Java 기반의 설정(&lt;b data-index-in-node=&quot;108&quot; data-path-to-node=&quot;2&quot;&gt;Java Config&lt;/b&gt;)이 표준으로 사용됩니다. 이번 장에서는 XML Config와 Java Config의 차이점을 살펴보고, Spring Boot가 Java Config를 기본으로 사용하는 이유를 알아봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. Spring Configuration이란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5&quot;&gt;Spring Configuration&lt;/b&gt;은 Spring 컨테이너에 어떤 Bean을 등록하고 어떻게 생성 및 주입할 것인지를 정의하는 설정 정보를 의미합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Configuration은 컨테이너에 다음과 같은 질문에 관한 답을 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;어떤 객체를 Bean으로 등록할 것인가?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체는 어떤 과정과 생성자로 생성할 것인가?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체 간 의존성(Dependency)은 어떻게 연결할 것인가?&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. XML Config와 Java Config 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1) XML Config&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 초기 방식으로, XML 파일 내부에 `&amp;lt;beans&amp;gt;` 및 `&amp;lt;bean&amp;gt;` 태그를 이용해 Bean의 이름, 클래스, 의존성을 직접 명시합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQgQQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;beans&amp;gt;
    &amp;lt;bean id=&quot;memberRepository&quot; class=&quot;com.example.MemberRepository&quot;/&amp;gt;
    &amp;lt;bean id=&quot;memberService&quot; class=&quot;com.example.MemberService&quot;&amp;gt;
        &amp;lt;constructor-arg ref=&quot;memberRepository&quot;/&amp;gt;
    &amp;lt;/bean&amp;gt;
&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,0,0&quot;&gt;장점:&lt;/b&gt; 코드 수정 없이 XML 변경만으로 설정 수정 가능, 비즈니스 코드와 설정 코드의 명확한 분리.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,1,0&quot;&gt;단점:&lt;/b&gt; 프로젝트 규모 증가 시 XML 파일 비대화, &lt;b data-index-in-node=&quot;29&quot; data-path-to-node=&quot;13,1,0&quot;&gt;타입 안전성 부족&lt;/b&gt;(문자열 기반 클래스 지정), 컴파일 시점 오타 감지 불가능, 자동 완성/리팩터링 등 IDE 지원 제한.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2) Java Config&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Java 클래스에 @Configuration과 @Bean 어노테이션을 사용하여 Bean을 등록하는 최신 표준 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQggQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class AppConfig {

    @Bean
    public MemberRepository memberRepository() {
        return new MemberRepository();
    }

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,0,0&quot;&gt;장점:&lt;/b&gt; &lt;b data-index-in-node=&quot;4&quot; data-path-to-node=&quot;17,0,0&quot;&gt;타입 안전성&lt;/b&gt;(컴파일 시점에 오류 확인), 뛰어난 IDE 지원(자동 완성, 안전한 리팩터링, 코드 이동), 메서드/조건문/반복문 활용 등 &lt;b data-index-in-node=&quot;80&quot; data-path-to-node=&quot;17,0,0&quot;&gt;객체지향적인 설정&lt;/b&gt; 가능.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. XML vs Java Config 종합 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;20&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;XML Config&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Java Config&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;20,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,1,0,0&quot;&gt;설정 방식&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;20,1,1,0&quot;&gt;XML 문서 파일&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;20,1,2,0&quot;&gt;Java 코드 + 어노테이션&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;20,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,2,0,0&quot;&gt;타입 안전성&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;20,2,1,0&quot;&gt;낮음 (문자열 기반)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;20,2,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,2,2,0&quot;&gt;높음&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;20,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,3,0,0&quot;&gt;컴파일 타임 검사&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;20,3,1,0&quot;&gt;불가능 (런타임에 오류 발견)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;20,3,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,3,2,0&quot;&gt;가능&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;20,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,4,0,0&quot;&gt;IDE 지원 (리팩터링/자동완성)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;20,4,1,0&quot;&gt;제한적&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;20,4,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,4,2,0&quot;&gt;우수&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;20,5,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,5,0,0&quot;&gt;현재 사용 빈도&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;20,5,1,0&quot;&gt;매우 낮음&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;20,5,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,5,2,0&quot;&gt;매우 높음 (표준)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. Spring Boot와 실무에서의 활용&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Boot가 Java Config를 사용하는 이유&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Boot는 Java Config, Component Scan, Auto Configuration(자동 설정)을 기본으로 설계되었습니다. 별도의 XML 파일 작성 없이 메인 클래스의 @SpringBootApplication 어노테이션 하나로 필요한 수많은 Bean을 자동으로 탐색하고 등록합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실무에서의 Bean 등록 패턴&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실무에서는 다음과 같은 조합을 주로 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;28&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;28,0,0&quot;&gt;대부분의 일반 Bean:&lt;/b&gt; @Component, @Service, @Repository, @Controller 등을 활용한 &lt;b data-index-in-node=&quot;68&quot; data-path-to-node=&quot;28,0,0&quot;&gt;Component Scan&lt;/b&gt;으로 자동 등록합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;28,1,0&quot;&gt;외부 라이브러리나 특별한 Bean:&lt;/b&gt; @Configuration 클래스 내부에서 @Bean 메서드로 직접 등록합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;28,2,0&quot;&gt;XML의 위치:&lt;/b&gt; 레거시(Spring Legacy) 프로젝트나 오래된 외부 라이브러리 유지보수 목적 외에 &lt;b data-index-in-node=&quot;58&quot; data-path-to-node=&quot;28,2,0&quot;&gt;신규 프로젝트에서는 XML을 거의 작성하지 않습니다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;31&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,0,0&quot;&gt;Spring Configuration:&lt;/b&gt; 컨테이너가 Bean을 생성&amp;middot;관리하기 위해 필요한 설정 정보입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,1,0&quot;&gt;Java Config의 우수성:&lt;/b&gt; 초기의 XML 방식과 달리 컴파일 시점의 타입 검사가 가능하며, IDE 자동 완성 및 안전한 리팩터링을 제공합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,2,0&quot;&gt;Spring Boot 표준:&lt;/b&gt; Java Config, Component Scan, Auto Configuration을 기반으로 구동됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 장에서는 Java Config의 핵심 어노테이션인 &lt;b data-index-in-node=&quot;31&quot; data-path-to-node=&quot;32&quot;&gt;@Configuration이 실제로 어떤 역할을 수행하는지&lt;/b&gt;와 싱글톤을 보장하는 내부 동작 원리(CGLIB Proxy)에 대해 자세히 살펴보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;35&quot;&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;35&quot;&gt;Q1. Spring Configuration이란 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Spring 컨테이너에 어떤 Bean을 등록하고 어떻게 생성 및 의존관계를 주입할 것인지를 정의하는 설정 정보입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37&quot;&gt;Q2. XML Config와 Java Config의 가장 큰 차이점은 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;38&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;XML Config는 XML 파일의 문자열 기반으로 Bean을 정의하여 컴파일 시점 오류 검증이 어렵지만, Java Config는 Java 코드를 활용하므로 타입 안전성과 IDE의 리팩터링/자동 완성 지원이 뛰어납니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;39&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;39&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;39&quot;&gt;Q3. 현재 Spring Boot에서 기본으로 사용하는 설정 방식은 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;40&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Java Config를 기본으로 사용하며, Component Scan과 Auto Configuration을 결합하여 Bean을 자동으로 탐색하고 등록합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;41&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;41&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41&quot;&gt;Q4. XML Config를 여전히 알아야 하는 이유는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;42&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;신규 프로젝트에는 거의 쓰이지 않지만, 레거시 시스템 유지보수나 오래된 Spring 기반 모듈 및 라이브러리 설정을 다룰 때 접할 수 있기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;0&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5.2 @Configuration의 동작 원리&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;앞에서 Java Config는 Java 코드를 이용하여 Spring Bean을 등록하는 방식이라는 것을 살펴보았습니다. Java Config의 중심에는 @Configuration과 @Bean이 있습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그렇다면 @Configuration은 단순히 설정 클래스임을 표시하는 어노테이션일까요? 실제로는 그보다 훨씬 중요한 역할을 수행합니다. 이번 장에서는 @Configuration의 역할과 내부 동작 원리를 살펴보고, 왜 반드시 필요한지 알아봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. @Configuration이란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Configuration은 &lt;b&gt;Spring의 설정 클래스(Configuration Class)임을 나타내는 어노테이션입니다.&lt;/b&gt; &lt;br /&gt;Spring은 이 클래스를 읽어 &lt;b&gt;Bean을 생성하고 컨테이너에 등록&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQqgQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class AppConfig {

    @Bean
    public MemberRepository memberRepository() {
        return new MemberRepository();
    }

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring은 AppConfig를 일반 클래스가 아닌 설정 클래스로 인식하며, 각 @Bean 메서드를 실행하여 반환된 객체를 Spring 컨테이너에 등록합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. @Configuration이 없다면? (Singleton 파괴)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;만약 다음과 같이 @Configuration을 제거하고 일반 클래스처럼 작성하면 어떻게 될까요?&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQqwQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;// @Configuration 없음!
public class AppConfig {

    @Bean
    public MemberRepository memberRepository() {
        return new MemberRepository();
    }

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean
    public OrderService orderService() {
        return new OrderService(memberRepository());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;문제점: 싱글톤(Singleton)이 깨짐&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;겉으로 보기에는 하나의 MemberRepository를 공유할 것처럼 보입니다. &lt;br /&gt;하지만 @Configuration이 없으면 해당 클래스는 일반 Java 클래스로 동작합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;memberService() 호출 ➔ memberRepository() 실행 ➔ &lt;b data-index-in-node=&quot;45&quot; data-path-to-node=&quot;14,0,0&quot;&gt;새 객체 A 생성&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;orderService() 호출 ➔ memberRepository() 실행 ➔ &lt;b data-index-in-node=&quot;44&quot; data-path-to-node=&quot;14,1,0&quot;&gt;새 객체 B 생성&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;즉, MemberRepository가 호출될 때마다 새로운 객체가 생성되어 &lt;b data-index-in-node=&quot;42&quot; data-path-to-node=&quot;15&quot;&gt;Spring의 핵심 원칙인 Singleton이 깨지는 문제&lt;/b&gt;가 발생합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. Spring의 해결책: 바이트코드 조작 프록시 (CGLIB Proxy)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Configuration이 붙으면 Spring은 설정 클래스를 그대로 사용하지 않고, &lt;br /&gt;&lt;b data-index-in-node=&quot;49&quot; data-path-to-node=&quot;18&quot;&gt;CGLIB 바이트코드 조작 라이브러리&lt;/b&gt;를 사용하여 설정 클래스를 상속받은 프록시 객체(Proxy Object)를 생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;프록시의 @Bean 메서드 호출 가로채기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;프록시 객체는 @Bean 메서드 호출을 가로채서 다음과 같이 동작합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;22&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,0,0&quot;&gt;컨테이너 조회:&lt;/b&gt; @Bean 메서드(예: memberRepository())가 호출되면 먼저 Spring 컨테이너에 해당 Bean이 이미 등록되어 있는지 확인합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,1,0&quot;&gt;이미 존재하는 경우:&lt;/b&gt; 새로 생성하지 않고 &lt;b data-index-in-node=&quot;23&quot; data-path-to-node=&quot;22,1,0&quot;&gt;기존에 등록된 Bean을 그대로 반환&lt;/b&gt;합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,2,0&quot;&gt;최초 호출인 경우:&lt;/b&gt; 실제 AppConfig에 정의된 생성 로직을 호출하여 &lt;b data-index-in-node=&quot;41&quot; data-path-to-node=&quot;22,2,0&quot;&gt;객체를 생성 ➔ 컨테이너에 저장 ➔ 반환&lt;/b&gt;합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이러한 메커니즘을 통해 개발자가 Java 메서드를 직접 호출하는 것처럼 작성하더라도 항상 싱글톤이 보장됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. @Component와 @Configuration의 차이&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Configuration도 내부적으로 @Component를 포함하고 있어 Component Scan의 대상이 됩니다. &lt;br /&gt;하지만 프록시 생성 여부에서 명확한 차이가 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;27&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;@Component&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;@Configuration&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;27,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,1,0,0&quot;&gt;목적&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;27,1,1,0&quot;&gt;일반 Bean 등록&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;27,1,2,0&quot;&gt;설정 클래스 등록&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;27,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,2,0,0&quot;&gt;프록시 생성 여부&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;27,2,1,0&quot;&gt;프록시 생성 안 함&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;27,2,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,2,2,0&quot;&gt;CGLIB 프록시 생성&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;27,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,3,0,0&quot;&gt;@Bean 메서드 간 호출 시&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;27,3,1,0&quot;&gt;싱글톤 보장 안 됨 (새 객체 생성)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;27,3,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,3,2,0&quot;&gt;싱글톤 보장됨 (컨테이너 조회)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;따라서 @Bean 메서드가 서로를 호출하여 의존관계를 주입하는 설정 클래스라면 반드시 @Configuration을 사용해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5. @Configuration(proxyBeanMethods = false)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Boot의 자동 설정(Auto Configuration) 클래스에서는 다음과 같은 옵션을 자주 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQrgQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Configuration(proxyBeanMethods = false)
public class AppConfig {
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;33&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;33,0,0&quot;&gt;역할:&lt;/b&gt; CGLIB 프록시 객체를 생성하지 않고 메서드를 직접 호출하게 만듭니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;33,1,0&quot;&gt;장점:&lt;/b&gt; 프록시 생성 단계를 생략하므로 애플리케이션 구동 시 약간의 성능 최적화를 기대할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;33,2,0&quot;&gt;주의사항:&lt;/b&gt; @Bean 메서드 간에 서로를 직접 호출하여 의존관계를 주입하는 로직이 있다면 &lt;b data-index-in-node=&quot;50&quot; data-path-to-node=&quot;33,2,0&quot;&gt;싱글톤이 깨지므로 절대 사용하면 안 됩니다.&lt;/b&gt; @Bean 메서드가 서로를 호출하지 않는 독립적인 설정일 때만 안전하게 사용할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;36&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;36,0,0&quot;&gt;@Configuration의 핵심 역할:&lt;/b&gt; 설정 클래스로 지정함과 동시에 CGLIB 프록시를 생성하여 @Bean 메서드의 싱글톤을 보장합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;36,1,0&quot;&gt;프록시 동작:&lt;/b&gt; @Bean 메서드 호출을 가로채 이미 컨테이너에 등록된 Bean이 있으면 해당 Bean을 반환하고, 없으면 새로 생성하여 컨테이너에 저장합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;36,2,0&quot;&gt;proxyBeanMethods = false:&lt;/b&gt; @Bean 메서드 간 호출이 없는 독립적인 설정 클래스에서 프록시 생성 비용을 아끼기 위한 최적화 옵션입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 장에서는 설정 클래스에서 실제 Bean을 등록하는 핵심 어노테이션인 @Bean의 동작 방식과 활용 방법을 자세히 살펴보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;39&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;40&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Q1. @Configuration의 역할은 무엇인가?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;41&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring의 설정 클래스를 정의하는 어노테이션이며, CGLIB 프록시를 생성하여 @Bean 메서드를 통해 등록되는 객체의 Singleton을 보장하는 역할을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;42&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;42&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Q2. @Configuration이 일반 @Component와 다른 점은 무엇인가?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;43&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Configuration은 CGLIB 프록시를 생성하여 @Bean 메서드 호출을 가로채 Singleton을 보장한다. 일반 @Component는 이러한 프록시 생성 기능을 제공하지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;44&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;44&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Q3. @Configuration 없이 @Bean만 사용하면 어떤 문제가 발생할 수 있는가?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;45&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Bean 메서드 간 직접 호출이 일어날 경우 메서드가 일반 Java 메서드처럼 실행되어 호출될 때마다 새로운 객체가 생성되므로 Singleton이 깨질 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;46&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;46&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Q4. proxyBeanMethods = false는 언제 사용하는가?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;47&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Bean 메서드가 서로를 직접 호출하지 않는 설정 클래스에서 프록시 생성을 생략하여 애플리케이션의 구동 성능을 최적화하고자 할 때 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;0&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5.3 @Bean의 동작 방식&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;앞에서 @Configuration은 설정 클래스를 정의하고, @Bean 메서드의 호출을 관리하여 Singleton을 보장한다는 것을 살펴보았습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그렇다면 실제로 Spring 컨테이너에 Bean을 등록하는 핵심 주체는 무엇일까요? 바로 @Bean입니다. @Bean은 Java Config에서 가장 핵심적인 어노테이션이며, 개발자가 객체 생성 과정을 직접 제어할 수 있도록 해줍니다. 이번 장에서는 @Bean의 역할과 동작 방식, 그리고 언제 사용하는지 살펴봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. @Bean이란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Bean은 &lt;b data-index-in-node=&quot;7&quot; data-path-to-node=&quot;5&quot;&gt;메서드의 반환 객체를 Spring Bean으로 등록&lt;/b&gt;하는 어노테이션입니다. Spring은 애플리케이션이 시작될 때 @Bean이 붙은 메서드를 실행한 뒤 반환된 객체를 Spring 컨테이너에 등록합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQ3wQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class AppConfig {

    @Bean
    public MemberRepository memberRepository() {
        return new MemberRepository();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bean 등록 과정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;Spring 시작&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0&quot;&gt;@Configuration 설정 클래스 검색&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,2,0&quot;&gt;@Bean 메서드 실행&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,3,0&quot;&gt;객체 생성 및 반환&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,4,0&quot;&gt;Spring Container에 등록&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;등록된 Bean은 이후 다른 Bean에서 자유롭게 의존관계를 주입받아 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. Bean 이름 결정 방식&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1) 기본 이름&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;특별한 설정이 없으면 &lt;b data-index-in-node=&quot;12&quot; data-path-to-node=&quot;13&quot;&gt;메서드 이름&lt;/b&gt;이 Bean 이름으로 사용됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQ4AQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public MemberRepository memberRepository() {
    return new MemberRepository();
}
// Bean 이름: memberRepository
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2) 이름 직접 지정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;필요에 따라 이름을 직접 지정할 수도 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQ4QQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean(&quot;repository&quot;)
public MemberRepository memberRepository() {
    return new MemberRepository();
}
// Bean 이름: repository
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-path-to-node=&quot;18,0&quot; data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실무 Tip: 특별한 이유가 없는 한 규칙성을 유지하기 위해 기본 이름(메서드명)을 사용하는 것이 일반적입니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. @Bean vs @Component&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;두 어노테이션은 모두 Spring Bean을 등록하지만, 등록 방식에서 명확한 차이가 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;22&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;@Component&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;@Bean&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;22,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,1,0,0&quot;&gt;등록 방식&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;22,1,1,0&quot;&gt;Component Scan을 통한 &lt;b data-index-in-node=&quot;19&quot; data-path-to-node=&quot;22,1,1,0&quot;&gt;자동 등록&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;22,1,2,0&quot;&gt;개발자가 직접 코드 작성하는 &lt;b data-index-in-node=&quot;16&quot; data-path-to-node=&quot;22,1,2,0&quot;&gt;수동 등록&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;22,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,2,0,0&quot;&gt;적용 대상&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;22,2,1,0&quot;&gt;클래스 (Class)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;22,2,2,0&quot;&gt;메서드 (Method)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;22,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,3,0,0&quot;&gt;주요 용도&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;22,3,1,0&quot;&gt;직접 개발한 서비스, 리포지토리, 컨트롤러&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;22,3,2,0&quot;&gt;외부 라이브러리, 복잡한 생성 로직을 가진 객체&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. 언제 @Bean을 사용할까?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실무에서는 대부분 @Component를 사용하지만, 다음과 같은 경우엔 @Bean 수동 등록이 적합합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;1) 외부 라이브러리 등록&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;외부 라이브러리 클래스(예: Jackson의 ObjectMapper)는 소스 코드를 수정할 수 없으므로 @Component를 붙일 수 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQ5AQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public ObjectMapper objectMapper() {
    return new ObjectMapper();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2) 객체 생성 과정이 복잡한 경우&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체 생성 전 추가 설정이나 여러 파라미터 조립이 필요한 경우 메서드 내부에서 자유롭게 제어할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQ5QQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    // 추가 설정 (Timeout, Interceptor 등)
    return restTemplate;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3) 조건에 따라 동적으로 Bean 생성&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Java 코드이므로 조건문(if), 환경 변수 등에 따라 실행 시점에 다른 객체를 반환하도록 구성할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQ5gQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public PaymentService paymentService() {
    if (useKakao()) {
        return new KakaoPaymentService();
    }
    return new TossPaymentService();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;36&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5. @Bean 메서드의 의존성 주입 및 생명주기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1) 메서드 매개변수를 통한 의존성 주입&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;38&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Bean 메서드의 매개변수로 필요한 타입을 선언하면, Spring 컨테이너가 해당하는 Bean을 찾아서 자동으로 전달해 줍니다 (생성자 주입과 동일).&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQ5wQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public MemberService memberService(MemberRepository repository) {
    return new MemberService(repository);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;40&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2) @Bean 메서드 간 직접 호출&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;41&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Configuration 설정 클래스 내부라면 메서드를 직접 호출하더라도 CGLIB 프록시가 가로채어 싱글톤을 보장합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQ6AQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public MemberService memberService() {
    return new MemberService(memberRepository()); // 항상 같은 memberRepository Bean 반환
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;43&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3) 생명주기(Lifecycle)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;44&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Bean으로 등록된 객체도 일반 Bean과 마찬가지로 동일한 생명주기를 거칩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;47&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;47&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실무 Scope 선택 및 사용 기준&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;48&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;48,0,0&quot;&gt;@Component 사용:&lt;/b&gt; Service, Repository, Controller, Component 등 직접 개발한 일반적인 클래스&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;48,1,0&quot;&gt;@Bean 사용:&lt;/b&gt; 외부 라이브러리 객체, 공통 Configuration 객체, 동적 조건이나 복잡한 설정으로 생성되는 객체&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;50&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;51&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;51,0,0&quot;&gt;@Bean의 정의:&lt;/b&gt; 메서드의 반환 객체를 Spring Bean으로 수동 등록하는 어노테이션입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;51,1,0&quot;&gt;수동 등록의 목적:&lt;/b&gt; 외부 라이브러리 등록, 복잡한 초기화 설정, 조건부 객체 생성 시 유용하게 쓰입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;51,2,0&quot;&gt;싱글톤 보장:&lt;/b&gt; @Configuration과 함께 사용 시 메서드 호출을 프록시가 제어하여 항상 동일한 인스턴스를 반환합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;52&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 장에서는 @Configuration의 핵심 기술인 &lt;b data-index-in-node=&quot;31&quot; data-path-to-node=&quot;52&quot;&gt;CGLIB와 설정 클래스 프록시&lt;/b&gt;를 통해 Spring이 Singleton을 어떻게 보장하는지 내부 구현 관점에서 자세히 살펴보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;54&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;55&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;55&quot;&gt;Q1. @Bean의 역할은 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;56&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;메서드의 반환 객체를 Spring 컨테이너에 Bean으로 등록하는 역할을 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;57&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;57&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;57&quot;&gt;Q2. @Bean과 @Component의 차이점은 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;58&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Component는 Component Scan을 통해 클래스 단위로 자동으로 Bean을 등록하고, @Bean은 메서드 단위에서 개발자가 객체 생성 및 초기화 과정을 직접 제어하여 수동으로 Bean을 등록합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;59&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;59&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;59&quot;&gt;Q3. 어떤 경우에 @Bean을 사용해야 하나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;60&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;소스 코드를 수정할 수 없는 외부 라이브러리 객체를 등록하거나, 객체 생성 과정이 복잡한 경우, 또는 실행 조건에 따라 다른 객체를 동적으로 생성해야 하는 경우에 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;61&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;61&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;61&quot;&gt;Q4. @Configuration과 함께 @Bean을 사용하면 어떤 장점이 있나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;62&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring이 CGLIB 프록시를 통해 @Bean 메서드 호출을 가로채므로, 메서드를 직접 호출하더라도 새로운 객체가 생성되지 않고 동일한 Singleton 인스턴스가 보장됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;63&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;63&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;63&quot;&gt;Q5. @Bean 메서드의 매개변수에 선언된 객체는 어떻게 주입되나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;64&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 컨테이너가 해당 매개변수 타입에 맞는 Bean을 탐색하여 자동으로 전달해 주며,이는 생성자 주입 방식과 동일하게 동작합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;65&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;65&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;65&quot;&gt;Q6. @Bean으로 등록한 객체에서도 @PostConstruct나 @PreDestroy 같은 생명주기 콜백이 동작하나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;66&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;네, @Bean으로 등록된 객체도 Spring 컨테이너가 생명주기를 관리하므로 동일하게 @PostConstruct 및 @PreDestroy 콜백이 정상적으로 동작합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;0&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5.4 CGLIB와 설정 클래스 프록시&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;앞에서 @Configuration과 @Bean을 함께 사용하면 &lt;b&gt;Spring이 Singleton을 보장&lt;/b&gt;한다는 것을 살펴보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;2&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;하지만 Java 코드 자체만 보면 이상한 점이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQjAU&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class AppConfig {

    @Bean
    public MemberRepository memberRepository() {
        return new MemberRepository();
    }

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean
    public OrderService orderService() {
        return new OrderService(memberRepository());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;memberService()와 orderService()는 모두 memberRepository()를 direct 호출하고 있습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;일반적인 Java 코드라면 memberRepository()가 호출될 때마다 새로운 객체가 생성되어야 합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;하지만 실제로는 단 하나의 MemberRepository Bean만 생성되어 공유됩니다. Spring은 이를 어떻게 보장할까요? &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이번 장에서는 Spring이 사용하는 &lt;b data-index-in-node=&quot;21&quot; data-path-to-node=&quot;5&quot;&gt;CGLIB 기반 설정 클래스 프록시의 동작 원리&lt;/b&gt;를 깊이 있게 살펴봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 프록시가 필요한 이유&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;먼저 어노테이션이 없는 순수 Java 클래스의 동작을 생각해 봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQjQU&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public class AppConfig {

    public MemberRepository memberRepository() {
        return new MemberRepository();
    }

    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    public OrderService orderService() {
        return new OrderService(memberRepository());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 클래스의 메서드들을 호출하면 다음과 같이 실행됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;memberService() 호출 ➔ memberRepository() 실행 ➔ &lt;b data-index-in-node=&quot;45&quot; data-path-to-node=&quot;11,0,0&quot;&gt;새 객체 A 생성&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;orderService() 호출 ➔ memberRepository() 실행 ➔ &lt;b data-index-in-node=&quot;44&quot; data-path-to-node=&quot;11,1,0&quot;&gt;새 객체 B 생성&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;결과적으로 2개의 MemberRepository 인스턴스가 생성되며 Singleton 원칙이 깨지게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. Spring의 해결책: CGLIB (Code Generation Library)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring은 @Configuration이 선언된 AppConfig 클래스를 있는 그대로 사용하지 않습니다. 대신 &lt;b data-index-in-node=&quot;64&quot; data-path-to-node=&quot;15&quot;&gt;CGLIB&lt;/b&gt;라는 바이트코드 조작 라이브러리를 통해 AppConfig를 상속받은 자식 프록시 클래스를 런타임에 동적으로 생성합니다. &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px; font-family: 'Noto Sans Light';&quot;&gt;실제 Spring IoC 컨테이너에는 개발자가 작성한 원본 AppConfig가 아닌, &lt;/span&gt;&lt;b data-index-in-node=&quot;48&quot; data-path-to-node=&quot;17&quot;&gt;CGLIB가 생성한 자식 프록시 객체&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px; font-family: 'Noto Sans Light';&quot;&gt;가 등록됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. CGLIB 프록시의 내부 동작 원리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;CGLIB 프록시 클래스는 원본 클래스의 @Bean 메서드들을 오버라이딩(Override)하여 메서드 호출을 가로챕니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;896&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6I0Ry/dJMcag7qQJC/EJFsqd6t5kTknWKNdgmeGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6I0Ry/dJMcag7qQJC/EJFsqd6t5kTknWKNdgmeGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6I0Ry/dJMcag7qQJC/EJFsqd6t5kTknWKNdgmeGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6I0Ry%2FdJMcag7qQJC%2FEJFsqd6t5kTknWKNdgmeGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;434&quot; height=&quot;489&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;896&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;22&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,0,0&quot;&gt;컨테이너 조회:&lt;/b&gt; 프록시 메서드가 호출되면 먼저 Spring 컨테이너에 해당 Bean이 이미 존재하는지 확인합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,1,0&quot;&gt;기존 Bean 반환:&lt;/b&gt; 이미 컨테이너에 등록된 Bean이 있다면 &lt;b data-index-in-node=&quot;35&quot; data-path-to-node=&quot;22,1,0&quot;&gt;기존 인스턴스를 즉시 반환&lt;/b&gt;합니다. (새 객체 생성 안 함)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,2,0&quot;&gt;최초 생성:&lt;/b&gt; 컨테이너에 Bean이 없다면 원본 클래스의 생성 로직을 실행하여 &lt;b data-index-in-node=&quot;43&quot; data-path-to-node=&quot;22,2,0&quot;&gt;객체를 생성한 뒤 컨테이너에 저장하고 반환&lt;/b&gt;합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 가로채기 메커니즘을 통해 @Bean 메서드를 몇 번을 직접 호출하더라도 싱글톤이 철저히 보장됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. JDK Dynamic Proxy vs CGLIB&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring에서 사용하는 대표적인 프록시 생성 기술 2가지를 비교해 봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;27&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;JDK Dynamic Proxy&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;CGLIB&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;27,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,1,0,0&quot;&gt;적용 방식&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;27,1,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,1,1,0&quot;&gt;인터페이스(Interface) 기반&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;27,1,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,1,2,0&quot;&gt;클래스 상속(Inheritance) 기반&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;27,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,2,0,0&quot;&gt;적용 대상&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;27,2,1,0&quot;&gt;인터페이스를 구현한 클래스&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;27,2,2,0&quot;&gt;인터페이스가 없는 일반 클래스&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;27,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,3,0,0&quot;&gt;설정 클래스 적용 여부&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;27,3,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,3,1,0&quot;&gt;불가능&lt;/b&gt; (설정 클래스는 인터페이스가 없음)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;27,3,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,3,2,0&quot;&gt;사용 (표준)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;AppConfig와 같은 설정 클래스는 대부분 구체 클래스(Concrete Class)이며 인터페이스를 구현하지 않습니다. 따라서 인터페이스가 필수인 JDK Dynamic Proxy를 사용할 수 없으므로, Spring은 설정 클래스에 &lt;b data-index-in-node=&quot;131&quot; data-path-to-node=&quot;28&quot;&gt;CGLIB&lt;/b&gt;를 적용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5. `@Configuration(proxyBeanMethods = false)`의 이해&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;만약 설정 클래스 내부의 @Bean 메서드들끼리 서로를 직접 호출하는 로직이 전혀 없다면 어떻게 할까요?&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQkQU&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration(proxyBeanMethods = false)
public class AppConfig {

    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;33&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;33,0,0&quot;&gt;proxyBeanMethods = false:&lt;/b&gt; CGLIB 프록시 생성 단계를 생략하고 메서드를 직접 호출하는 Lite Mode로 작동합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;33,1,0&quot;&gt;장점:&lt;/b&gt; CGLIB 바이트코드 생성 비용을 아껴 애플리케이션 시작 및 메모리 성능을 미세하게 최적화할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;33,2,0&quot;&gt;주의점:&lt;/b&gt; 메서드 간 직접 호출 시 싱글톤이 깨지므로, &lt;b data-index-in-node=&quot;30&quot; data-path-to-node=&quot;33,2,0&quot;&gt;@Bean 메서드 간 간섭이 없는 경우에만 사용&lt;/b&gt;해야 합니다. (Spring Boot의 자동 설정 클래스에서 널리 활용됨)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;36&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;36,0,0&quot;&gt;CGLIB 프록시:&lt;/b&gt; Spring은 @Configuration 클래스를 상속받은 자식 프록시 객체를 만들어 컨테이너에 등록합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;36,1,0&quot;&gt;싱글톤 유지:&lt;/b&gt; 프록시는 @Bean 메서드 호출을 가로채어 이미 등록된 Bean이 있으면 반환하고, 없으면 생성하여 저장하는 방식으로 싱글톤을 보장합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;36,2,0&quot;&gt;CGLIB 채택 이유:&lt;/b&gt; Java 설정 클래스는 인터페이스가 없는 일반 클래스이므로 상속 기반의 CGLIB를 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;36,3,0&quot;&gt;proxyBeanMethods = false:&lt;/b&gt; 메서드 간 direct 호출이 없는 독립적 Bean 설정 시 프록시 생성을 끄는 성능 최적화 옵션입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 장에서는 지금까지 학습한 IoC 컨테이너를 다시 정리하는 의미에서 &lt;b data-index-in-node=&quot;40&quot; data-path-to-node=&quot;37&quot;&gt;BeanFactory와 ApplicationContext의 역할과 차이점&lt;/b&gt;을 심화하여 살펴봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;39&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;40&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;40&quot;&gt;Q1. Spring은 @Configuration 클래스에 왜 CGLIB를 사용하나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;41&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Bean 메서드 호출을 가로채 이미 컨테이너에 생성된 Bean이 존재하면 기존 인스턴스를 반환함으로써 동일한 Bean이 중복 생성되지 않도록 싱글톤(Singleton)을 보장하기 위해서입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;41&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;42&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;42&quot;&gt;Q2. CGLIB는 어떤 방식으로 프록시를 생성하나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;43&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;CGLIB는 대상 클래스를 상속(extends)받아 런타임에 새로운 자식 프록시 클래스를 생성하며, 오버라이딩된 메서드 내부에서 컨테이너 조회 및 싱글톤 보장 로직을 수행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;43&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;44&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44&quot;&gt;Q3. proxyBeanMethods = false는 어떤 경우에 사용할 수 있나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;45&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;@Bean 메서드가 서로를 직접 호출하여 의존관계를 주입하지 않는 독립적인 설정 클래스에서, CGLIB 프록시 생성 과정을 생략하여 애플리케이션 구동 성능을 최적화하고자 할 때 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;45&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;46&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;46&quot;&gt;Q4. 설정 클래스 프록시 생성 시 JDK Dynamic Proxy 대신 CGLIB를 사용하는 이유는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;47&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Java 설정 클래스는 인터페이스 없이 구현된 일반 클래스인 경우가 대부분입니다. JDK Dynamic Proxy는 인터페이스 기반으로만 프록시를 생성할 수 있지만, CGLIB는 클래스 상속 기반이므로 인터페이스가 없는 구체 클래스에도 프록시를 생성할 수 있기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;48&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;48&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;48&quot;&gt;Q5. CGLIB 프록시 동작 시 설정 클래스의 제약 사항에는 무엇이 있나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;49&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;CGLIB는 클래스 상속 및 메서드 오버라이딩을 이용하므로, 설정 클래스 및 @Bean 메서드에 final 키워드가 붙어 있으면 상속이나 오버라이딩이 불가능하여 프록시가 정상적으로 생성되지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;50&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;50&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;50&quot;&gt;Q6. @Configuration이 없는 클래스에서 @Bean 메서드를 선언하면(Lite Mode) 어떻게 동작하나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;51&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;CGLIB 프록시가 생성되지 않으므로 일반 Java 메서드처럼 동작합니다. 따라서 해당 @Bean 메서드를 다른 메서드에서 직접 호출할 경우 호출될 때마다 새로운 인스턴스가 생성되어 싱글톤이 파괴됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;0&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5.5 BeanFactory와 ApplicationContext 다시 보기&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Part 2에서는 Spring IoC 컨테이너의 핵심 인터페이스인 BeanFactory와 ApplicationContext를 간단히 살펴보았습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이후 Bean의 생명주기와 Scope, Java Config, @Configuration, @Bean의 동작 원리를 학습하면서 Spring 컨테이너가 실제로 어떤 역할을 수행하는지 이해하게 되었습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이번 장에서는 지금까지 배운 내용을 바탕으로&lt;b&gt; BeanFactory와 ApplicationContext를 다시 정리하고, 두 인터페이스의 관계와 차이점을 심화하여 살펴봅니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. Spring IoC 컨테이너&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring IoC 컨테이너는 애플리케이션에서 사용하는 객체를 생성하고 관리하는 핵심 구성 요소입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;컨테이너는 다음과 같은 역할을 수행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bean 생성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;의존성 주입(DI)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생명주기 관리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bean 조회&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Singleton 관리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이벤트 처리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;환경 정보 관리&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이러한 기능을 제공하는 대표적인 인터페이스가 BeanFactory와 ApplicationContext입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. BeanFactory란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;BeanFactory는 Spring IoC 컨테이너의 가장 기본이 되는 최상위 인터페이스입니다. &lt;br /&gt;가장 중요한 기능은 &lt;b data-index-in-node=&quot;65&quot; data-path-to-node=&quot;12&quot;&gt;Bean을 생성하고 조회&lt;/b&gt;하는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQsAU&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;BeanFactory beanFactory = ...;

MemberService service = beanFactory.getBean(MemberService.class);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;필요한 Bean을 요청하면 컨테이너에서 객체를 찾아 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;BeanFactory가 제공하는 기능&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;16&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bean 생성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bean 조회&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;의존성 관리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Singleton 관리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bean 생명주기 관리&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;즉, Spring IoC의 핵심 기능은 모두 BeanFactory에서 시작됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. ApplicationContext란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ApplicationContext는 BeanFactory를 상속하는 인터페이스입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;즉, ApplicationContext는 BeanFactory의 모든 기능을 포함하면서 부가적인 엔터프라이즈 기능들을 추가로 제공합니다. 실제 Spring Boot 애플리케이션에서는 대부분 ApplicationContext를 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. ApplicationContext가 제공하는 부가 기능&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ApplicationContext는 Bean 관리뿐만 아니라 애플리케이션 전체를 관리하는 프레임워크 수준의 기능을 추가로 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;1) 국제화 지원 (MessageSource)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring은 다국어/국제화(i18n) 기능을 제공합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;messages.properties, messages_ko.properties, messages_en.properties 등의 파일에서 현재 사용자의 Locale에 부합하는 메시지를 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQsQU&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;String message = applicationContext.getMessage(&quot;welcome&quot;, null, locale);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; 2) 환경 정보 관리 (Environment)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;application.yml이나 application.properties 등 애플리케이션 실행 환경의 설정값(Profiles 및 Properties)을 쉽게 조회할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQsgU&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Environment environment = applicationContext.getEnvironment();
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; 3) 리소스 조회 (ResourceLoader)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;classpath:, file:, http: 등 위치에 상관없이 동일한 Unified API로 리소스 파일을 읽어올 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQswU&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Resource resource = applicationContext.getResource(&quot;classpath:data.txt&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;&lt;b&gt;4) 이벤트 발행 및 구독 (ApplicationEventPublisher)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;36&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이벤트 기반 프로그래밍을 지원합니다. &lt;br /&gt;예를 들어 회원가입 완료 후 이메일 발송, 포인트 지급 등의 비동기/동기 이벤트를 손쉽게 발행할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQtAU&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;applicationContext.publishEvent(new MemberRegisteredEvent(member));
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;39&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5. BeanFactory vs ApplicationContext 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;40&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;두 인터페이스의 기능을 비교하면 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;41&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;기능 / 역할&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;BeanFactory&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;ApplicationContext&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,1,0,0&quot;&gt;Bean 생성 &amp;amp; 조회&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,1,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,1,1,0&quot;&gt;O&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,1,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,1,2,0&quot;&gt;O&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,2,0,0&quot;&gt;의존성 주입 (DI)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,2,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,2,1,0&quot;&gt;O&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,2,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,2,2,0&quot;&gt;O&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,3,0,0&quot;&gt;Singleton 관리&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,3,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,3,1,0&quot;&gt;O&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,3,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,3,2,0&quot;&gt;O&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,4,0,0&quot;&gt;국제화 (MessageSource)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,4,1,0&quot;&gt;X&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,4,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,4,2,0&quot;&gt;O&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,5,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,5,0,0&quot;&gt;환경 변수/프로파일 (Environment)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,5,1,0&quot;&gt;X&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,5,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,5,2,0&quot;&gt;O&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,6,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,6,0,0&quot;&gt;리소스 로딩 (ResourceLoader)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,6,1,0&quot;&gt;X&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,6,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,6,2,0&quot;&gt;O&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,7,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,7,0,0&quot;&gt;이벤트 발행 (ApplicationEventPublisher)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,7,1,0&quot;&gt;X&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,7,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,7,2,0&quot;&gt;O&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,8,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,8,0,0&quot;&gt;Bean 로딩 시점&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,8,1,0&quot;&gt;지연 로딩 (Lazy Loading)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;41,8,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,8,2,0&quot;&gt;조기 로딩 (Eager Loading)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-path-to-node=&quot;42&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;42,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;42,0&quot;&gt;Eager Loading이란?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;42,1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ApplicationContext는 애플리케이션 구동 시점에 모든 Singleton Bean을 미리 생성하고 주입받으므로, &lt;br /&gt;구동 시점에 설정 오류를 즉시 감지할 수 있다는 큰 장점이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;48&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;49&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이번 &lt;b data-index-in-node=&quot;3&quot; data-path-to-node=&quot;49&quot;&gt;Part 5. Spring Configuration&lt;/b&gt;에서는 Spring의 Java 기반 설정 방식을 학습하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;50&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;50,0,0&quot;&gt;@Configuration:&lt;/b&gt; 설정 클래스를 정의하며 CGLIB 프록시를 생성하여 @Bean 메서드의 싱글톤을 보장합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;50,1,0&quot;&gt;@Bean:&lt;/b&gt; 수동으로 객체 생성 과정을 제어하여 Spring Bean으로 등록합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;50,2,0&quot;&gt;ApplicationContext:&lt;/b&gt; BeanFactory의 Bean 관리 기능뿐만 아니라 국제화, 환경 변수, 리소스 로딩, 이벤트 처리 등 애플리케이션 전반의 핵심 기능을 담당합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;54&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;55&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;55&quot;&gt;Q1. BeanFactory와 ApplicationContext의 가장 큰 차이점은 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;56&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;BeanFactory는 Bean의 생성, 조회, DI 등 핵심 기능만 담당하는 기본 IoC 컨테이너인 반면, ApplicationContext는 BeanFactory를 상속 및 확장하여 국제화(MessageSource), 환경 정보(Environment), 리소스 로딩(ResourceLoader), 이벤트 발행(ApplicationEventPublisher) 등의 엔터프라이즈 부가 기능을 추가로 제공하는 컨테이너입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;57&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;57&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;57&quot;&gt;Q2. Spring Boot에서는 일반적으로 어떤 컨테이너를 사용하나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;58&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;ApplicationContext를 주로 사용합니다. 애플리케이션 시작 시 SpringApplication.run()이 실행되면서 ApplicationContext가 생성되고 필요한 모든 Bean을 초기화 및 사전 등록(Eager Loading)하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;59&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;59&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;59&quot;&gt;Q3. ApplicationContext가 제공하는 대표적인 추가 기능은 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;60&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Locale 기반 국제화 메시지를 처리하는 MessageSource, 실행 환경의 설정값과 프로파일을 다루는 Environment, classpath나 file 자원을 일관되게 다루는 ResourceLoader, 그리고 이벤트 기반 프로그래밍을 지원하는 ApplicationEventPublisher가 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;61&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;61&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;61&quot;&gt;Q4. 실무에서 ApplicationContext를 코드 내에서 직접 사용할 때는 언제인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;62&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;대부분의 비즈니스 로직에서는 의존성 주입(DI)을 통해 Bean을 제공받으므로 직접 다룰 일이 없지만, 실행 시점에 동적으로 Bean을 구해야 할 때, Spring 이벤트를 발행할 때, 환경 정보 및 리소스 파일에 직접 접근할 때 사용됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;63&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;63&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;63&quot;&gt;Q5. Bean 로딩 시점 관점에서 BeanFactory와 ApplicationContext의 차이는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;64&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;BeanFactory는 Bean이 실제로 요청될 때 객체를 생성하는 지연 로딩(Lazy Loading) 방식을 기본으로 사용하는 반면, ApplicationContext는 애플리케이션 구동 시점에 모든 Singleton Bean을 미리 생성하는 조기 로딩(Eager Loading) 방식을 사용하여 구동 시점에 오류를 미리 검증할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;65&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;65&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;65&quot;&gt;Q6. ApplicationContext의 상속 구조 및 계층 구조는 어떻게 구성되어 있나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;66&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ApplicationContext는 BeanFactory를 상속할 뿐만 아니라 MessageSource, EnvironmentCapable, ResourcePatternResolver, ApplicationEventPublisher 등의 여러 인터페이스를 다중 상속받아 하나의 통합된 애플리케이션 컨테이너 인터페이스로 제공됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;51&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> SpringBoot</category>
      <category>Spring</category>
      <author>limdaeil</author>
      <guid isPermaLink="true">https://limdaeil.tistory.com/88</guid>
      <comments>https://limdaeil.tistory.com/88#entry88comment</comments>
      <pubDate>Wed, 29 Jul 2026 15:34:24 +0900</pubDate>
    </item>
    <item>
      <title>Spring:  Part 4. Bean 생명주기(Lifecycle)</title>
      <link>https://limdaeil.tistory.com/87</link>
      <description>&lt;h2 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Part&amp;nbsp;4.&amp;nbsp;Bean&amp;nbsp;생명주기(Lifecycle)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;스프링(Spring) 프레임워크는 Bean을 생성하고 의존성을 주입하여 관리합니다. 그렇다면 Bean은 생성된 이후부터 애플리케이션이 종료될 때까지 어떤 과정을 거칠까요? &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;스프링은 단지 객체를 생성하는 데서 끝나지 않고 &lt;b data-index-in-node=&quot;27&quot; data-path-to-node=&quot;4&quot;&gt;생성, 초기화, 의존성 연결, 소멸까지의 전체 과정&lt;/b&gt;을 자동으로 안전하게 관리합니다. 이 과정을 &lt;b&gt;'Bean 생명주기(Bean Lifecycle)'&lt;/b&gt;라고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4.1 Bean 생명주기(Bean Lifecycle)란?&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;스프링 컨테이너가 Bean 객체를 생성한 시점부터 애플리케이션 종료로 인해 소멸될 때까지 관리하는 전체 흐름을 의미합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;기본 생명주기 흐름&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;Bean 생성 (Create)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;의존성 주입 (Dependency Injection)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0&quot;&gt;초기화 (Initialization)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,3,0&quot;&gt;애플리케이션에서 사용 (Use)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,4,0&quot;&gt;소멸 (Destroy)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 생명주기 단계별 상세 설명&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. Bean 생성 (Create)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;애플리케이션이 시작되면 Component Scan이나 @Bean 설정을 통해 객체를 생성합니다. &lt;br /&gt;이 시점에는 객체만 만들어졌을 뿐, 의존성이 연결된 상태는 아닙니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2.의존성 주입 (Dependency Injection)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체 생성이 완료되면 필요한 의존성을 주입합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;16&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,0,0&quot;&gt;진행 순서 예시: &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;MemberRepository 생성 ➔ MemberService 생성 ➔ 의존성 연결&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. 초기화 (Initialization)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;의존성 주입이 끝난 후, 외부 시스템 연결이나 데이터 준비 등 준비 작업을 진행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;19&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,0,0&quot;&gt;대표적인 초기화 작업:&lt;/b&gt; DB 연결, 메시지 브로커 연결, 캐시 초기화, 외부 API 클라이언트 준비 등&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,1,0&quot;&gt;핵심:&lt;/b&gt; 초기화 단계까지 완전히 완료되어야 Bean을 정상적으로 사용할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. Bean 사용 (Use)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;초기화가 끝난 Bean은 애플리케이션 요청을 처리하는 데 사용됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;22&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,0,0&quot;&gt;호출 흐름:&lt;/b&gt; Client ➔ Controller ➔ Service ➔ Repository&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Singleton Bean의 경우 동일한 객체를 계속 재사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5. 소멸 (Destroy)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;애플리케이션 종료 시 안전하게 자원을 해제하고 정리를 수행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;25&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,0,0&quot;&gt;주요 소멸 작업:&lt;/b&gt; DB Connection 종료, Thread Pool 종료, Socket 종료, Cache Flush 등&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. 객체 생성과 초기화를 분리해야 하는 이유&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자 내부에서 외부 서버와 통신하거나 무거운 초기화 작업을 진행하는 것은 좋지 않은 설계입니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQgAI&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// ❌ 좋지 않은 예시: 생성자와 초기화 작업이 섞인 경우
public MemberService() {
    connectDatabase();
    loadCache();
    connectMessageBroker();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;분리해야 하는 이유&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;31&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,0,0&quot;&gt;생성 단계:&lt;/b&gt; 객체를 만드는 단순한 작업만 수행합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,1,0&quot;&gt;초기화 단계:&lt;/b&gt; 의존성 주입이 완료된 후 안전하게 외부 자원 연결 등의 작업을 수행합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성과 초기화를 명확히 분리하면 &lt;b data-index-in-node=&quot;18&quot; data-path-to-node=&quot;32&quot;&gt;객체 생성이 단순해지고, 테스트 및 유지보수가 매우 쉬워집니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. 실무에서의 Bean 생명주기 활용&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;대부분의 일반적인 Bean은 별도의 초기화 과정 없이 생성되지만, &lt;b&gt;외부 자원을 다루는 객체는 생명주기 관리가 필수적&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;36&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;36,0,0&quot;&gt;관리가 중요한 대상:&lt;/b&gt; DB Connection Pool, Redis Client, Kafka Producer/Consumer, Scheduler, Thread Pool, 파일 시스템 연결 등&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;36,1,0&quot;&gt;이유:&lt;/b&gt; 자원을 생성한 뒤 해제하지 않으면 메모리 누수나 연결 누수가 발생할 수 있고, 초기화되지 않은 상태로 사용하면 오류가 발생할 수 있기 때문입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size18&quot; data-path-to-node=&quot;47&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Q1. Spring Bean의 생명주기란 무엇인가요?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 컨테이너가 Bean을 생성하고 의존성을 주입한 뒤 초기화를 수행하고, 애플리케이션 종료 시 Bean을 소멸시키는 전체 과정을 의미합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Q2. Bean의 일반적인 생명주기 순서는 어떻게 되나요?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bean 생성 &amp;rarr; 의존성 주입 &amp;rarr; 초기화 &amp;rarr; 사용 &amp;rarr; 소멸 순서로 진행됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Q3. 객체 생성과 초기화를 분리하는 이유는 무엇인가요?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체 생성을 단순하게 유지하고, 의존성이 모두 준비된 이후 필요한 초기화 작업을 수행하기 위해서입니다. 이를 통해 객체의 안정성과 테스트 용이성을 높일 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Q4. 생명주기 관리가 중요한 Bean의 예를 들어보세요.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;데이터베이스 연결 객체, Redis Client, Kafka Client, Thread Pool, Scheduler와 같이 외부 자원을 사용하는 Bean은 생성 후 초기화와 종료 시 자원 해제가 필요하므로 생명주기 관리가 중요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4.2 @PostConstruct와 @PreDestroy를 활용한 생명주기 콜백&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이전 글에서 Spring Bean의 생성, 의존성 주입, 초기화, 사용, 소멸로 이어지는 전체 생명주기(Lifecycle)를 알아보았습니다. 그렇다면 초기화 작업과 종료 작업은 실제로 코드의 어느 위치에서 수행해야 할까요? &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;스프링은 특정 시점에 필요한 작업을 자동으로 수행해 주는 '생명주기 콜백(Lifecycle Callback)'을 제공합니다.&lt;/b&gt; 대표적인 방법이 바로 자바 표준 어노테이션인 `@PostConstruct`와 `@PreDestroy`입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 생명주기 콜백(Lifecycle Callback)이란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;스프링 컨테이너가 Bean의 생명주기 중 특정 시점에 도달했을 때 &lt;b data-index-in-node=&quot;37&quot; data-path-to-node=&quot;7&quot;&gt;개발자가 지정한 메서드를 자동으로 호출해 주는 기능&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;전체 실행 흐름&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;객체 생성&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;의존성 주입 완료&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0&quot;&gt;@PostConstruct 호출&lt;/b&gt; (초기화 작업)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,3,0&quot;&gt;Bean 사용&lt;/b&gt; (서비스 수행)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,4,0&quot;&gt;@PreDestroy 호출&lt;/b&gt; (종료 및 자원 해제)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,5,0&quot;&gt;Bean 소멸&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. @PostConstruct (초기화 콜백)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bean이 생성되고 &lt;b data-index-in-node=&quot;11&quot; data-path-to-node=&quot;12&quot;&gt;의존성 주입(DI)이 완전히 완료된 직후 한 번 호출&lt;/b&gt;되는 메서드에 붙입니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQmgI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Component
public class NetworkClient {

    @PostConstruct
    public void init() {
        System.out.println(&quot;초기화 작업을 수행합니다.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote data-path-to-node=&quot;15&quot; data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;생성자 대신 @PostConstruct를 사용하는 이유&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자가 실행되는 시점에는 아직 필요한 의존성이 주입되지 않은 상태입니다. 반면&lt;b&gt; `@PostConstruct`는 의존성 주입이 끝난 후 실행되므로, 주입된 다른 Bean들을 안전하게 참조하여 초기화 작업을 진행할 수 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;주요 사용 예시&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;데이터베이스 및 외부 API 클라이언트 초기화&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;메시지 브로커(Kafka 등) 연결&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;캐시 데이터 사전 적재(Cache Loading)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;주요 설정 정보 검증&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. @PreDestroy (소멸 콜백)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;스프링 컨테이너가 종료되어 &lt;b data-index-in-node=&quot;15&quot; data-path-to-node=&quot;21&quot;&gt;Bean이 소멸되기 바로 직전에 한 번 호출&lt;/b&gt;되는 메서드에 붙입니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQmwI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Component
public class NetworkClient {

    @PreDestroy
    public void close() {
        System.out.println(&quot;연결을 안전하게 종료합니다.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;주요 사용 예시&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;25&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;데이터베이스 Connection 및 Socket 종료&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Thread Pool 종료&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;파일/로그 Flush&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Redis/Kafka 연결 종료&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. 실무 사용 시 주의사항 및 특징&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;메서드 작성 규칙&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;29&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;매개변수(Parameter)를 갖지 않아야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;반환 타입은 `void`를 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;하나의 Bean 클래스 내에서는 각각 하나의 메서드에만 적용하는 것이 권장됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bean Scope(스코프)와의 관계&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;31&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,0,0&quot;&gt;Singleton Bean:&lt;/b&gt; 애플리케이션 시작 시 @PostConstruct가 1회, 종료 시 @PreDestroy가 1회 호출됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,1,0&quot;&gt;Prototype Bean:&lt;/b&gt; 스프링 컨테이너가 Bean의 &lt;b data-index-in-node=&quot;32&quot; data-path-to-node=&quot;31,1,0&quot;&gt;생성과 의존성 주입까지만 관리&lt;/b&gt;하고 소멸에는 관여하지 않습니다. 따라서 &lt;b data-index-in-node=&quot;71&quot; data-path-to-node=&quot;31,1,0&quot;&gt;@PreDestroy가 자동으로 호출되지 않으므로&lt;/b&gt; 주의해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;핵심 질문&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Q1. @PostConstruct는 언제 호출되나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bean 생성과 의존성 주입이 모두 완료된 이후, Bean이 사용되기 직전에 한 번 호출됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Q2. @PreDestroy는 언제 호출되나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 컨테이너가 Bean을 소멸시키기 직전에 한 번 호출됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Q3. 생성자 대신 @PostConstruct를 사용하는 이유는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자가 실행되는 시점에는 의존성 주입이 완료되지 않았을 수 있습니다. @PostConstruct는 의존성 주입 이후 호출되므로 다른 Bean을 안전하게 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Q4. Prototype Bean에서도 @PreDestroy가 자동으로 호출되나요?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;아닙니다. Prototype Bean은 생성만 Spring이 관리하며, 소멸은 관리하지 않으므로 @PreDestroy가 자동으로 호출되지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; letter-spacing: 0px;&quot;&gt;4.3&amp;nbsp; Spring Bean Scope 개념 및 종류 완벽 정리&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Bean은 생성부터 소멸까지 일련의 생명주기(Lifecycle)를 거칩니다. 하지만 모든 Bean이 똑같은 방식으로, 동일한 시점에 생성되어 유지되는 것은 아닙니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;어떤 Bean은 애플리케이션 시작 시 딱 하나만 생성되어 계속 공유되는 반면, 어떤 Bean은 요청이 올 때마다 새로 생성됩니다. 이처럼 &lt;b data-index-in-node=&quot;77&quot; data-path-to-node=&quot;4&quot;&gt;Spring 컨테이너가 Bean을 얼마 동안 유지하고 몇 개나 생성하여 관리할지 결정하는 범위&lt;/b&gt;를&lt;b&gt; 'Bean Scope(스프링 빈 스코프)'&lt;/b&gt;라고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. Bean Scope가 필요한 이유&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;&lt;i&gt;&quot;모든 객체를 하나만 만들어 공유하면 편하지 않을까?&quot;&lt;/i&gt;&lt;/b&gt;라고 생각할 수 있습니다. |&lt;br /&gt;하지만 객체의 성격에 따라 필요한 관리 방식이 다릅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;싱글톤 방식이 적합한 객체&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;종류:&lt;/b&gt; Service, Repository, Controller 등&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;특징:&lt;/b&gt; 여러 사용자가 동시에 접근해서 공유하여 사용해도 상태가 변경되지 않는 객체입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;개별 관리가 필요한 객체&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;종류:&lt;/b&gt; 로그인 사용자 정보, HTTP 요청 데이터, 장바구니 등&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;특징:&lt;/b&gt; 사용자마다 서로 다른 상태 데이터값을 가져야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;만약 사용자 정보를 담는 Bean을 하나만 생성해 공유하면 &lt;b data-index-in-node=&quot;33&quot; data-path-to-node=&quot;12&quot;&gt;서로 다른 사용자의 데이터가 섞이는 치명적인 문제&lt;/b&gt;가 발생합니다. 반대로 모든 Bean을 매번 새로 생성하면 &lt;b data-index-in-node=&quot;93&quot; data-path-to-node=&quot;12&quot;&gt;메모리 사용량 증가와 성능 저하&lt;/b&gt;가 발생합니다. 따라서 용도에 맞는 Scope 설정이 필수적입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. Spring이 제공하는 주요 Scope 종류&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;스프링은 다양한 환경과 목적에 맞춰 다음과 같은 Scope를 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;16&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Scope&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;생성 시점&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;16,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,1,0,0&quot;&gt;Singleton&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;16,1,1,0&quot;&gt;컨테이너당 단 하나의 Bean만 생성하여 공유 &lt;b data-index-in-node=&quot;26&quot; data-path-to-node=&quot;16,1,1,0&quot;&gt;(기본값)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;16,1,2,0&quot;&gt;애플리케이션 시작 시&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;16,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,2,0,0&quot;&gt;Prototype&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;16,2,1,0&quot;&gt;Bean을 요청할 때마다 매번 새로운 객체를 생성&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;16,2,2,0&quot;&gt;Bean 요청 시&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;16,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,3,0,0&quot;&gt;Request&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;16,3,1,0&quot;&gt;HTTP 요청 하나가 들어오고 나갈 때까지 유지되는 Scope&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;16,3,2,0&quot;&gt;HTTP 요청 시&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;16,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,4,0,0&quot;&gt;Session&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;16,4,1,0&quot;&gt;HTTP Session과 생명주기를 같이하는 Scope&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;16,4,2,0&quot;&gt;Session 생성 시&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;16,5,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,5,0,0&quot;&gt;Application&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;16,5,1,0&quot;&gt;ServletContext와 생명주기를 같이하는 Scope&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;16,5,2,0&quot;&gt;ServletContext 생성 시&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-path-to-node=&quot;17,0&quot; data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;참고: &lt;b&gt;Request&lt;/b&gt;, &lt;b&gt;Session&lt;/b&gt;, &lt;b&gt;Application&lt;/b&gt; Scope는 주로 &lt;b&gt;웹 애플리케이션 환경(Web Scope)에서 사용&lt;/b&gt;됩니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. Scope별 생명주기의 차이&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Scope에 따라 Bean의 생명주기도 크게 달라집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1) Singleton Scope&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;22&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,0,0&quot;&gt;흐름:&lt;/b&gt; 애플리케이션 시작 ➔ Bean 생성 ➔ 사용 ➔ 애플리케이션 종료 ➔ Bean 소멸&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,1,0&quot;&gt;특징:&lt;/b&gt; 애플리케이션의 시작부터 종료까지 컨테이너와 생명주기를 함께합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2) Prototype Scope&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;24&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24,0,0&quot;&gt;흐름:&lt;/b&gt; Bean 요청 ➔ Bean 생성 및 주입 ➔ 사용 ➔ Spring 관리 종료&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24,1,0&quot;&gt;특징:&lt;/b&gt; 스프링 컨테이너는 Bean을 생성하고 의존성을 주입하여 반환까지만 담당합니다. &lt;b data-index-in-node=&quot;48&quot; data-path-to-node=&quot;24,1,0&quot;&gt;이후 소멸 과정 등은 스프링이 관리하지 않습니다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. Scope 설정 방법 및 주의사항&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Scope 지정 방법&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Scope 어노테이션을 사용하여 지정할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQtwI&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// Prototype Scope 지정
@Component
@Scope(&quot;prototype&quot;)
public class PrototypeBean {
}

// Singleton Scope (기본값이므로 실무에서는 명시적 선언을 생략합니다)
@Component
@Scope(&quot;singleton&quot;)
public class MemberService {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Scope 선택 시 주의할 점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;31&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,0,0&quot;&gt;Singleton Bean 내부 상태 저장 금지 (무상태 유지)&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Singleton Bean에 사용자별 상태 데이터를 저장하면, 여러 사용자가 하나의 객체를 공유하므로 데이터 오염 및 동시성 문제가 발생합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,1,0&quot;&gt;Prototype Bean 과도한 사용 자제&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;요청마다 객체를 생성하므로 남발할 경우 객체 생성 비용 및 메모리 부담이 커집니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;핵심 질문&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Q1. Bean Scope란 무엇인가요?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 컨테이너가 Bean을 생성하고 유지하며 소멸시키는 범위를 정의하는 정책입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Q2. Spring의 기본 Bean Scope는 무엇인가요?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Singleton Scope이다. 별도의 설정이 없으면 모든 Bean은 Singleton으로 등록됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Q3. Spring에서 자주 사용하는 Bean Scope에는 어떤 것이 있는가?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Singleton, Prototype, Request, Session, Application Scope가 대표적입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Q4. Scope를 잘못 선택하면 어떤 문제가 발생할 수 있나요?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Singleton에 사용자별 상태를 저장하면 데이터 공유 문제가 발생할 수 있으며, Prototype을 과도하게 사용하면 객체 생성 비용과 메모리 사용량이 증가할 수 있습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;0&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4.4 Singleton Scope&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;앞에서 Bean Scope는 Spring 컨테이너가 Bean을 생성하고 관리하는 범위를 의미한다는 것을 살펴보았습니다. Spring에서 가장 많이 사용하는 Scope는 &lt;b data-index-in-node=&quot;94&quot; data-path-to-node=&quot;1&quot;&gt;Singleton Scope&lt;/b&gt;입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실제로 Spring Boot 애플리케이션에서 Service, Repository, Controller, Configuration 등 대부분의 Bean은 Singleton으로 관리됩니다. 이번 장에서는 Singleton Scope의 동작 원리와 장점, 그리고 사용할 때 주의해야 할 사항을 알아봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. Singleton Scope란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Singleton Scope는 &lt;b data-index-in-node=&quot;17&quot; data-path-to-node=&quot;5&quot;&gt;Spring 컨테이너당 하나의 Bean 인스턴스만 생성&lt;/b&gt;하는 Scope입니다. &lt;br /&gt;즉, 동일한 Bean을 여러 번 요청하더라도 항상 같은 객체를 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnTQNm/dJMcaaMYt3A/AWGb8ZoArSVpPteioeT5Uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnTQNm/dJMcaaMYt3A/AWGb8ZoArSVpPteioeT5Uk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnTQNm/dJMcaaMYt3A/AWGb8ZoArSVpPteioeT5Uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnTQNm%2FdJMcaaMYt3A%2FAWGb8ZoArSVpPteioeT5Uk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;193&quot; height=&quot;230&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;모든 클라이언트는 동일한 MemberService 객체를 공유하여 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring에서 별도의 Scope를 지정하지 않으면 모든 Bean은 Singleton Scope로 등록됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. Singleton Bean 생성 과정 및 특징&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Singleton Bean 생성 과정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;애플리케이션이 시작되면 Spring 컨테이너는 Singleton Bean을 생성하고 내부 저장소(Cache)에 보관합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;Application Start&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0&quot;&gt;Spring Container 생성&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,2,0&quot;&gt;Singleton Bean 생성&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,3,0&quot;&gt;Bean 저장 (Singleton Cache)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,4,0&quot;&gt;애플리케이션 실행&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이후에는 Bean을 새로 생성하지 않고, 이미 생성된 객체를 재사용하여 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;같은 Bean을 여러 번 요청한 경우&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQ2QI&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;MemberService service1 = context.getBean(MemberService.class);
MemberService service2 = context.getBean(MemberService.class);

System.out.println(service1 == service2); // true
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;컨테이너에서 여러 번 조회해도 주소 값이 같은 동일한 객체임을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. Singleton Scope를 사용하는 이유와 장점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;요청이 들어올 때마다 객체를 새로 생성하면 객체 생성 비용과 메모리 사용량이 지속적으로 증가합니다. &lt;br /&gt;반면 Singleton Scope는 한 번만 생성하고 계속 재사용하므로 다음과 같은 강력한 장점을 가집니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;20&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,0,0&quot;&gt;메모리 사용량 감소:&lt;/b&gt; 객체를 단 하나만 생성하여 공유하므로 메모리를 매우 효율적으로 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,1,0&quot;&gt;객체 생성 비용 감소 및 성능 향상:&lt;/b&gt; 생성 비용이 큰 객체를 반복해서 만들지 않고 이미 생성된 객체를 재사용하므로 요청 처리 속도가 향상됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,2,0&quot;&gt;Spring의 설계 철학과 부합:&lt;/b&gt; Service나 Repository처럼 상태를 가지지 않는 객체는 여러 사용자가 동시에 공유해도 문제가 없으므로 Singleton 구조에 가장 적합합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4.가장 중요한 주의사항: 무상태(Stateless) 설계&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Singleton Bean은 여러 사용자(Thread)가 동시에 공유하는 객체입니다.&lt;/b&gt;&lt;br /&gt;따라서 &lt;b data-index-in-node=&quot;52&quot; data-path-to-node=&quot;23&quot;&gt;절대 내부 상태를 저장하는 Stateful 구조로 설계해서는 안 됩니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;❌ Stateful Bean의 문제점 (잘못된 예시)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQ2gI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Service
public class OrderService {

    private String currentUser; // ⚠️ 필드에 상태를 저장!

    public void order(String user) {
        currentUser = user;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;26&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;사용자 A가 주문을 진행하여 `currentUser = &quot;UserA&quot;`가 됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;동시 요청에 의해 사용자 B가 주문을 진행하면 `currentUser = &quot;UserB&quot;`로 값이 변경됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이때 사용자 A의 정보가 덮어씌워져 데이터 오염 및 심각한 동시성 장애가 발생합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;⭕ Stateless Bean 설계 (올바른 예시)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQ2wI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Service
public class OrderService {

    public void order(String user) {
        // 필드에 저장하지 않고 메서드 매개변수나 지역 변수로 처리합니다.
        System.out.println(user);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;29&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Singleton Bean은 T&lt;b&gt;hread Safety를 자동으로 보장하지 않습니다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;상태를 필드에 저장하지 않고 매개변수, 지역 변수, ThreadLocal 등을 활용하여 &lt;b data-index-in-node=&quot;49&quot; data-path-to-node=&quot;29,1,0&quot;&gt;Stateless하게 작성하는 것이 핵심 원칙&lt;/b&gt;입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5. 실무에서의 활용&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실무에서는 대부분의 Bean을 Singleton으로 관리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;33&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;33,0,0&quot;&gt;주요 대상:&lt;/b&gt; Controller, Service, Repository, Configuration, Utility Bean&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;33,1,0&quot;&gt;특징:&lt;/b&gt; 이러한 객체들은 상태를 저장하지 않고 요청을 처리하는 로직만 수행하므로 Singleton으로 관리하기에 가장 적합합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;36&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Singleton Scope는 Spring 컨테이너당 하나의 Bean만 생성하여 재사용하는 기본 Scope입니다. 객체 생성 비용을 줄이고 메모리를 효율적으로 사용할 수 있어 대부분의 Spring Bean은 Singleton으로 관리됩니다. 다만 Singleton Bean은 여러 요청이 하나의 객체를 공유하므로 상태를 저장하지 않는 Stateless 객체로 설계해야 합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 장에서는 Singleton과 반대로 Bean을 요청할 때마다 새로운 객체를 생성하는 Prototype Scope를 살펴봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;39&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;40&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Q1. Singleton Scope란 무엇인가요?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;41&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 컨테이너당 하나의 Bean 인스턴스만 생성하여 모든 요청에서 공유하는 Scope입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;42&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;42&quot;&gt;Q2. Spring의 기본 Bean Scope는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;43&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Singleton Scope입니다. 별도의 Scope를 지정하지 않으면 모든 Bean은 Singleton으로 등록됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;44&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44&quot;&gt;Q3. Singleton Bean을 Stateless하게 설계해야 하는 이유는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;45&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Singleton Bean은 여러 요청과 여러 스레드가 동일한 객체를 공유하므로, 필드에 상태를 저장하면 데이터가 덮어쓰이거나 동시성 문제가 발생할 수 있기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;46&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;46&quot;&gt;Q4. Singleton Scope의 장점은 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;47&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체를 한 번만 생성하여 재사용하므로 메모리 사용량과 객체 생성 비용을 줄일 수 있으며, 애플리케이션의 성능을 향상시킬 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;0&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4.5 Prototype Scope&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;앞에서 Singleton Scope는 Spring의 기본 Scope이며, 컨테이너당 하나의 Bean만 생성하여 모든 요청에서 공유한다는 것을 살펴보았습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그렇다면 매번 새로운 객체가 필요한 경우에는 어떻게 해야 할까요? 예를 들어 요청마다 독립적인 객체가 필요하거나, 상태를 일시적으로 저장하는 객체라면 Singleton보다 다른 Scope가 적합합니다. 이러한 경우 사용하는 것이 &lt;b data-index-in-node=&quot;128&quot; data-path-to-node=&quot;2&quot;&gt;Prototype Scope&lt;/b&gt;입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이번 장에서는 Prototype Scope의 동작 원리와 Singleton과의 차이점, 그리고 실무에서 사용할 때 주의해야 할 사항을 알아봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. Prototype Scope란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Prototype Scope는 &lt;b data-index-in-node=&quot;17&quot; data-path-to-node=&quot;6&quot;&gt;Bean을 요청할 때마다 새로운 객체를 생성하는 Scope&lt;/b&gt;입니다. Singleton처럼 하나의 객체를 공유하지 않고, 컨테이너에 Bean을 요청할 때마다 새로운 인스턴스를 생성하여 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;6&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bean 요청 ➔ Prototype Bean 생성 ➔ 반환&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;6&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다시&amp;nbsp;요청&amp;nbsp;➔&amp;nbsp;새로운&amp;nbsp;Prototype&amp;nbsp;Bean&amp;nbsp;생성&amp;nbsp;➔&amp;nbsp;반환&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Prototype Bean 등록 및 조회&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Scope(&quot;prototype&quot;) 어노테이션을 사용하여 지정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQgwM&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Component
@Scope(&quot;prototype&quot;)
public class PrototypeBean {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;컨테이너에서 같은 Bean을 두 번 조회해 보면 매번 새로운 객체가 생성되므로 서로 다른 주소 값을 가집니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQhAM&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;PrototypeBean bean1 = context.getBean(PrototypeBean.class);
PrototypeBean bean2 = context.getBean(PrototypeBean.class);

System.out.println(bean1 == bean2); // false
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. Singleton과 Prototype 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1) 객체 생성 시점의 차이&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;16&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,0,0&quot;&gt;Singleton:&lt;/b&gt; 애플리케이션 시작 시(Spring 컨테이너 생성 시) 객체를 생성하고 이후 계속 재사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,1,0&quot;&gt;Prototype:&lt;/b&gt; 애플리케이션 시작 시에는 생성되지 않고, &lt;b data-index-in-node=&quot;34&quot; data-path-to-node=&quot;16,1,0&quot;&gt;Bean을 요청하는 시점&lt;/b&gt;마다 새로 생성됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2) 생명주기(Lifecycle)의 차이&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,0,0&quot;&gt;Singleton:&lt;/b&gt; 생성 ➔ 의존성 주입 ➔ 초기화 ➔ 사용 ➔ 소멸까지 전체 생명주기를 Spring이 관리합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,1,0&quot;&gt;Prototype:&lt;/b&gt; 생성 ➔ 의존성 주입 ➔ 초기화까지만 Spring이 관리하며, &lt;b data-index-in-node=&quot;46&quot; data-path-to-node=&quot;18,1,0&quot;&gt;Bean을 반환한 이후에는 더 이상 Spring이 관리하지 않습니다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. Prototype Bean 사용 시 주의사항&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. @PreDestroy가 호출되지 않는 이유&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Prototype Bean에서는 @PreDestroy 소멸 콜백 메서드가 자동으로 실행되지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQhQM&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Component
@Scope(&quot;prototype&quot;)
public class PrototypeBean {

    @PreDestroy
    public void destroy() {
        System.out.println(&quot;Destroy&quot;); // ❌ 자동으로 호출되지 않습니다!
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 컨테이너는 Prototype Bean을 생성하고 초기화한 뒤 클라이언트에 반환하면 더 이상 관리 권한을 갖지 않습니다. 따라서 애플리케이션이 종료되더라도 소멸 메서드가 자동으로 실행되지 않으며, &lt;b data-index-in-node=&quot;116&quot; data-path-to-node=&quot;24&quot;&gt;자원 해제가 필요하다면 개발자가 직접 관리&lt;/b&gt;해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. Singleton Bean과 함께 사용할 때 발생하는 문제 (스프링 빈 주입)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실무에서 자주 실수하는 부분 중 하나는 Singleton Bean 내부에서 Prototype Bean을 직접 주입받는 경우입니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQhgM&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class MemberService {

    // Singleton Bean 생성 시점에 한 번만 주입됩니다!
    private final PrototypeBean prototypeBean; 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;28&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;MemberService는 Singleton이므로 애플리케이션 시작 시 딱 한 번만 생성됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 시점에 PrototypeBean도 함께 주입되므로, 이후 MemberService를 사용할 때마다 &lt;b data-index-in-node=&quot;57&quot; data-path-to-node=&quot;28,1,0&quot;&gt;동일한 PrototypeBean을 계속 사용&lt;/b&gt;하게 됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;즉, Prototype으로 설정했더라도 실제로는 Singleton처럼 동작하는 문제가 발생합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-path-to-node=&quot;29&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;해결 방법: ObjectProvider를 통한 지연 조회(DL)&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;매번 새로운 Prototype Bean을 생성하여 사용하고 싶다면, &lt;br /&gt;주입 대신 필요한 시점에&lt;b&gt; 지연 조회(Dependency Lookup)&lt;/b&gt;를 수행해야 합니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQhwM&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class MemberService {

    private final ObjectProvider&amp;lt;PrototypeBean&amp;gt; provider;

    public void execute() {
        // getObject()를 호출할 때마다 새로운 Prototype Bean이 생성됩니다.
        PrototypeBean bean = provider.getObject(); 
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h4 data-path-to-node=&quot;33&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. 실무에서의 활용&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Prototype Scope는 생각보다 실무에서 자주 사용되지 않습니다. 대부분의 &lt;b&gt;Spring Bean은 무상태(Stateless)로 설계되어 Singleton으로 충분하기 때문입니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;35&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;35,0,0&quot;&gt;적합한 사용 대상:&lt;/b&gt; 상태를 일정 시간 유지해야 하는 작업 객체, 매 요청마다 새로 조합해야 하는 Builder 객체, 일회성 처리 객체, 독립적인 테스트용 객체 등&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;대부분의 스프링 프로젝트에서는 Singleton을 중심으로 구성하며, 특수한 목적이 있을 때만 제한적으로 Prototype을 활용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;38&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Prototype Scope는 Bean을 요청할 때마다 새로운 객체를 생성하는 Scope입니다. Singleton과 달리 객체를 공유하지 않으며, 생성과 초기화까지만 Spring이 관리하고 이후의 생명주기는 관리하지 않습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;따라서 @PreDestroy가 자동으로 호출되지 않으며, 필요한 자원 해제는 개발자가 직접 수행해야 합니다. 또한 Singleton Bean에 Prototype Bean을 일반적인 방식으로 주입하면 한 번만 생성된 객체가 계속 사용되므로, 매번 새로운 객체가 필요하다면 ObjectProvider와 같은 지연 조회 기능을 사용하는 것이 적절합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 장에서는 웹 애플리케이션에서 자주 사용하는 Request Scope, Session Scope, Application Scope를 중심으로 Web Scope를 살펴봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;42&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;43&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;43&quot;&gt;Q1. Prototype Scope란 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;44&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bean을 요청할 때마다 새로운 인스턴스를 생성하여 반환하는 Scope입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;45&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;45&quot;&gt;Q2. Singleton Scope와 Prototype Scope의 가장 큰 차이점은 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;46&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Singleton은 하나의 Bean을 재사용하지만, Prototype은 Bean을 요청할 때마다 새로운 객체를 생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;47&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;47&quot;&gt;Q3. Prototype Bean에서 @PreDestroy가 자동으로 호출되지 않는 이유는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;48&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring은 Prototype Bean의 생성과 초기화까지만 관리하며, Bean을 반환한 이후의 생명주기는 관리하지 않기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;49&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;49&quot;&gt;Q4. Singleton Bean에 Prototype Bean을 주입하면 항상 새로운 Prototype Bean이 사용되는가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;50&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;아닙니다. Singleton Bean이 생성되는 시점에 한 번만 Prototype Bean이 주입됩니다. 매번 새로운 Prototype Bean이 필요하다면 ObjectProvider와 같은 지연 조회 방식을 사용해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;0&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4.6 Web Scope (Request, Session, Application)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;앞에서 Singleton Scope와 Prototype Scope를 살펴보았습니다. Singleton은 애플리케이션 전체에서 하나의 객체를 공유하고, Prototype은 요청할 때마다 새로운 객체를 생성합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;하지만 웹 애플리케이션에서는 이 두 Scope만으로 해결하기 어려운 경우가 있습니다. 예를 들어 HTTP 요청마다 다른 객체가 필요하거나, 로그인한 사용자별로 데이터를 관리해야 하는 경우가 있습니다. 이러한 상황을 위해 Spring은 &lt;b data-index-in-node=&quot;131&quot; data-path-to-node=&quot;2&quot;&gt;Web Scope&lt;/b&gt;를 제공합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이번 장에서는 Request Scope, Session Scope, Application Scope의 개념과 활용 방법을 알아봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. Web Scope란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Web Scope는 &lt;b data-index-in-node=&quot;11&quot; data-path-to-node=&quot;6&quot;&gt;웹 환경에서만 사용할 수 있는 Bean Scope&lt;/b&gt;입니다. &lt;br /&gt;HTTP 요청과 Session 같은 웹의 생명주기에 맞춰 Bean을 생성하고 관리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;7&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Scope&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;생성 기준&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;소멸 시점&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;7,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0,0&quot;&gt;Request&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;7,1,1,0&quot;&gt;HTTP 요청마다&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;7,1,2,0&quot;&gt;요청 종료 시&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;7,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,2,0,0&quot;&gt;Session&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;7,2,1,0&quot;&gt;HTTP Session마다&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;7,2,2,0&quot;&gt;Session 종료 시&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;7,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,3,0,0&quot;&gt;Application&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;7,3,1,0&quot;&gt;ServletContext마다&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;7,3,2,0&quot;&gt;애플리케이션 종료 시&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Singleton이나 Prototype과 달리 웹 요청의 흐름에 맞춰 Bean이 관리됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. Request Scope&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Request Scope는 &lt;b data-index-in-node=&quot;15&quot; data-path-to-node=&quot;11&quot;&gt;HTTP 요청마다 새로운 Bean을 생성&lt;/b&gt;하는 Scope입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;11&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`Request A ➔ RequestBean A`&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;11&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`Request B ➔ RequestBean B`&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;사용자 A와 사용자 B가 동시에 요청을 보내더라도 서로 다른 Bean을 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bean은 요청이 끝나면 함께 소멸됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Request Scope 등록&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Scope를 이용하거나 Spring에서 제공하는 축약 어노테이션인 @RequestScope를 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQtQM&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// 축약 어노테이션 사용 (권장)
@Component
@RequestScope
public class RequestBean {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Request Scope의 활용&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;요청마다 독립적인 데이터를 저장해야 하는 경우에 적합합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;19&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,0,0&quot;&gt;주요 사용 예시:&lt;/b&gt; 요청 ID(Request ID), 요청 시작 시간, 요청 로그, 사용자 요청 정보&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;각 요청은 자신만의 RequestLog 객체를 사용하므로 다른 요청과 데이터가 섞이지 않습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. Session Scope &amp;amp; Application Scope&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Session Scope&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;HTTP Session마다 하나의 Bean을 생성하는 Scope입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;11&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`User A Session ➔ SessionBean A`&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;11&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`User B Session ➔ SessionBean B`&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,0,0&quot;&gt;등록 방법:&lt;/b&gt; @SessionScope 어노테이션을 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,1,0&quot;&gt;주요 사용 예시:&lt;/b&gt; 로그인한 사용자 정보, 장바구니, 사용자 설정 등 사용자별 상태 관리&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Application Scope&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ServletContext당 하나의 Bean을 생성하는 Scope입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;28&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;28,0,0&quot;&gt;특징:&lt;/b&gt; Singleton과 비슷해 보이지만 Singleton은 Spring Container 기준이고, Application Scope는 ServletContext 기준입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;일반적인 Spring Boot 애플리케이션에서는 두 Scope의 차이를 체감할 일이 많지 않습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. Scope별 생명주기 및 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Scope별 생명주기 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;31&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Singleton&lt;/b&gt;:&amp;nbsp;&amp;nbsp;&amp;nbsp;Application 시작&amp;nbsp;&amp;nbsp;─────────────► Application 종료&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;31&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Request&lt;/b&gt;:&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;요청 시작&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;───────────────► 요청 종료&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;31&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Session&lt;/b&gt;:&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Session 생성&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;──────────────► Session 종료&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;31&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Application&lt;/b&gt;:&amp;nbsp;ServletContext&amp;nbsp;생성&amp;nbsp;───────────►&amp;nbsp;ServletContext&amp;nbsp;종료&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Singleton vs Request Scope 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;34&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Singleton&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Request&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;34,1,0,0&quot;&gt;하나의 객체 공유&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;34,1,1,0&quot;&gt;요청마다 새로운 객체&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;34,2,0,0&quot;&gt;모든 사용자가 공유&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;34,2,1,0&quot;&gt;요청마다 독립적인 객체&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;34,3,0,0&quot;&gt;상태 저장에 부적합&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;34,3,1,0&quot;&gt;요청 단위 상태 저장에 적합&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-path-to-node=&quot;36&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5. Singleton Bean에서 Request Scope Bean 사용 시 문제점&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Singleton Bean은 애플리케이션 시작 시 생성되지만, Request Bean은 Actual HTTP 요청이 들어와야 생성됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQugM&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class MemberService {

    // ⚠️ 애플리케이션 시작 시점에는 Request가 존재하지 않아 에러가 발생합니다!
    private final RequestBean requestBean; 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;39&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;애플리케이션 시작 시점에는 HTTP 요청이 없으므로 주입 단계에서 오류가 발생합니다. 실무에서는 이러한 문제를 해결하기 위해 Scoped Proxy (proxyMode = ScopedProxyMode.TARGET_CLASS)나 ObjectProvider를 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;41&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;6. 실무에서의 활용&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;42&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실무에서는 대부분의 Bean이 Singleton입니다. Web Scope는 다음과 같이 필요한 경우에만 제한적으로 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;43&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;43,0,0&quot;&gt;Request Scope:&lt;/b&gt; 요청 추적(Request Trace), 요청 로그, Correlation ID&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;43,1,0&quot;&gt;Session Scope:&lt;/b&gt; 로그인 사용자 정보, 장바구니&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;43,2,0&quot;&gt;Application Scope:&lt;/b&gt; ServletContext 공유 데이터, 애플리케이션 전역 속성&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;45&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;46&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Web Scope는 HTTP 요청과 Session 같은 웹 환경의 생명주기에 맞춰 Bean을 관리하는 Scope입니다. Request Scope는 요청마다 새로운 Bean을 생성하고, Session Scope는 사용자 Session마다 하나의 Bean을 생성하며, Application Scope는 ServletContext 단위로 Bean을 관리합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;대부분의 Spring Bean은 Singleton을 사용하지만, 요청별 또는 사용자별 상태를 안전하게 관리해야 하는 경우에는 Web Scope를 사용하는 것이 적절합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 장에서는 지금까지 학습한 내용을 바탕으로 Singleton Bean과 Prototype Bean의 차이점 및 사용 시 주의사항을 종합적으로 정리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;50&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;51&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Q1. Web Scope란 무엇인가?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;52&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;웹 애플리케이션에서 HTTP 요청과 Session 등의 생명주기에 맞추어 Bean을 생성하고 관리하는 Scope이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;53&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;53&quot;&gt;Q2. Request Scope와 Session Scope의 차이점은 무엇인가?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;54&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Request Scope는 HTTP 요청마다 새로운 Bean을 생성하며 요청 종료 시 소멸한다. Session Scope는 사용자 Session마다 하나의 Bean을 생성하며 Session 종료 시 소멸한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;55&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;55&quot;&gt;Q3. Singleton Bean에 Request Scope Bean을 바로 주입하면 어떤 문제가 발생할 수 있는가?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;56&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Singleton Bean은 애플리케이션 시작 시 생성되지만 Request Scope Bean은 HTTP 요청이 있어야 생성된다. 따라서 단순한 주입은 생성 시점의 차이로 인해 문제가 발생할 수 있으며, 일반적으로 Scoped Proxy나 ObjectProvider를 사용하여 해결한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;57&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;57&quot;&gt;Q4. Web Scope는 언제 사용하는가?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;58&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;요청별 로그 관리, 사용자별 로그인 정보, 장바구니, 요청 추적과 같이 요청이나 Session 단위의 상태를 관리해야 하는 경우에 사용한다.&lt;/span&gt;&lt;/p&gt;</description>
      <category> SpringBoot</category>
      <category>Spring</category>
      <author>limdaeil</author>
      <guid isPermaLink="true">https://limdaeil.tistory.com/87</guid>
      <comments>https://limdaeil.tistory.com/87#entry87comment</comments>
      <pubDate>Wed, 29 Jul 2026 15:02:12 +0900</pubDate>
    </item>
    <item>
      <title>Spring: Part 3. 의존성 주입(DI)과 활용</title>
      <link>https://limdaeil.tistory.com/86</link>
      <description>&lt;h2 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Part 3. 의존성 주입(DI)과 활용&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3.1 의존성 주입(DI)의 종류&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;앞서 DI(Dependency Injection)는 객체가 필요한 의존성을 직접 생성하지 않고 외부(Spring 컨테이너)로부터 전달받아 연결하는 메커니즘임을 살펴보았습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Framework는 객체 간의 의존성을 주입하기 위해 총 4가지 방식을 지원합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;생성자 주입 (Constructor Injection)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;수정자 주입 (Setter Injection)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,0&quot;&gt;필드 주입 (Field Injection)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,3,0&quot;&gt;일반 메서드 주입 (Method Injection)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;모든 방식이 기술적으로 동작은 가능하지만, &lt;b data-index-in-node=&quot;24&quot; data-path-to-node=&quot;7&quot;&gt;실무와 Spring 공식 문서에서는 생성자 주입을 절대적인 표준으로 권장&lt;/b&gt;합니다.&lt;br /&gt;각 주입 방식의 코드 형태와 특징, 그리고 왜 생성자 주입이 권장되는지 비교 분석해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 생성자 주입 (Constructor Injection)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;클래스의 &lt;b data-index-in-node=&quot;5&quot; data-path-to-node=&quot;10&quot;&gt;생성자를 통해 의존성을 전달받는 방식&lt;/b&gt;입니다. &lt;br /&gt;객체가 생성(인스턴스화)되는 시점에 생성자의 파라미터로 의존 대상 Bean이 전달됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ7AM&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    private final MemberRepository repository;

    // 생성자를 통한 의존성 주입
    public MemberService(MemberRepository repository) {
        this.repository = repository;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;주요 특징&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,0,0&quot;&gt;완전한 상태의 객체 보장:&lt;/b&gt; 객체가 생성되는 단 한 번의 시점에 의존성이 모두 전달되므로, 생성 직후부터 안전하게 사용할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,1,0&quot;&gt;final 키워드 사용 가능:&lt;/b&gt; 필드를 불변(Immutable) 상태로 유지할 수 있어 외부 변경 위험을 차단합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,2,0&quot;&gt;순환 참조 조기 발견:&lt;/b&gt; 애플리케이션 구동 시점(객체 생성 단계)에 순환 참조 에러를 즉시 감지할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 수정자 주입 (Setter Injection)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;의존성을 받아오는 &lt;b data-index-in-node=&quot;10&quot; data-path-to-node=&quot;16&quot;&gt;Setter(수정자) 메서드를 작성하고 상단에 @Autowired를 부착&lt;/b&gt;하여 주입받는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ7QM&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    private MemberRepository repository;

    @Autowired
    public void setRepository(MemberRepository repository) {
        this.repository = repository;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;동작 흐름&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-path-to-node=&quot;18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 컨테이너에서 객체 생성&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Setter 메서드 호출(`@Autowired`)&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;의존성 주입 완료&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;주요 특징&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;21&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,0,0&quot;&gt;주입 시점:&lt;/b&gt; 객체가 먼저 생성된 이후, Spring이 Setter 메서드를 호출하여 의존성을 넘겨줍니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,1,0&quot;&gt;장점:&lt;/b&gt; 런타임에 의존 대상을 유연하게 변경하거나 선택적으로 주입할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,2,0&quot;&gt;단점:&lt;/b&gt; 필드에 final을 사용할 수 없으며, 객체 생성 후 Setter 호출 전까지 의존성이 누락된 불완전한 상태(NullPointerException 위험)로 노출될 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. 필드 주입 (Field Injection)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;클래스의 &lt;b data-index-in-node=&quot;5&quot; data-path-to-node=&quot;24&quot;&gt;필드 선언부에 직접 @Autowired 어노테이션을 붙여&lt;/b&gt; Spring이 의존성을 바로 주입하도록 하는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ7wM&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    @Autowired
    private MemberRepository repository;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;주요 특징 및 치명적 단점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;27&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,0,0&quot;&gt;외견상 간결함:&lt;/b&gt; 코드가 매우 짧아 보여 초심자가 유혹받기 쉽습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,1,0&quot;&gt;Spring 컨테이너에 강하게 종속:&lt;/b&gt; 외부에서 의존성을 주입할 방법(생성자나 Setter)이 전혀 없어, &lt;b data-index-in-node=&quot;59&quot; data-path-to-node=&quot;27,1,0&quot;&gt;Spring 프레임워크 없이는 순수 자바 단위 테스트(Unit Test) 작성이 불가능&lt;/b&gt;합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,2,0&quot;&gt;final 선언 불가:&lt;/b&gt; 객체의 불변성을 보장할 수 없으며 의존 관계가 클래스 외부로 드러나지 않아 은폐됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ8AM&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 순수 자바 테스트 코드 작성 시 에러 발생 예시
MemberService service = new MemberService(); // repository는 null 상태!
service.signUp(); // NullPointerException 발생 (DI 불가)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. 일반 메서드 주입 (Method Injection)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Setter 메서드가 아닌 &lt;b data-index-in-node=&quot;15&quot; data-path-to-node=&quot;31&quot;&gt;임의의 일반 메서드에 @Autowired를 부착&lt;/b&gt;하여 여러 필드의 의존성을 한 번에 주입받는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ8QM&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    private MemberRepository repository;

    @Autowired
    public void init(MemberRepository repository) {
        this.repository = repository;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;주요 특징&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;34&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Setter 주입과 동작 방식 및 특징(객체 생성 후 주입, final 사용 불가)이 완전히 동일합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실무에서는 거의 사용되지 않는 방식입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;36&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;의존성 주입 4가지 방식 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 132px;&quot; border=&quot;1&quot; data-path-to-node=&quot;37&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;생성자 주입&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Setter 주입&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;필드 주입&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;일반 메서드 주입&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,1,0,0&quot;&gt;주입 시점&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,1,1,0&quot;&gt;객체 생성 시점&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,1,2,0&quot;&gt;객체 생성 완료 후&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,1,3,0&quot;&gt;객체 생성 완료 후&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,1,4,0&quot;&gt;객체 생성 완료 후&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,2,0,0&quot;&gt;final 사용 여부&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,2,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,2,1,0&quot;&gt;가능 (Immutable)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,2,2,0&quot;&gt;불가능&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,2,3,0&quot;&gt;불가능&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,2,4,0&quot;&gt;불가능&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,3,0,0&quot;&gt;순수 자바 테스트&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,3,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,3,1,0&quot;&gt;매우 용이함&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,3,2,0&quot;&gt;가능 (Setter 필요)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,3,3,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,3,3,0&quot;&gt;불가능&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,3,4,0&quot;&gt;가능 (메서드 필요)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,4,0,0&quot;&gt;의존성 필수 여부&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,4,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,4,1,0&quot;&gt;필수적 (Mandatory)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,4,2,0&quot;&gt;선택적 (Optional)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,4,3,0&quot;&gt;필수적&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,4,4,0&quot;&gt;선택적&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,5,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,5,0,0&quot;&gt;실무 권장도&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,5,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,5,1,0&quot;&gt;표준 (★★★★★)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,5,2,0&quot;&gt;제한적 (★★☆☆☆)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,5,3,0&quot;&gt;금지 (★☆☆☆☆)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;37,5,4,0&quot;&gt;미사용 (★☆☆☆☆)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-path-to-node=&quot;39&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring이 생성자 주입을 강력히 권장하는 핵심 이유&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-path-to-node=&quot;39&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체의 불변성(Immutability) 보장 (final 키워드 활용)&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;39&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;필수 의존성의 누락 없는 안전한 생성&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;39&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 컨테이너 없는 순수 자바 단위 테스트 작성 용이&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;39&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;순환 참조(Circular Dependency) 문제의 애플리케이션 시작 즉시 방지&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;41&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자 주입은 객체의 생명주기 중 &lt;b data-index-in-node=&quot;19&quot; data-path-to-node=&quot;41&quot;&gt;&quot;생성과 의존성 주입&quot;이 단 한 번에 완결&lt;/b&gt;되므로, 객체지향 설계 원칙에서 말하는 &lt;b data-index-in-node=&quot;64&quot; data-path-to-node=&quot;41&quot;&gt;안정적이고 견고한 불변 객체&lt;/b&gt;를 만드는 데 가장 완벽하게 부합합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;43&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;44&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44,0,0&quot;&gt;DI의 4가지 유형:&lt;/b&gt; 생성자 주입, Setter 주입, 필드 주입, 일반 메서드 주입이 존재합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44,1,0&quot;&gt;실무 선택 전략:&lt;/b&gt; &lt;b data-index-in-node=&quot;10&quot; data-path-to-node=&quot;44,1,0&quot;&gt;기본적으로 100% 생성자 주입을 사용&lt;/b&gt;합니다. 주입받을 의존성이 실행 중에 변경되거나 선택적(Optional)인 특수한 경우에만 제한적으로 Setter 주입을 고려합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44,2,0&quot;&gt;핵심 가치:&lt;/b&gt; 생성자 주입을 통해 final 키워드로 불변성을 확보하고, 컨테이너 독립적인 단위 테스트 환경을 쉽게 구축할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;45&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 파트에서는 생성자 주입이 왜 객체지향 아키텍처와 단위 테스트 관점에서 최고의 선택인지, &lt;b data-index-in-node=&quot;52&quot; data-path-to-node=&quot;45&quot;&gt;생성자 주입을 권장하는 심화 원리와 코드적 이점&lt;/b&gt;을 자세히 분석합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;47&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;48&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;48&quot;&gt;Q1. Spring에서 지원하는 대표적인 의존성 주입(DI) 방식 4가지는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;49,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;49,0&quot;&gt;생성자 주입&lt;/b&gt;, &lt;b data-index-in-node=&quot;8&quot; data-path-to-node=&quot;49,0&quot;&gt;수정자(Setter) 주입&lt;/b&gt;, &lt;b data-index-in-node=&quot;24&quot; data-path-to-node=&quot;49,0&quot;&gt;필드 주입&lt;/b&gt;, &lt;b data-index-in-node=&quot;31&quot; data-path-to-node=&quot;49,0&quot;&gt;일반 메서드 주입&lt;/b&gt;이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;50&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;50&quot;&gt;Q2. 실무에서 생성자 주입을 가장 많이 사용하고 권장하는 이유는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;51,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체 생성 시점에 필수 의존성을 모두 주입받아 완전한 상태의 객체를 보장할 수 있고, &lt;b data-index-in-node=&quot;48&quot; data-path-to-node=&quot;51,0&quot;&gt;final 키워드를 활용해 불변성(Immutability)을 확보&lt;/b&gt;할 수 있기 때문입니다. 또한 Spring 컨테이너에 의존하지 않고 순수 자바 코드로 단위 테스트를 쉽게 작성할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;52&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;52&quot;&gt;Q3. 필드 주입(@Autowired private MemberRepository repo;)을 사용하면 안 되는 이유는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;53,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;필드 주입은 Spring 컨테이너 없이는 외부에 의존성을 주입할 방법이 없어 &lt;b data-index-in-node=&quot;43&quot; data-path-to-node=&quot;53,0&quot;&gt;순수 자바 환경에서의 단위 테스트가 불가능&lt;/b&gt;해집니다. 또한 final 키워드를 쓸 수 없어 객체의 불변성이 깨지고 의존 관계가 외부에 명확히 드러나지 않는 문제점이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;54&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;54&quot;&gt;Q4. Setter 주입은 언제 활용할 수 있나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;55,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;의존 대상이 항상 존재하지 않고 &lt;b&gt;선택적(Optional)&lt;/b&gt;이거나, 애플리케이션 실행 중 의존성이 &lt;b data-index-in-node=&quot;57&quot; data-path-to-node=&quot;55,0&quot;&gt;동적으로 변경될 필요가 있는 경우&lt;/b&gt; 제한적으로 활용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3.2 왜 생성자 주입을 사용하는가?&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;앞서 Spring이 지원하는 4가지 의존성 주입 방식(생성자, Setter, 필드, 일반 메서드)을 살펴보았습니다. 기술적으로는 어떤 방식을 선택하더라도 의존성 주입 자체는 가능합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;하지만 Spring 공식 문서를 포함해 대부분의 실무 프로젝트에서는 생성자 주입(Constructor Injection)을 표준(Default)으로 채택하고 있습니다. 생성자 주입이 제공하는 객체지향 설계, 유지보수성, 단위 테스트 측면에서의 압도적인 이점을 자세히 분석해 보겠습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 객체를 완전한 상태로 생성 (불완전 상태 방지)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자 주입은 &lt;b data-index-in-node=&quot;8&quot; data-path-to-node=&quot;8&quot;&gt;객체가 인스턴스화되는 바로 그 순간&lt;/b&gt;에 필요한 모든 의존성을 전달받습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQkAQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    private final MemberRepository repository;

    public MemberService(MemberRepository repository) {
        this.repository = repository;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;주입 방식에 따른 상태 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;생성자 주입:&lt;/b&gt; MemberService가 메모리에 생성된 직후에는 이미 MemberRepository가 주입되어 있어 &lt;b data-index-in-node=&quot;66&quot; data-path-to-node=&quot;11,0,0&quot;&gt;언제나 즉시 사용 가능한 완전한 상태&lt;/b&gt;가 됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;Setter / 필드 주입:&lt;/b&gt; 객체가 먼저 생성된 후 의존성이 주입되므로, 객체 생성과 주입 사이의 미세한 시점에 의존성이 없는 불완전한 상태(Null 상태)로 존재할 위험이 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 필수 의존성 강제 및 명확한 표현&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자 주입은 객체를 생성하기 위해 &lt;b data-index-in-node=&quot;20&quot; data-path-to-node=&quot;14&quot;&gt;의존성 전달을 언어 차원에서 강제&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQkQQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 올바른 객체 생성 (의존성 전달 필수)
MemberRepository repository = new JpaMemberRepository();
MemberService service = new MemberService(repository);

// 컴파일 에러 발생 (의존성 미전달 시 생성 불가)
MemberService service = new MemberService(); // Compile Error!
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자 시그니처 자체가 &lt;b&gt;&lt;i&gt;&quot;이 클래스가 정상 동작하려면 반드시 이 의존성들이 필요하다&quot;&lt;/i&gt;&lt;/b&gt;라는 계약(Contract)을 명확히 명시하므로, 코드의 의도가 투명하게 드러납니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. final 키워드 활용과 불변성(Immutability) 보장&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자 주입을 사용하면 필드에 &lt;b data-index-in-node=&quot;17&quot; data-path-to-node=&quot;19&quot;&gt;final 키워드를 선언&lt;/b&gt;할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQkgQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;private final MemberRepository repository;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;final 선언의 장점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;22&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,0,0&quot;&gt;불변성 확보:&lt;/b&gt; 객체가 생성된 이후 런타임에 의존 대상이 무단으로 변경되는 것을 원천 차단합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,1,0&quot;&gt;오류 예방:&lt;/b&gt; 실수로 필드 값을 재할당하려는 코드가 작성되면 컴파일 시점에 즉시 감지됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,2,0&quot;&gt;안정성:&lt;/b&gt; Setter 주입처럼 service.setRepository(...)를 통해 실행 중 의존성이 바뀌는 위험을 방지합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. 객체의 의존 관계가 한눈에 파악됨&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;클래스 상단의 생성자 파라미터만 확인하면 해당 객체가 어떤 외부 객체들과 협력하는지 직관적으로 알 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQkwQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// 생성자 주입: 3개의 의존 관계가 한눈에 파악됨
public MemberService(
        MemberRepository repository,
        DiscountPolicy discountPolicy,
        EventPublisher eventPublisher) {
    this.repository = repository;
    this.discountPolicy = discountPolicy;
    this.eventPublisher = eventPublisher;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;반면 필드 주입은 @Autowired가 여러 필드에 흩어져 있어 클래스 규모가 커질수록 의존성을 한눈에 파악하기 어렵습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5. 프레임워크 없는 순수 자바 단위 테스트 용이성&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자 주입은 &lt;b data-index-in-node=&quot;8&quot; data-path-to-node=&quot;30&quot;&gt;Spring 컨테이너(IOC Container)를 가동하지 않고도&lt;/b&gt; 순수 자바 코드로 단위 테스트(Unit Test)를 작성하기 매우 쉽습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQlAQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Test
void signUpTest() {
    // Spring 없이 가짜(Fake/Mock) 객체를 생성자로 직접 주입
    MemberRepository repository = new FakeMemberRepository();
    MemberService service = new MemberService(repository);

    // 단위 테스트 수행
    service.signUp(new Member(&quot;userA&quot;));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;필드 주입의 경우 Spring 컨테이너 없이는 필드에 의존성을 넣을 방법이 없어 NullPointerException이 발생하지만, 생성자 주입은 테스트 프레임워크나 Mocking 라이브러리와의 결합도를 획기적으로 낮춰줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;6. 순환 참조(Circular Dependency)의 조기 발견&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;두 개 이상의 Bean이 서로를 참조하는 순환 참조 구조가 발생했을 때, 주입 방식에 따라 감지 시점이 다릅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wH3WC/dJMcagfjVo2/IpwkRaMSbpjICaX5nNb14K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wH3WC/dJMcagfjVo2/IpwkRaMSbpjICaX5nNb14K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wH3WC/dJMcagfjVo2/IpwkRaMSbpjICaX5nNb14K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwH3WC%2FdJMcagfjVo2%2FIpwkRaMSbpjICaX5nNb14K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;526&quot; height=&quot;136&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;37&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,0,0&quot;&gt;Setter / 필드 주입:&lt;/b&gt; 객체 생성이 완료된 후 주입되므로, 해당 메서드가 실제 호출되는 &lt;b data-index-in-node=&quot;52&quot; data-path-to-node=&quot;37,0,0&quot;&gt;런타임 시점&lt;/b&gt;에 StackOverflowError가 발생합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,1,0&quot;&gt;생성자 주입:&lt;/b&gt; 객체를 생성하는 단계에서 서로의 생성자를 호출하다가 애플리케이션 구동 시점(부팅 단계)에 Spring이 순환 참조를 감지하고 구동을 즉시 중단합니다. (실패를 가장 빠르게 감지)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;39&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;7. 객체지향 설계 원칙(DIP / SRP)과의 부합&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;객&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;체지향 관점에서의 생성자 주입 이점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;41&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;DIP (의존역전 원칙):&lt;/b&gt; 인터페이스 타입에 의존하고, &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;구현체 생성 및 주입은 외부(Spring)에 전적으로 위임&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;41&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;SRP (단일 책임 원칙):&lt;/b&gt; 생성자 파라미터가 너무 많다면, &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;클래스의 책임이 과도하다는 코드 악취(Code Smell) 감지&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;41&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자 매개변수가 너무 많아진다면?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;42&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;파라미터가 5~6개 이상으로 늘어난다면 이는 생성자 주입의 불편함이 아니라, &lt;b data-index-in-node=&quot;43&quot; data-path-to-node=&quot;42&quot;&gt;해당 클래스가 단일 책임 원칙(SRP)을 위배하고 있다는 경고 신호&lt;/b&gt;입니다. 생성자 주입은 이처럼 설계상의 문제점을 조기에 수면 위로 올려 리팩토링을 유도하는 긍정적인 역할을 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;44&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;45&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;관점&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자 주입이 제공하는 가치&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;45,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;45,1,0,0&quot;&gt;객체 상태&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;45,1,1,0&quot;&gt;생성 시점에 의존성이 완비되어 불완전 객체 상태 방지&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;45,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;45,2,0,0&quot;&gt;안정성&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;45,2,1,0&quot;&gt;final 키워드를 통한 런타임 불변성(Immutability) 확보&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;45,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;45,3,0,0&quot;&gt;테스트&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;45,3,1,0&quot;&gt;Spring 컨테이너 없이 순수 자바 및 Mock 객체 기반 단위 테스트 용이&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;45,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;45,4,0,0&quot;&gt;설계 개선&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;45,4,1,0&quot;&gt;순환 참조 조기 발견 및 과도한 매개변수를 통한 SRP 위반 감지&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-path-to-node=&quot;46&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자 주입은 단순히 'Spring의 한 가지 기능'이 아니라, &lt;b data-index-in-node=&quot;36&quot; data-path-to-node=&quot;46&quot;&gt;객체지향적인 코드를 작성하도록 강제해 주는 안전장치&lt;/b&gt;입니다. 실무에서는 특별한 예외가 없는 한 항상 생성자 주입을 기본으로 채택합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 파트에서는 Spring이 여러 생성자 중 어떤 생성자를 선택하여 의존성을 주입하는지, 그리고 내부적으로 @Autowired가 어떻게 동작하는지 메커니즘을 상세히 다룹니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;49&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;50&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;50&quot;&gt;Q1. Spring 프로젝트에서 생성자 주입을 권장하는 핵심 이유는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;51,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체 생성 시점에 필수 의존성이 모두 전달되어 &lt;b data-index-in-node=&quot;26&quot; data-path-to-node=&quot;51,0&quot;&gt;완전한 상태의 객체를 보장&lt;/b&gt;하고, &lt;b data-index-in-node=&quot;44&quot; data-path-to-node=&quot;51,0&quot;&gt;final 키워드를 통해 불변성&lt;/b&gt;을 가질 수 있기 때문입니다. 또한 Spring 컨테이너 없이도 &lt;b data-index-in-node=&quot;97&quot; data-path-to-node=&quot;51,0&quot;&gt;단위 테스트 작성이 용이&lt;/b&gt;하며, 애플리케이션 &lt;b data-index-in-node=&quot;121&quot; data-path-to-node=&quot;51,0&quot;&gt;구동 시점에 순환 참조를 조기 발견&lt;/b&gt;할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;52&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;52&quot;&gt;Q2. 생성자 주입 시 필드에 final 키워드를 선언하는 이유는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;53,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;의존성 필드가 생성 시점 이후 런타임에 실수로 변경되거나 재할당되는 것을 방지하여 &lt;b data-index-in-node=&quot;46&quot; data-path-to-node=&quot;53,0&quot;&gt;객체의 불변성과 상태 안정성을 확보&lt;/b&gt;하기 위해서입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;54&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;54&quot;&gt;Q3. 생성자 주입이 단위 테스트(Unit Test) 작성에 주는 이점은 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;55,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@SpringBootTest 같은 무거운 프레임워크 환경을 띄우지 않고도, &lt;b data-index-in-node=&quot;42&quot; data-path-to-node=&quot;55,0&quot;&gt;순수 자바 코드(new)로 가짜 객체(Mock/Fake)를 생성자에 직접 전달&lt;/b&gt;하여 매우 빠르고 독립적인 단위 테스트를 작성할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;56&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;56&quot;&gt;Q4. 생성자의 매개변수가 너무 많아지는 문제(예: 5개 이상)는 어떻게 해석하고 해결해야 하나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;57,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이는 생성자 주입 방식 자체의 문제가 아니라, 해당 클래스가 너무 많은 역할을 수행하여 &lt;b&gt;단일 책임 원칙(SRP)을 위배하고 있다는 신호(Code Smell)&lt;/b&gt;입니다. 클래스의 책임을 더 작은 단위로 분리하고 재설계해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3.3 @Autowired와 의존성 자동 주입&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;앞서 생성자 주입이 Spring에서 가장 권장되는 표준 방식이라는 점을 확인했습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그그렇다면 Spring 컨테이너는 객체를 생성할 때 생성자의 매개변수를 어떻게 파악하고, 적절한 Bean을 찾아 전달해 줄까요? 이 핵심 역할을 수행하는 어노테이션이 바로 @Autowired입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Autowired는 Spring이 관리하는 IoC 컨테이너 내부에서 필요한 타입의 Bean을 자동으로 검색하여 해당 위치에 주입(Injection)해 주는 메커니즘입니다. 이번 파트에서는 @Autowired의 동작 원리와 자동 주입 과정에서 적용되는 주요 규칙을 살펴봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. @Autowired의 기본 사용법&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Autowired는 Spring에게 &lt;b&gt;&lt;i&gt;&quot;이 위치에 필요한 의존성 Bean을 자동으로 찾아 주입하라&quot;&lt;/i&gt;&lt;/b&gt;는 지시를 전달합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQyAQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    private final MemberRepository repository;

    // 생성자에 @Autowired 부착
    @Autowired
    public MemberService(MemberRepository repository) {
        this.repository = repository;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 컨테이너가 생성될 때 MemberRepository 타입의 Bean을 찾아 MemberService 생성자의 매개변수로 자동 전달하므로, 개발자가 직접 new 키워드로 객체를 생성하고 조립할 필요가 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 생성자가 단 하나일 때의 @Autowired 생략 규칙&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14&quot;&gt;Spring 4.3 버전부터는 클래스 내에 생성자가 단 하나만 존재할 경우 @Autowired 어노테이션을 생략&lt;/b&gt;할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQyQQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    private final MemberRepository repository;

    // 생성자가 1개이므로 @Autowired 생략 가능 (실무 표준 방식)
    public MemberService(MemberRepository repository) {
        this.repository = repository;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자가 하나뿐이라면 Spring은 해당 생성자를 의존성 주입의 유일한 창구로 판단하여 자동으로 주입을 진행합니다. &lt;br /&gt;&lt;b data-index-in-node=&quot;65&quot; data-path-to-node=&quot;16&quot;&gt;실무 프로젝트에서는 이 생략 패턴을 기본 표준&lt;/b&gt;으로 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. @Autowired를 적용할 수 있는 4가지 위치&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Autowired는 클래스 내부의 다양한 위치에 선언할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQygQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;// 1. 생성자 (Constructor Injection) - [가장 권장]
@Autowired
public MemberService(MemberRepository repository) {
    this.repository = repository;
}

// 2. 수정자 (Setter Injection) - [선택적 의존성에 사용]
@Autowired
public void setRepository(MemberRepository repository) {
    this.repository = repository;
}

// 3. 필드 (Field Injection) - [권장하지 않음]
@Autowired
private MemberRepository repository;

// 4. 일반 메서드 (Method Injection) - [사용 빈도 매우 낮음]
@Autowired
public void init(MemberRepository repository) {
    this.repository = repository;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. 의존성 자동 주입의 내부 동작 과정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음과 같은 서비스 클래스와 인터페이스 구현체가 존재할 때, Spring 컨테이너 내부에서는 다음과 같은 순서로 자동 주입이 완료됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQywQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Repository
public class JpaMemberRepository implements MemberRepository {
    // MemberRepository의 구현체 Bean
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;자동 주입 프로세스&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-path-to-node=&quot;25&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;MemberService 객체 생성 요청&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;25&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자 매개변수의 '타입' 확인 (MemberRepository)&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;25&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 컨테이너 내에서 해당 '타입'을 지원하는 Bean 검색&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;25&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;JpaMemberRepository Bean 발견&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;25&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;MemberService 생성자 매개변수로 해당 Bean 객체 주입&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQzAQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,0&quot;&gt;핵심 원칙:&lt;/b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; letter-spacing: 0px;&quot;&gt; Spring의 자동 주입(@Autowired)은 기본적으로 &lt;/span&gt;&lt;b data-index-in-node=&quot;40&quot; data-path-to-node=&quot;27,0&quot;&gt;타입(Type)을 최우선 기준&lt;/b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; letter-spacing: 0px;&quot;&gt;으로 의존 대상을 탐색합니다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5. 자동 주입 시 발생할 수 있는 예외 상황&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1) 같은 타입의 Bean이 2개 이상 등록된 경우 (조회 빈이 2개 이상)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQzQQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Repository
public class JpaMemberRepository implements MemberRepository {}

@Repository
public class MemoryMemberRepository implements MemberRepository {}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1098&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpekIA/dJMcagfjVDK/YKMx21KBK6EVVmJyNKJW60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpekIA/dJMcagfjVDK/YKMx21KBK6EVVmJyNKJW60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpekIA/dJMcagfjVDK/YKMx21KBK6EVVmJyNKJW60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpekIA%2FdJMcagfjVDK%2FYKMx21KBK6EVVmJyNKJW60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;632&quot; height=&quot;288&quot; data-origin-width=&quot;1098&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;MemberRepository 구현체가 2개 이상 Bean으로 등록되어 있다면, Spring은 어떤 객체를 주입해야 할지 판단하지 못해 애플리케이션 시작 시점에 NoUniqueBeanDefinitionException 예외를 던집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2) 주입할 Bean이 컨테이너에 존재하지 않는 경우&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자 매개변수로 요구하는 타입의 Bean이 Spring 컨테이너에 하나도 등록되어 있지 않다면, 주입을 진행할 수 없어 UnsatisfiedDependencyException 예외가 발생하며 구동이 즉시 중단됩니다. 이는 객체가 불완전한 상태로 구동되는 것을 방지하기 위한 안전장치입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;6. 의존성 자동 주입의 핵심 이점&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Autowired 자동 주입의 장점&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-path-to-node=&quot;37&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체 생명주기 관리 자동화 (조립 및 연결 코드 제거)&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;37&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;결합도(Coupling) 감소 (인터페이스 기반 유연한 설계)&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;37&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;높은 보수성 (구현체가 변경되어도 서비스 로직 수정 없음)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;37&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;DIP(의존역전 원칙)의 자연스러운 충족&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;40&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;41&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,0,0&quot;&gt;@Autowired 기능:&lt;/b&gt; Spring 컨테이너에서 &lt;b data-index-in-node=&quot;29&quot; data-path-to-node=&quot;41,0,0&quot;&gt;타입(Type)을 기준&lt;/b&gt;으로 적절한 Bean을 찾아 의존성을 자동 주입하는 어노테이션입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,1,0&quot;&gt;생성자 생략 규칙:&lt;/b&gt; 단일 생성자 구조에서는 @Autowired를 생략하더라도 자동으로 생성자 주입이 동작합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,2,0&quot;&gt;예외 발생 조건:&lt;/b&gt; 주입하려는 타입의 Bean이 컨테이너에 전혀 없거나, 반대로 2개 이상 존재하여 충돌이 발생하면 구동 시점에 예외가 발생합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;42&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;동일한 타입의 Bean이 2개 이상일 때 발생하는 충돌 문제는 &lt;b data-index-in-node=&quot;35&quot; data-path-to-node=&quot;42&quot;&gt;@Primary와 @Qualifier&lt;/b&gt; 어노테이션을 통해 해결할 수 있습니다. &lt;br /&gt;다음 파트에서 그 사용법과 우선순위를 상세히 알아봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;44&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;45&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;45&quot;&gt;Q1. @Autowired 어노테이션의 역할과 기본 주입 탐색 기준은 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;46,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring IoC 컨테이너가 관리하는 Bean 중 필요한 의존성을 찾아 자동으로 주입해 주는 역할을 하며, 기본적으로 &lt;b data-index-in-node=&quot;67&quot; data-path-to-node=&quot;46,0&quot;&gt;타입(Type)을 기준으로 Bean을 검색&lt;/b&gt;하여 주입합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;47&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;47&quot;&gt;Q2. 생성자 주입 시 @Autowired를 생략할 수 있는 조건은 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;48,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;클래스 내에 &lt;b data-index-in-node=&quot;7&quot; data-path-to-node=&quot;48,0&quot;&gt;생성자가 단 하나만 존재&lt;/b&gt;할 경우, Spring 4.3부터는 @Autowired 어노테이션을 생략해도 자동으로 의존성 주입이 수행됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;49&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;49&quot;&gt;Q3. 동일한 인터페이스를 구현한 Bean이 2개 이상 등록되어 있을 때 @Autowired를 사용하면 어떻게 되나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;50,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring이 어떤 Bean을 주입할지 결정할 수 없어 &lt;b data-index-in-node=&quot;31&quot; data-path-to-node=&quot;50,0&quot;&gt;NoUniqueBeanDefinitionException 예외가 발생&lt;/b&gt;하며 애플리케이션 구동에 실패합니다. 이를 해결하려면 @Primary나 @Qualifier를 사용해 우선순위나 이름을 명시해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;51&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;51&quot;&gt;Q4. 주입받으려는 Bean이 Spring 컨테이너에 등록되어 있지 않은 경우 어떻게 동작하나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;52,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;의존성 주입을 완료할 수 없으므로 &lt;b data-index-in-node=&quot;19&quot; data-path-to-node=&quot;52,0&quot;&gt;UnsatisfiedDependencyException 예외가 발생&lt;/b&gt;하며 애플리케이션 시작 단계에서 구동이 즉시 중단됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3.4 @Primary와 @Qualifier&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;앞서 @Autowired는 기본적으로 타입(Type)을 기준으로 적절한 Bean을 탐색하여 자동 주입을 진행한다는 점을 살펴보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그렇다면 동일한 인터페이스를 구현한 Bean이 둘 이상 등록되어 있다면 Spring은 어떻게 동작할까요? 예를 들어 MemberRepository 인터페이스의 구현체 Bean이 2개라면, Spring은 어떤 Bean을 선택해야 할지 결정할 수 없어 애플리케이션 시작 단계에서 예외를 발생시킵니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Framework는 이러한 Bean 충돌 문제를 명확하게 해결할 수 있도록 @Primary와 @Qualifier라는 두 가지 핵심 어노테이션을 제공합니다. 이번 파트에서는 동일한 타입의 Bean이 여러 개 존재하는 상황에서 주입 우선순위를 제어하는 방식을 다룹니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 동일한 타입의 Bean 충돌 상황&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음과 같이 MemberRepository를 구현한 2개의 클래스가 모두 @Repository로 선언되어 Spring Bean으로 등록된 상황을 가정해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ-AQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public interface MemberRepository {}

@Repository
public class JpaMemberRepository implements MemberRepository {}

@Repository
public class MemoryMemberRepository implements MemberRepository {}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 상태에서 MemberService에 MemberRepository 타입의 의존성을 주입하려 시도하면 충돌이 발생합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ-QQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    private final MemberRepository repository;

    // MemberRepository 타입 Bean이 2개이므로 충돌 발생!
    public MemberService(MemberRepository repository) {
        this.repository = repository;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ-gQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3llhi/dJMcadbJ5cf/CKAgGhOWeL7SJrEA6FHAfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3llhi/dJMcadbJ5cf/CKAgGhOWeL7SJrEA6FHAfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3llhi/dJMcadbJ5cf/CKAgGhOWeL7SJrEA6FHAfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3llhi%2FdJMcadbJ5cf%2FCKAgGhOWeL7SJrEA6FHAfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;506&quot; height=&quot;313&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. @Primary: 기본(Default) Bean 지정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Primary는 동일한 타입의 여러 Bean 중 &lt;b data-index-in-node=&quot;28&quot; data-path-to-node=&quot;16&quot;&gt;우선적으로 주입될 기본 Bean을 지정&lt;/b&gt;하는 어노테이션입니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ-wQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Repository
@Primary // 기본 주입 대상 지정
public class JpaMemberRepository implements MemberRepository {}

@Repository
public class MemoryMemberRepository implements MemberRepository {}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ_AQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    private final MemberRepository repository;

    public MemberService(MemberRepository repository) {
        this.repository = repository; // @Primary가 붙은 JpaMemberRepository 선택
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Primary 주요 특징 및 활용처&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;20&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,0,0&quot;&gt;기본 전략(Default Strategy) 설정:&lt;/b&gt; 주입받는 코드 측에 별도의 어노테이션을 붙이지 않아도 자동으로 @Primary가 선언된 Bean이 채택됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,1,0&quot;&gt;실무 활용:&lt;/b&gt; 대부분의 로직에서 Main Database(예: JPA)를 활용하고, 아주 예외적인 상황에서만 별도의 저장소(예: In-Memory/Redis)를 사용할 때 가장 깔끔한 해결책입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. @Qualifier: 특정 Bean 명시적 지정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Qualifier는 주입할 Bean의 &lt;b data-index-in-node=&quot;22&quot; data-path-to-node=&quot;23&quot;&gt;고유 식별자(이름)를 지정하여 특정 Bean을 명확하게 핑포인트로 선택&lt;/b&gt;하는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ_QQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// 1. Bean 등록 측에 Qualifier 이름 부여
@Repository
@Qualifier(&quot;mainRepository&quot;)
public class JpaMemberRepository implements MemberRepository {}

@Repository
@Qualifier(&quot;tempRepository&quot;)
public class MemoryMemberRepository implements MemberRepository {}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ_gQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// 2. 주입받는 생성자 파라미터에 Qualifier 지정
@Service
public class MemberService {

    private final MemberRepository repository;

    public MemberService(@Qualifier(&quot;mainRepository&quot;) MemberRepository repository) {
        this.repository = repository; // mainRepository 명시적 선택
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;@Qualifier 동작 프로세스&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-path-to-node=&quot;26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;MemberRepository 타입 검색&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Qualifier(&quot;mainRepository&quot;) 식별자 확인 &lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;해당 Qualifier 이름을 가진 JpaMemberRepository 핀포인트 선택&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. @Primary와 @Qualifier 우선순위 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;만약 @Primary와 @Qualifier가 동시에 적용된 환경이라면 Spring은 어떤 Bean을 선택할까요?&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQgAU&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Repository
@Primary
public class JpaMemberRepository implements MemberRepository {}

@Repository
@Qualifier(&quot;memoryRepo&quot;)
public class MemoryMemberRepository implements MemberRepository {}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQgQU&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    private final MemberRepository repository;

    public MemberService(@Qualifier(&quot;memoryRepo&quot;) MemberRepository repository) {
        this.repository = repository; // memoryRepo 주입
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote data-path-to-node=&quot;33,0&quot; data-ke-style=&quot;style2&quot;&gt;우선순위 원칙: @Qualifier가 @Primary보다 더 높은 우선순위를 가집니다.&lt;/blockquote&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring은 &lt;b data-index-in-node=&quot;8&quot; data-path-to-node=&quot;34&quot;&gt;더 상세하고 구체적인 지시&lt;/b&gt;를 우선시합니다. 메인 환경 전체에 적용되는 기본값(@Primary)보다 개발자가 특정 주입 지점에 직접 지정한 매칭 구문(@Qualifier)의 결합력이 더 강력하기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;탐색 및 선택 우선순위&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;35&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1순위: @Qualifier 지정을 통한 핀포인트 매칭&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;35&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;(미지정 시) 2순위: @Primary가 부여된 대표 Bean 선택&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;35&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;(미선택&amp;nbsp;시) 3순위:&amp;nbsp;파라미터&amp;nbsp;변수명과&amp;nbsp;일치하는&amp;nbsp;Bean&amp;nbsp;이름&amp;nbsp;매칭&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;38&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5. 실무에서의 모범 활용 패턴 (Best Practice)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;39&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실무 프로젝트에서는 시스템 전체에 공통으로 적용되는 주 스토리지와 보조 스토리지를 함께 운용할 때 두 어노테이션의 조합을 적극적으로 활용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;362&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWzGQq/dJMcaiEeIDy/SntRBPne7q0YdTFIy2RhDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWzGQq/dJMcaiEeIDy/SntRBPne7q0YdTFIy2RhDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWzGQq/dJMcaiEeIDy/SntRBPne7q0YdTFIy2RhDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWzGQq%2FdJMcaiEeIDy%2FSntRBPne7q0YdTFIy2RhDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;532&quot; height=&quot;219&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;362&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;41&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,0,0&quot;&gt;메인 로직:&lt;/b&gt; 대부분의 결제 서비스에는 기본값으로 동작할 @Primary Bean을 주입하여 간결함을 유지합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,1,0&quot;&gt;특수 로직:&lt;/b&gt; 포인트 결제 등 명확하게 특정 구현체가 필요한 일부 서비스 클래스에서만 @Qualifier(&quot;pointPay&quot;)를 지정하여 수동으로 채택합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;43&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;44&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;@Primary&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;@Qualifier&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;44,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44,1,0,0&quot;&gt;개념&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;44,1,1,0&quot;&gt;대표 기본 Bean 선언&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;44,1,2,0&quot;&gt;특정 Bean을 고유 이름으로 직접 지정&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;44,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44,2,0,0&quot;&gt;설정 위치&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;44,2,1,0&quot;&gt;Bean 클래스 선언부&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;44,2,2,0&quot;&gt;Bean 클래스 선언부 + 주입받는 위치&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;44,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44,3,0,0&quot;&gt;우선순위&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;44,3,1,0&quot;&gt;보통 (2순위)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;44,3,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44,3,2,0&quot;&gt;높음 (1순위)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;44,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44,4,0,0&quot;&gt;장점&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;44,4,1,0&quot;&gt;주입받는 코드가 깔끔해짐&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;44,4,2,0&quot;&gt;오해 없이 주입 대상을 명확하게 지정 가능&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;44,5,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44,5,0,0&quot;&gt;추천 케이스&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;44,5,1,0&quot;&gt;메인/서브 관계가 명확할 때 (메인 지정용)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;44,5,2,0&quot;&gt;여러 구현체가 자주 개별 선택되는 케이스&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-path-to-node=&quot;45&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;동일한 타입의 Bean이 여러 개 있을 때는 기본값으로 사용될 메인 Bean에 @Primary를 붙이고, 특별한 용도로 구분해서 사용하는 Bean들에 @Qualifier를 부여하여 관리하는 것이 코드의 가독성과 유지보수성을 극대화하는 표준 접근법입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;46&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 파트에서는 생성자 주입 코드를 획기적으로 줄여주는 Lombok의 @RequiredArgsConstructor를 활용한 간결한 DI 구현법을 알아봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;48&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;49&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;49&quot;&gt;Q1. 동일한 타입의 Bean이 여러 개 등록되어 있을 때 주입 시 어떤 문제가 발생하나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;50,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring이 어떤 Bean을 선택해야 할지 판단할 수 없어 애플리케이션 시작 단계(구동 시점)에서 &lt;b data-index-in-node=&quot;56&quot; data-path-to-node=&quot;50,0&quot;&gt;NoUniqueBeanDefinitionException 예외가 발생&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;51&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;51&quot;&gt;Q2. @Primary와 @Qualifier의 차이점은 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;52,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Primary는 여러 Bean 중 &lt;b data-index-in-node=&quot;20&quot; data-path-to-node=&quot;52,0&quot;&gt;기본(Default)으로 채택될 메인 Bean&lt;/b&gt;을 설정하여 주입받는 코드의 간결성을 유지해 줍니다. 반면 @Qualifier는 주입받는 지점에 &lt;b data-index-in-node=&quot;99&quot; data-path-to-node=&quot;52,0&quot;&gt;직접 이름을 명시하여 특정 Bean을 핀포인트로 선택&lt;/b&gt;하는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;53&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;53&quot;&gt;Q3. @Primary와 @Qualifier가 동시에 설정된 경우 어떤 어노테이션이 우선적으로 적용되나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;54,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;54,0&quot;&gt;@Qualifier가 더 높은 우선순위&lt;/b&gt;를 가집니다. Spring은 더 구체적이고 명시적으로 지정된 지시어(@Qualifier)를 기본 설정(@Primary)보다 우선하여 처리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;55&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;55&quot;&gt;Q4. 실무에서는 두 어노테이션을 어떻게 조합하여 사용하는 것이 권장되나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;56,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;자주 사용되는 대표 구현체(예: 메인 DB Repository)에는 &lt;b data-index-in-node=&quot;38&quot; data-path-to-node=&quot;56,0&quot;&gt;@Primary를 적용&lt;/b&gt;해 주입받는 코드를 깔끔하게 유지하고, 보조적이거나 특수한 목적으로 사용되는 구현체에는 &lt;b data-index-in-node=&quot;99&quot; data-path-to-node=&quot;56,0&quot;&gt;@Qualifier를 사용&lt;/b&gt;해 필요한 곳에서만 명시적으로 선택하도록 설계합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3.5 @RequiredArgsConstructor와 Lombok&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;앞서 생성자 주입이 Spring에서 가장 권장되는 표준 방식이며, final 키워드를 활용해 불변성과 안전성을 동시에 확보할 수 있음을 확인했습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;하지만 프로젝트 규모가 커지고 클래스가 참조하는 의존성이 늘어날수록 &lt;b data-index-in-node=&quot;38&quot; data-path-to-node=&quot;5&quot;&gt;매번 동일한 형태의 생성자 코드를 반복해서 작성&lt;/b&gt;해야 하는 번거로움이 발생합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이러한 반복적인 보일러플레이트(Boilerplate) 코드를 획기적으로 줄여주는 자바 라이브러리가 바로 &lt;b data-index-in-node=&quot;58&quot; data-path-to-node=&quot;6&quot;&gt;Lombok&lt;/b&gt;이며, 의존성 주입에서는 &lt;b data-index-in-node=&quot;78&quot; data-path-to-node=&quot;6&quot;&gt;@RequiredArgsConstructor&lt;/b&gt; 어노테이션이 실무 표준으로 사용됩니다. 이번 파트에서는 Lombok을 활용해 생성자 주입 코드를 단축하는 메커니즘을 다룹니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. Lombok이란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Lombok은 자바 클래스 작성 시 상투적으로 반복되는 Getter, Setter, Constructor, ToString, Builder 등의 코드를 컴파일 시점에 &lt;b data-index-in-node=&quot;93&quot; data-path-to-node=&quot;9&quot;&gt;어노테이션 기반으로 자동 생성&lt;/b&gt;해 주는 라이브러리입니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Boot 프로젝트 설정 시 필수적으로 추가하는 핵심 라이브러리 중 하나입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 생성자를 직접 작성하는 경우의 한계&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Lombok 없이 보편적인 생성자 주입 방식으로 서비스를 구현하면 다음과 같이 작성하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-hveid=&quot;3&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    private final MemberRepository repository;
    private final DiscountPolicy discountPolicy;

    // 생성자를 통한 직접 주입
    public MemberService(MemberRepository repository, DiscountPolicy discountPolicy) {
        this.repository = repository;
        this.discountPolicy = discountPolicy;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;의존성이 2개일 때는 문제가 되지 않지만, 협력 객체가 늘어날수록 다음과 같이 반복적인 필드 대입 생서자 코드가 점차 길어지게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-hveid=&quot;6&quot;&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;private final MemberRepository repository;
private final DiscountPolicy discountPolicy;
private final MailSender mailSender;
private final EventPublisher eventPublisher;
private final CacheManager cacheManager;

// 필드가 추가될 때마다 생성자 매개변수와 내부 대입 코드를 매번 수정해야 함!
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. @RequiredArgsConstructor를 통한 극적인 코드 단축&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Lombok이 제공하는 @RequiredArgsConstructor를 부착하면 위 생성자 코드를 완전히 대체할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-hveid=&quot;9&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor // final 필드를 모아 생성자를 자동 생성
public class MemberService {

    private final MemberRepository repository;
    private final DiscountPolicy discountPolicy;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;개발자가 직접 생성자 코드를 작성하지 않아도, 자바 컴파일 과정(Annotation Processing)에서 Lombok이 내부적으로 다음과 같은 생성자를 바이트코드에 생성해 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-hveid=&quot;12&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// 컴파일 시점에 Lombok이 바이트코드에 자동 생성하는 생성자
public MemberService(MemberRepository repository, DiscountPolicy discountPolicy) {
    this.repository = repository;
    this.discountPolicy = discountPolicy;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. 생성자 포함 대상: 필수 필드 (Required Field)&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@RequiredArgsConstructor는 이름 그대로 &lt;b data-index-in-node=&quot;33&quot; data-path-to-node=&quot;25&quot;&gt;초기화가 필요한 '필수 필드'만을 대상&lt;/b&gt;으로 생성자의 파라미터로 포함합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자 대상 필드 조건&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;27&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,0,0&quot;&gt;final이 선언된 필드&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,1,0&quot;&gt;@NonNull 어노테이션이 선언된 필드&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div data-hveid=&quot;15&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class MemberService {

    private final MemberRepository repository;     // [포함] final 필드
    private final DiscountPolicy discountPolicy;    // [포함] final 필드
    
    private String optionalNote;                    // [제외] 일반 필드 (생성자에 포함 안 됨)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; final이 아닌 값이 바뀔 수 있는 일반 변수 필드는 생성자 주입 대상에서 제외되므로, 필수 의존성은 final로 명확히 고정할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;5. Spring DI와 Lombok의 명확한 역할 분담&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;많은 개발자들이 &lt;i&gt;&lt;b&gt;&quot;Lombok의 @RequiredArgsConstructor가 의존성 주입을 대신해 준다&quot;&lt;/b&gt;&lt;/i&gt;고 오해하곤 합니다. &lt;br /&gt;하지만 두 기술은 명확히 다른 역할을 수행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;Lombok과 Spring의 역할 분담 체계&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;32&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Lombok (컴파일 타임): `final` 필드를 모아서 자바 생성자 코드를 '자동 작성'&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;32&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Framework (런타임/구동 타임): 단일 생성자를 감지하여 해당 생성자로 'Bean 주입(DI)' 수행&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dd9bX4/dJMcaaGgc6s/Id9GgSAV0W18i9rkYqvF2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dd9bX4/dJMcaaGgc6s/Id9GgSAV0W18i9rkYqvF2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dd9bX4/dJMcaaGgc6s/Id9GgSAV0W18i9rkYqvF2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdd9bX4%2FdJMcaaGgc6s%2FId9GgSAV0W18i9rkYqvF2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;559&quot; height=&quot;285&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Lombok은 단지 자바 언어 차원의 &lt;b data-index-in-node=&quot;21&quot; data-path-to-node=&quot;35&quot;&gt;생성자 작성 노동을 대체&lt;/b&gt;해 줄 뿐이며, 실제로 객체를 찾아서 넘겨주는 &lt;b data-index-in-node=&quot;60&quot; data-path-to-node=&quot;35&quot;&gt;의존성 주입 주체는 여전히 Spring&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;6. 실무에서의 표준 작성 스타일 (Best Practice)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;38&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;현재 대부분의 Spring Boot 및 자바 웹 프로젝트에서는 다음 스타일을 기본 표준으로 채택하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQoQU&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class MemberService {

    private final MemberRepository repository;
    private final DiscountPolicy discountPolicy;

    public void signUp(Member member) {
        repository.save(member);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;40&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 조합이 주는 이점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;41&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,0,0&quot;&gt;최고의 가독성:&lt;/b&gt; 불필요하게 늘어나는 생성자 코드가 없어져 깔끔함 유지가 가능합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,1,0&quot;&gt;final을 통한 불변성 유지:&lt;/b&gt; 의존성이 런타임에 변형될 위험을 원천 차단합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,2,0&quot;&gt;@Autowired 작성 불필요:&lt;/b&gt; 생성자가 단 1개만 생성되므로 @Autowired 표시를 할 필요가 없습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41,3,0&quot;&gt;완벽한 단위 테스트 지원:&lt;/b&gt; Spring 없이도 생성자를 호출하여 가짜(Mock) 객체를 넣는 단위 테스트 작성이 가능합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;43&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;44&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44,0,0&quot;&gt;@RequiredArgsConstructor 역할:&lt;/b&gt; final 또는 @NonNull이 붙은 필수 필드를 모아서 컴파일 시점에 생성자를 자동으로 만들어 줍니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44,1,0&quot;&gt;Spring 단일 생성자 규칙과의 시너지:&lt;/b&gt; 생성자가 1개만 생성되므로 Spring 4.3+의 @Autowired 생략 규칙이 자동 적용됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44,2,0&quot;&gt;핵심 구분:&lt;/b&gt; Lombok은 &lt;b data-index-in-node=&quot;15&quot; data-path-to-node=&quot;44,2,0&quot;&gt;생성자 생성&lt;/b&gt;을 담당하고, Spring은 그 생성자를 이용해 의존성 주입(DI)을 수행합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;45&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 파트에서는 의존성 주입 시 발생할 수 있는 가장 치명적인 문제인 순환 참조(Circular Dependency)의 원인과 해결 방안을 집중적으로 분석합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;45&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;48&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;48&quot;&gt;Q1. Lombok의 @RequiredArgsConstructor는 어떤 역할을 수행하나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;49,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;클래스 내에 선언된 &lt;b data-index-in-node=&quot;11&quot; data-path-to-node=&quot;49,0&quot;&gt;final 필드나 @NonNull 필드만을 매개변수로 갖는 생성자를 컴파일 시점에 자동으로 생성&lt;/b&gt;해 주는 어노테이션입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;50&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;50&quot;&gt;Q2. @RequiredArgsConstructor를 사용할 때 @Autowired를 명시하지 않아도 의존성 주입이 되는 이유는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;51,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Lombok이 생성자를 1개만 만들어내기 때문입니다. &lt;b data-index-in-node=&quot;30&quot; data-path-to-node=&quot;51,0&quot;&gt;Spring 4.3부터는 클래스 내 단일 생성자에 한해 @Autowired 생략이 가능&lt;/b&gt;하므로, Spring이 이를 감지하여 자동으로 의존성을 주입합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;52&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;52&quot;&gt;Q3. @RequiredArgsConstructor가 의존성 주입(DI) 기능 자체를 수행하는 것인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;53,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;아닙니다. &lt;b data-index-in-node=&quot;6&quot; data-path-to-node=&quot;53,0&quot;&gt;Lombok은 컴파일 시점에 자바 생성자 코드를 생성해 주는 역할&lt;/b&gt;만 수행하며, 애플리케이션 실행 시점에 해당 생성자를 통해 &lt;b data-index-in-node=&quot;75&quot; data-path-to-node=&quot;53,0&quot;&gt;실제 Bean 객체를 주입해 주는 주체는 Spring IoC 컨테이너&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;54&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;54&quot;&gt;Q4. 실무에서 생성자 주입과 @RequiredArgsConstructor 조합을 가장 선호하는 이유는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;55,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;final 키워드를 활용해 &lt;b data-index-in-node=&quot;15&quot; data-path-to-node=&quot;55,0&quot;&gt;객체의 불변성을 유지&lt;/b&gt;하고 &lt;b data-index-in-node=&quot;29&quot; data-path-to-node=&quot;55,0&quot;&gt;생성자 코드를 작성하는 반복 노동을 최소화&lt;/b&gt;할 수 있기 때문입니다. 또한 코드 가독성이 향상되며 단위 테스트 작성도 매우 용이해집니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3.6 순환 참조(Circular Dependency)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;앞서 생성자 주입은 객체를 완전한 상태로 생성하고 final을 활용할 수 있어 Spring에서 가장 권장되는 방식임을 다루었습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자 주입의 또 다른 결정적인 장점은 &lt;b data-index-in-node=&quot;22&quot; data-path-to-node=&quot;5&quot;&gt;순환 참조(Circular Dependency) 문제를 애플리케이션 구동 시점에 조기에 발견&lt;/b&gt;해 준다는 점입니다. 순환 참조는 시스템의 정상 구동을 막는 대표적인 아키텍처 및 설계 결함 중 하나입니다. 이번 파트에서는 순환 참조의 정의와 원인, 그리고 올바른 해결 전략을 분석합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 순환 참조란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;순환 참조는 &lt;b data-index-in-node=&quot;7&quot; data-path-to-node=&quot;8&quot;&gt;둘 이상의 Bean 객체가 서로를 직&amp;middot;간접적으로 참조(의존)하면서 의존성 형성 과정이 원형(Cycle) 고리를 이루는 상태&lt;/b&gt;를 의미합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oP8dI/dJMcagTXmjQ/5KSTVViBv9PBrTAXeutsnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oP8dI/dJMcagTXmjQ/5KSTVViBv9PBrTAXeutsnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oP8dI/dJMcagTXmjQ/5KSTVViBv9PBrTAXeutsnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoP8dI%2FdJMcagTXmjQ%2F5KSTVViBv9PBrTAXeutsnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;394&quot; height=&quot;99&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQzAE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    private final OrderService orderService;

    public MemberService(OrderService orderService) {
        this.orderService = orderService;
    }
}

@Service
public class OrderService {

    private final MemberService memberService;

    public OrderService(MemberService memberService) {
        this.memberService = memberService;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 순환 참조 시 생성자 주입이 동작하는 메커니즘 (무한 루프)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 컨테이너가 Bean을 인스턴스화하는 과정에서 생성자 주입을 만나면, 생성자 파라미터로 들어갈 의존 대상을 우선적으로 생성하려 시도합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-path-to-node=&quot;14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;MemberService 인스턴스 생성 시도&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자 파라미터인 OrderService Bean 필요&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;OrderService 인스턴스 생성 시도&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자 파라미터인 MemberService Bean 필요&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;MemberService는 아직 [1]번 단계에서 생성 완료되지 못함 (Null/Uninitialized)&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;무한 인스턴스화 대기 루프 ──&amp;gt; BeanCurrentlyInCreationException 예외 발생!&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자 주입 구조에서는 어느 한쪽도 완시점의 객체로 만들어지지 못하므로, Spring은 애플리케이션 부팅 과정에서 이를 직관적으로 감지하고 구동을 즉시 중단(Fail-Fast)시킵니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. 주입 방식별 순환 참조 감지 시점의 차이&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;주입 방식에 따른 순환 참조 감지 시점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;생성자 주입&lt;/b&gt;: 애플리케이션 시작 단계(Buildup)에서 감지&amp;nbsp;&amp;rarr; 구동 시 예외 발생으로 운영 장애 원천 차단 (Fail-Fast) &lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Setter / 필드 주입&lt;/b&gt;: 우선 빈 객체를 생성 후 주입 수행&amp;nbsp;&amp;rarr; 애플리케이션은 정상 부팅되나, 런타임에 메서드 호출 시 StackOverflowError 발생 위험에 노출&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQzgE&quot; data-hveid=&quot;0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Past Spring 버전에서는 Setter 주입의 지연 주입 특성을 통해 순환 참조를 일시적으로 허용하기도 했으나, 이는 심각한 설계 결함을 은폐하는 위험이 있습니다. &lt;b data-index-in-node=&quot;95&quot; data-path-to-node=&quot;20&quot;&gt;현재의 Spring Boot(2.6+) 환경에서는 필드/Setter 주입이더라도 순환 참조 발생 시 기본적으로 구동을 거부하도록 정책이 강화&lt;/b&gt;되어 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. 순환 참조가 발생하는 근본적인 원인&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;순환 참조는 단순한 Framework 설정 오류가 아니라 객체지향 설계 관점에서 클래스의 역할과 책임이 명확히 분리되지 않았다는 강한 경고 신호(Code Smell)입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;24&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24,0,0&quot;&gt;책임의 얽힘:&lt;/b&gt; MemberService와 OrderService가 서로의 로직을 상호 호출하고 있다면, 두 클래스의 경계가 모호해졌음을 뜻합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24,1,0&quot;&gt;단일 책임 원칙(SRP) 위반:&lt;/b&gt; 하나의 클래스가 너무 많은 일을 담당하거나 두 서비스의 공통 도메인 로직이 분리되지 않았을 때 발생합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5. 순환 참조 해결 방안&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;방안 1: 공통 로직 분리를 통한 단방향 의존 설계 (가장 권장)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;두 클래스가 서로 의존하게 만드는 공통 로직이나 연관 로직을 추출하여 &lt;b data-index-in-node=&quot;39&quot; data-path-to-node=&quot;28&quot;&gt;제3의 서비스 클래스(예: CommonService 또는 OrderFacade)로 분리&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pJi3h/dJMcadQm9wL/AhEFnE0OpKwbo78XiADPX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pJi3h/dJMcadQm9wL/AhEFnE0OpKwbo78XiADPX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pJi3h/dJMcadQm9wL/AhEFnE0OpKwbo78XiADPX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpJi3h%2FdJMcadQm9wL%2FAhEFnE0OpKwbo78XiADPX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;632&quot; height=&quot;240&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQ0AE&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class OrderProcessFacade { // 공통 또는 중재 로직 전담

    private final MemberService memberService;
    private final OrderService orderService;

    public void processOrder() {
        // 회원 검증 및 주문 처리 유즈케이스 통합 수행
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;방안 2: 이벤트 기반 아키텍처(ApplicationEventPublisher) 적용&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;한 서비스가 다른 서비스의 상태 변경을 알릴 때 직접 참조 대신 &lt;b data-index-in-node=&quot;36&quot; data-path-to-node=&quot;32&quot;&gt;Spring의 이벤트 발행 메커니즘을 이용해 결합도를 완화&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQ0QE&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class OrderService {

    private final ApplicationEventPublisher eventPublisher;

    public void createOrder() {
        // 주문 생성 로직...
        eventPublisher.publishEvent(new OrderCreatedEvent(this)); // 직접 참조 없이 이벤트 발행
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;방안 3: @Lazy 어노테이션 활용 (비권장 임시방편)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Lazy를 사용하면 주입 시점에 실제 객체 대신 프록시(Proxy) 객체를 주입하여 Bean 생성을 실제 사용 시점까지 지연시킴으로써 순환 고리를 임시로 끊어줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi-vu76gPeVAxUAAAAAHQAAAAAQ0gE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;public MemberService(@Lazy OrderService orderService) {
    this.orderService = orderService;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;37,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,0&quot;&gt;주의:&lt;/b&gt; @Lazy는 설계를 개선하지 않고 순환 참조를 우회 및 은폐하는 미봉책에 불과하므로, 실무에서는 &lt;b data-index-in-node=&quot;58&quot; data-path-to-node=&quot;37,0&quot;&gt;최후의 수단&lt;/b&gt;으로만 고려하고 &lt;b data-index-in-node=&quot;73&quot; data-path-to-node=&quot;37,0&quot;&gt;반드시 클래스 분리를 통한 리팩토링을 우선&lt;/b&gt;해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;39&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;6. 순환 참조를 예방하는 레이어드 아키텍처 원칙&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;40&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;순환 참조를 원천 예방하기 위해서는 &lt;b data-index-in-node=&quot;20&quot; data-path-to-node=&quot;40&quot;&gt;의존성의 방향이 항상 단방향(상위 계층 -&amp;gt; 하위 계층)으로만 흐르도록 설계&lt;/b&gt;해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/noPmz/dJMcafU4Niz/o9Hkk7k7nzEloU10kV3XCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/noPmz/dJMcafU4Niz/o9Hkk7k7nzEloU10kV3XCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/noPmz/dJMcafU4Niz/o9Hkk7k7nzEloU10kV3XCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnoPmz%2FdJMcafU4Niz%2Fo9Hkk7k7nzEloU10kV3XCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;458&quot; height=&quot;409&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;42&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;42,0,0&quot;&gt;절대 금지:&lt;/b&gt; Repository가 Service를 참조하거나, Service가 Controller를 역참조하는 구조는 지양해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;42,1,0&quot;&gt;동일 계층 참조 제한:&lt;/b&gt; Service 간의 상호 참조가 빈번하다면 계층을 재조정하거나 Facade 패턴 도입을 검토해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;44&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;45&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;45,0,0&quot;&gt;순환 참조 개념:&lt;/b&gt; 둘 이상의 Bean이 서로를 원형 구조로 참조하여 객체 생성이 불가능해지는 상태입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;45,1,0&quot;&gt;생성자 주입의 장점:&lt;/b&gt; 애플리케이션 시작 단계(구동 시점)에서 순환 참조를 조기에 감지하고 구동을 중단시켜 안정성을 높여줍니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;45,2,0&quot;&gt;근본 해결책:&lt;/b&gt; @Lazy 같은 우회 어노테이션에 의존하지 않고, &lt;b data-index-in-node=&quot;36&quot; data-path-to-node=&quot;45,2,0&quot;&gt;공통 클래스 추출 및 단방향 의존 관계로의 리팩토링&lt;/b&gt;을 진행해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;47&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Part 3. 의존성 주입(DI)과 활용편이 모두 마무리되었습니다. 다음 Part 4에서는 Spring Bean의 생명주기(Lifecycle)와 스코프(Scope)에 대해 자세히 다루어 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;49&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;50&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;50&quot;&gt;Q1. 순환 참조(Circular Dependency)란 무엇이며 왜 발생하는가?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;51,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;둘 이상의 Bean 객체가 서로를 직&amp;middot;간접적으로 참조하면서 &lt;b data-index-in-node=&quot;33&quot; data-path-to-node=&quot;51,0&quot;&gt;의존성 주입 관계가 원형 고리(Cycle)를 형성하는 문제&lt;/b&gt;입니다. 주로 클래스의 책임이 명확히 분리되지 않고 기능이 서로 얽혀 있을 때(SRP 위반) 발생합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;52&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;52&quot;&gt;Q2. 생성자 주입을 사용할 때 순환 참조가 발생하면 어떻게 처리되는가?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;53,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 컨테이너가 Bean을 인스턴스화하는 생성자 호출 단계에서 무한 루프를 감지하고, **애플리케이션 구동 시점에 BeanCurrentlyInCreationException 예외를 발생시키며 즉시 구동을 중단(Fail-Fast)**합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;54&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;54&quot;&gt;Q3. 순환 참조 문제를 해결하기 위한 올바른 접근법은 무엇인가?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;55,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;가장 근본적인 해결책은 &lt;b data-index-in-node=&quot;13&quot; data-path-to-node=&quot;55,0&quot;&gt;객체의 책임을 재설계&lt;/b&gt;하는 것입니다. 서로 참조하는 공통 로직을 별도의 클래스(또는 Facade 레이어)로 분리하거나, Spring 이벤트를 활용해 의존 관계를 &lt;b data-index-in-node=&quot;102&quot; data-path-to-node=&quot;55,0&quot;&gt;단방향&lt;/b&gt;으로 전환해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;56&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;56&quot;&gt;Q4. 순환 참조 해결을 위해 @Lazy 어노테이션을 사용하는 것은 바람직한가?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;56&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; letter-spacing: 0px;&quot;&gt;바람직하지 않습니다. @Lazy는 프록시 객체를 이용해 주입 시점을 지연시켜 문제를 임시로 피할 뿐, &lt;/span&gt;&lt;b data-index-in-node=&quot;57&quot; data-path-to-node=&quot;57,0&quot;&gt;설계적 결함을 은폐&lt;/b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; letter-spacing: 0px;&quot;&gt;하게 됩니다. 따라서 특수한 상황을 제외하고는 클래스 리팩토링을 통한 근본적 해결을 우선해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> SpringBoot</category>
      <category>Spring</category>
      <author>limdaeil</author>
      <guid isPermaLink="true">https://limdaeil.tistory.com/86</guid>
      <comments>https://limdaeil.tistory.com/86#entry86comment</comments>
      <pubDate>Wed, 29 Jul 2026 13:24:31 +0900</pubDate>
    </item>
    <item>
      <title>Spring: Part 2. IoC 컨테이너와 Bean</title>
      <link>https://limdaeil.tistory.com/85</link>
      <description>&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Part&amp;nbsp;2.&amp;nbsp;IoC&amp;nbsp;컨테이너와&amp;nbsp;Bean&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2.1 IoC와 DI의 개념 (제어의 역전과 의존성 주입)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Framework를 이해할 때 가장 근간이 되는 두 축은 IoC(Inversion of Control, 제어의 역전)와 DI(Dependency Injection, 의존성 주입)입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;많은 초심자가 IoC를 단순히 &quot;Spring이 객체를 대신 생성해 주는 편리한 기능&quot; 정도로 오해하지만, 이는 결과물 중 일부일 뿐입니다. IoC와 DI의 본질은 &lt;b data-index-in-node=&quot;91&quot; data-path-to-node=&quot;7&quot;&gt;객체의 생명주기 제어권을 프레임워크에 위임하고, 객체 간의 결합도를 낮추기 위한 구조적 설계 메커니즘&lt;/b&gt;에 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 제어의 역전 (IoC)이란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;전통적인 자바 프로그래밍에서는 개발자가 작성한 코드가 객체의 생성부터 생명주기 전체를 직접 제어했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;생성 제어:&lt;/b&gt; 언제 new 키워드로 객체를 만들 것인가?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;구현체 결정:&lt;/b&gt; 어떤 구체(Concrete) 클래스를 사용할 것인가?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,2,0&quot;&gt;연결 및 생명주기 제어:&lt;/b&gt; 생성한 객체를 누구에게 전달하고 언제 파괴할 것인가?&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQwAM&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;// 개발자가 모든 제어권을 직접 쥐고 있는 구조
public class MemberService {
    // MemberService가 MemoryMemberRepository 구현체를 직접 선택하고 생성함
    private final MemberRepository repository = new MemoryMemberRepository();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 방식은 직관적이지만, MemberService가 저장소 구현체에 강하게 결합(High Coupling)되어 저장 기술(JPA, MyBatis 등)이 바뀔 때마다 서비스 코드를 직접 수정해야 하는 치명적인 단점이 존재합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;IoC(Inversion of Control)는 이러한 객체의 생성 및 관리 책임을 개발자가 아닌 프레임워크(Spring 컨테이너)로 전적으로 넘기는 설계 원칙입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 의존성 주입 (DI)이란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;MemberService가 DB에 접근하기 위해 MemberRepository를 사용하는 것처럼, &quot;한 객체가 작동하기 위해 다른 객체의 기능을 가져다 쓰는 관계&quot;를 의존성(Dependency)이라고 합니다.&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;IoC가 &quot;제어권을 컨테이너에 위임한다&quot;는 상위 차원의 설계 원칙이라면, &lt;b data-index-in-node=&quot;41&quot; data-path-to-node=&quot;18&quot;&gt;DI(Dependency Injection)는 이 IoC 원칙을 코드 수준에서 완성하는 구체적인 기술 패턴&lt;/b&gt;입니다. 객체가 의존 대상을 직접 생성하지 않고, &lt;b data-index-in-node=&quot;128&quot; data-path-to-node=&quot;18&quot;&gt;외부(Spring 컨테이너)로부터 주입받아 사용&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQwQM&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    private final MemberRepository repository;

    // 객체를 직접 생성하지 않고, 외부(Spring)에서 생성된 객체를 전달(주입)받음
    public MemberService(MemberRepository repository) {
        this.repository = repository;
    }

    public void signUp() {
        repository.save(...); // 주입받은 객체의 기능만 '사용'에 집중
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이제 MemberService 내부에는 new 키워드가 존재하지 않습니다. 어떤 구현체가 들어올지 고민할 필요 없이, 오직 본연의 비즈니스 로직을 수행하는 책임만 남게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. Spring IoC/DI 컨테이너의 동작 구조&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring은 애플리케이션 시작 시점에 IoC 컨테이너를 통해 객체를 생성하고 의존성을 수동/자동으로 연결합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EdeQ0/dJMb99NXYmH/m7uESjeCK6ntkwqt3hEPe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EdeQ0/dJMb99NXYmH/m7uESjeCK6ntkwqt3hEPe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EdeQ0/dJMb99NXYmH/m7uESjeCK6ntkwqt3hEPe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEdeQ0%2FdJMb99NXYmH%2Fm7uESjeCK6ntkwqt3hEPe1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;528&quot; height=&quot;191&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;IoC와 DI의 관계 재정립&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;26&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;26,0,0&quot;&gt;IoC (설계 원칙):&lt;/b&gt; &quot;객체 제어의 주도권을 개발자에서 프레임워크로 넘긴다.&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;26,1,0&quot;&gt;DI (구현 패턴):&lt;/b&gt; &quot;IoC를 달성하기 위해, 필요로 하는 의존 객체를 외부에서 주입해 준다.&quot;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. IoC와 DI가 제공하는 핵심 이점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;개발자가 직접 `new`를 사용하지 않고 IoC/DI 구조를 취함으로써 얻는 이점은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;30&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;30,0,0&quot;&gt;낮은 결합도 (Loose Coupling):&lt;/b&gt; 구체 클래스가 아닌 인터페이스에 의존하므로, 구현체가 바뀌어도 클라이언트 코드는 단 한 줄도 수정할 필요가 없습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;30,1,0&quot;&gt;단일 책임 원칙 (SRP) 준수:&lt;/b&gt; 객체는 자신의 핵심 역할(비즈니스 로직)에만 집중하고, 객체의 생성 및 연결은 컨테이너가 전담합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;30,2,0&quot;&gt;테스트 용이성 (Testability):&lt;/b&gt; 단위 테스트 작성 시 실제 DB 연동 객체 대신 가짜 객체(Mock/Fake)를 생성자를 통해 손쉽게 주입할 수 있습니다.&lt;/span&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 테스트 코드에서 가짜 저장소를 손쉽게 주입하여 단위 테스트 수행
MemberRepository mockRepo = new FakeMemberRepository();
MemberService service = new MemberService(mockRepo);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;33&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;33,0,0&quot;&gt;IoC:&lt;/b&gt; 객체의 생성, 생명주기, 의존성 제어권이 개발자에서 Spring 컨테이너로 역전되는 &lt;b data-index-in-node=&quot;52&quot; data-path-to-node=&quot;33,0,0&quot;&gt;설계 개념&lt;/b&gt;입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;33,1,0&quot;&gt;DI:&lt;/b&gt; IoC 개념을 코드 수준에서 실현하는 &lt;b data-index-in-node=&quot;25&quot; data-path-to-node=&quot;33,1,0&quot;&gt;구체적인 기술 패턴&lt;/b&gt;으로, 필요한 의존 객체를 외부에서 주입해 줍니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;33,2,0&quot;&gt;핵심 가치:&lt;/b&gt; 객체의 &lt;b data-index-in-node=&quot;11&quot; data-path-to-node=&quot;33,2,0&quot;&gt;'생성'과 '사용' 책임을 명확히 분리&lt;/b&gt;하여 높은 유연성, 낮은 결합도, 뛰어난 테스트 용이성을 확보합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 파트에서는 이렇게 Spring IoC/DI 컨테이너에 의해 생명주기가 관리되는 핵심 단위인 &lt;b data-index-in-node=&quot;54&quot; data-path-to-node=&quot;34&quot;&gt;Spring Bean&lt;/b&gt;의 정의와 일반 자바 객체(POJO)와의 차이점을 살펴봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;36&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37&quot;&gt;Q1. IoC(Inversion of Control, 제어의 역전)란 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;38,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체의 생성, 의존성 연결, 생명주기 관리 등의 제어 주권이 개발자의 코드에서 프레임워크(Spring 컨테이너)로 넘어가는 설계 원칙을 의미합니다. 개발자는 객체 생성 제어에서 벗어나 비즈니스 로직 구현에만 집중할 수 있게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;39&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;39&quot;&gt;Q2. IoC와 DI의 차이점은 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;40,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;40,0&quot;&gt;IoC&lt;/b&gt;는 제어권의 주체가 프레임워크로 이동한다는 상위 수준의 추상적인 &lt;b data-index-in-node=&quot;39&quot; data-path-to-node=&quot;40,0&quot;&gt;설계 원칙/개념&lt;/b&gt;이며, &lt;b data-index-in-node=&quot;51&quot; data-path-to-node=&quot;40,0&quot;&gt;DI&lt;/b&gt;는 이러한 IoC 원칙을 실제 코드 수준에서 달성하기 위해 외부에서 의존 객체를 주입해 주는 구체적인 &lt;b data-index-in-node=&quot;110&quot; data-path-to-node=&quot;40,0&quot;&gt;기술 및 구현 패턴&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;41&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;41&quot;&gt;Q3. 객체를 직접 new로 생성하지 않고 DI를 적용하는 이유는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;42,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체의 &lt;b data-index-in-node=&quot;4&quot; data-path-to-node=&quot;42,0&quot;&gt;생성 책임과 사용 책임을 분리&lt;/b&gt;하기 위함입니다. 이를 통해 객체 간 결합도를 낮추어 구현체 변경 시 유연한 대응이 가능해지며, 단위 테스트 시 가짜 객체(Mock)를 손쉽게 주입할 수 있어 테스트 용이성이 대폭 향상됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2.2 Spring Bean&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;앞서 다룬 &lt;b data-index-in-node=&quot;6&quot; data-path-to-node=&quot;4&quot;&gt;IoC&lt;/b&gt;와 &lt;b data-index-in-node=&quot;11&quot; data-path-to-node=&quot;4&quot;&gt;DI&lt;/b&gt;를 통해 Spring이 객체의 생성과 관리 주도권을 가져가고, 필요한 의존성을 자동으로 주입한다는 메커니즘을 살펴보았습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그렇다면 &lt;b&gt;&quot;Spring이 생성하고 관리하는 그 객체의 정체&quot;&lt;/b&gt;는 무엇일까요? 이 객체들을 일컬어 &lt;b&gt;Spring Bean(스프링 빈)&lt;/b&gt;이라고 부릅니다. 이후 학습할 DI, AOP, 선언적 트랜잭션, Spring MVC 등 Spring의 모든 핵심 기술은 예외 없이 이 &lt;b data-index-in-node=&quot;146&quot; data-path-to-node=&quot;5&quot;&gt;Spring Bean을 중심으로 작동&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Bean이란 무엇인가?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Bean은 &lt;b data-index-in-node=&quot;13&quot; data-path-to-node=&quot;8&quot;&gt;Spring IoC 컨테이너가 생성, 의존성 주입, 생명주기를 직접 관리하는 객체&lt;/b&gt;를 의미합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;자바 애플리케이션에 존재하는 모든 객체가 Bean이 되는 것은 아닙니다. &lt;b data-index-in-node=&quot;41&quot; data-path-to-node=&quot;9&quot;&gt;Spring 컨테이너의 관리 대상(Registry)으로 등록된 객체만&lt;/b&gt;을 Bean이라고 부릅니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ_wE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 1. 일반 Java 객체 (Spring이 알지 못함)
public class MemberService {
}

// 2. Spring Bean (@Service 어노테이션으로 스캔 및 컨테이너 등록 대상이 됨)
@Service
public class MemberService {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`@Service`, `@Controller`, `@Repository`, `@Component`, `@Bean` 등의 어노테이션이 부착되거나 설정 파일에 등록되어 애플리케이션 실행 시 컨테이너에 의해 생성된 객체가 바로 Spring Bean입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;일반 Java 객체 vs Spring Bean&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;두 객체 모두 형태적으로는 동일한 Java 클래스로 작성되지만, &lt;b&gt;&quot;객체를 관리하는 주체가 누구인가&lt;/b&gt;&quot;에 따라 결정적인 차이가 발생합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,0&quot;&gt;일반 Java 객체 (POJO)&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,1,0,0&quot;&gt;관리 주체:&lt;/b&gt; 개발자 (`new` 키워드로 직접 생성)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,1,1,0&quot;&gt;생명주기:&lt;/b&gt; 개발자 작성 코드 및 GC(Garbage Collector)에 의해 관리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,1,2,0&quot;&gt;의존 관계:&lt;/b&gt; 개발자가 직접 코드로 연결 (`new` 및 setter/생성자 직접 호출)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,1,3,0&quot;&gt;특징:&lt;/b&gt; Spring이 제공하는 차원 높은 부가기능(AOP, Transaction 등)을 직접 받지 못함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,0&quot;&gt;Spring Bean&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,1,0,0&quot;&gt;관리 주체:&lt;/b&gt; Spring IoC 컨테이너&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,1,1,0&quot;&gt;생명주기:&lt;/b&gt; 초기화부터 소멸까지 컨테이너가 제어&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,1,2,0&quot;&gt;의존 관계:&lt;/b&gt; DI 메커니즘을 통해 컨테이너가 자동으로 주입 및 연결&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,1,3,0&quot;&gt;특징:&lt;/b&gt; AOP, 트랜잭션 프록시, 보안, 이벤트 등 Spring 프레임워크 생태계의 모든 혜택을 받음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring이 Bean을 직접 관리하는 이유&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring이 객체를 Bean으로 승격시켜 컨테이너에서 직접 관리하는 이유는 단순한 저장/보관 목적이 아닙니다. &lt;br /&gt;&lt;b data-index-in-node=&quot;63&quot; data-path-to-node=&quot;18&quot;&gt;Bean으로 관리되어야만 Spring Framework의 핵심 부가 기능들을 적용할 수 있기 때문&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;19&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,0,0&quot;&gt;의존성 자동 주입 (DI):&lt;/b&gt; 필요한 객체를 자동으로 찾아 연결&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,1,0&quot;&gt;싱글톤 스코프 관리:&lt;/b&gt; 객체를 애플리케이션 스코프 내에서 효율적으로 공유&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,2,0&quot;&gt;생명주기 콜백 지원:&lt;/b&gt; 객체 생성 직후(`@PostConstruct`) 및 소멸 직전(`@PreDestroy`)에 원하는 로직 수행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,3,0&quot;&gt;AOP 및 트랜잭션 프록시 적용:&lt;/b&gt; `@Transactional` 등의 어노테이션은 Spring이 Bean 객체 주위에 생성한 프레임워크 프록시(Proxy)를 통해서만 작동합니다. 일반 자바 객체에는 이러한 프록시가 적용되지 않습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bean은 언제 생성되는가?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;기본 설정 상태인 &lt;b data-index-in-node=&quot;10&quot; data-path-to-node=&quot;22&quot;&gt;Singleton Scope Bean&lt;/b&gt;은 애플리케이션 구동 시점(컨테이너 초기화 단계)에 모두 일괄 생성됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-path-to-node=&quot;22&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Application 실행&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;22&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ApplicationContext(컨테이너) 생성&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;22&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Component Scan &amp;amp; 모든 Singleton Bean 생성&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;22&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bean 간의 의존성 주입(DI)&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;22&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;애플리케이션 초기화 완료 및 요청 수신&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQgAI&quot; data-hveid=&quot;0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;애플리케이션이 외부 HTTP 요청을 받기도 전에, 필요한 모든 Bean을 미리 생성하고 의존 관계를 완성해 두기 때문에 실제 사용자 요청 처리 속도가 대폭 향상됩니다. &lt;i data-index-in-node=&quot;94&quot; data-path-to-node=&quot;24&quot;&gt;(단, `@Lazy` 어노테이션을 부착하면 실제 사용 시점까지 객체 생성을 미룰 수도 있습니다.)&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bean 관리 구조 (이름과 타입)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 컨테이너는 Bean을 등록할 때 Bean &lt;b&gt;이름(Name)&lt;/b&gt;과 Bean &lt;b&gt;타입(Type)&lt;/b&gt;이라는 두 가지 핵심 키 값을 기준으로 관리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQgQI&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Service
public class MemberService { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;29&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;29,0,0&quot;&gt;기본 Bean 이름:&lt;/b&gt; 클래스명의 첫 글자를 소문자로 바꾼 &lt;b data-index-in-node=&quot;32&quot; data-path-to-node=&quot;29,0,0&quot;&gt;memberService&lt;/b&gt; (카멜 케이스 규칙)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;29,1,0&quot;&gt;Bean 타입:&lt;/b&gt; 해당 클래스 타입(MemberService)은 물론, &lt;b data-index-in-node=&quot;39&quot; data-path-to-node=&quot;29,1,0&quot;&gt;구현한 인터페이스 타입&lt;/b&gt;으로도 함께 관리됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQggI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public interface MemberRepository { }

@Repository
public class JpaMemberRepository implements MemberRepository { }
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;위의 JpaMemberRepository Bean은 컨테이너 내부에서 아래의 두 가지 타입 모두로 조회 및 주입이 가능합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;32&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;JpaMemberRepository (구체 클래스 타입)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;MemberRepository (인터페이스 타입)&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring이 인터페이스 기반의 다형성 DI를 손쉽게 수행할 수 있는 원리가 바로 여기에 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bean과 일반 객체의 포함 관계&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;432&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgLeKZ/dJMcaf1J1OV/lBtLm5XWiSbG6kq8l9e801/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgLeKZ/dJMcaf1J1OV/lBtLm5XWiSbG6kq8l9e801/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgLeKZ/dJMcaf1J1OV/lBtLm5XWiSbG6kq8l9e801/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgLeKZ%2FdJMcaf1J1OV%2FlBtLm5XWiSbG6kq8l9e801%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;465&quot; height=&quot;207&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;432&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQgwI&quot; data-hveid=&quot;0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;37&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;모든 Bean은 자바 객체이지만, 애플리케이션 안의 모든 자바 객체가 Bean인 것은 아닙니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Bean은 &quot;Spring 컨테이너라는 특수한 공간에 등록되어 관리를 받는 자바 객체&quot;입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;39&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;40&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;40,0,0&quot;&gt;Spring Bean:&lt;/b&gt; Spring IoC 컨테이너가 생성, 의존성 주입, 생명주기를 총괄 관리하는 자바 객체입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;40,1,0&quot;&gt;핵심 차이:&lt;/b&gt; 객체의 생성 주체가 개발자인지, 아니면 Spring 프레임워크(컨테이너)인지에 따라 나뉩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;40,2,0&quot;&gt;존재 이유:&lt;/b&gt; Bean으로 관리되어야만 DI, AOP, 선언적 트랜잭션, 생명주기 콜백 등 Spring의 고차원적 기능들을 원활히 적용받을 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;41&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 파트에서는 이러한 Bean들을 실제로 등록하고 내부에 저장하여 관리하는 핵심 주체인 Spring 컨테이너(BeanFactory 및 ApplicationContext)에 대해 다룹니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;43&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;44&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44&quot;&gt;Q1. Spring Bean이란 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;45,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;45,0&quot;&gt;Spring IoC 컨테이너에 의해 생성, 의존성 주입, 생명주기가 관리되는 자바 객체&lt;/b&gt;를 의미합니다. 컨테이너에 등록된 객체만 Spring Bean이라 부릅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;46&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;46&quot;&gt;Q2. 일반 Java 객체와 Spring Bean의 결정적인 차이는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;46&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;객체의 관리 주체(Control)&lt;/b&gt;입니다. 일반 자바 객체는 개발자가 직접 `new`로 생성하고 생명주기를 제어하지만, Spring Bean은 Spring 컨테이너가 생성을 전담하고 DI, AOP, 트랜잭션 처리 등 프레임워크 부가기능을 적용해 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;47&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;48&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;48&quot;&gt;Q3. 자바 객체를 그냥 쓰지 않고, 굳이 Spring Bean으로 등록하여 관리하는 이유는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;49,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체를 컨테이너의 Bean으로 등록해야만 &lt;b data-index-in-node=&quot;23&quot; data-path-to-node=&quot;49,0&quot;&gt;의존성 자동 주입(DI), 싱글톤 관리, 생명주기 콜백, 그리고 AOP 기반의 트랜잭션 프록시 처리&lt;/b&gt; 같은 Spring의 핵심 이점들을 누릴 수 있기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;50&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;50&quot;&gt;Q4. Spring Bean의 기본 생성 시점은 언제인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;51,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;기본적으로 &lt;b data-index-in-node=&quot;6&quot; data-path-to-node=&quot;51,0&quot;&gt;Singleton Scope&lt;/b&gt;를 따르므로, &lt;b&gt;애플리케이션이 구동되는 시점(컨테이너 초기화 단계)&lt;/b&gt;에 필요한 모든 Bean이 일괄 생성되고 의존성이 연결됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2.4 Spring 컨테이너 (BeanFactory와 ApplicationContext)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;앞서 &lt;b data-index-in-node=&quot;3&quot; data-path-to-node=&quot;4&quot;&gt;Spring Bean&lt;/b&gt;이 Spring IoC 컨테이너에 의해 생성되고 관리되는 객체임을 확인했습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그렇다면 이 &lt;b data-index-in-node=&quot;7&quot; data-path-to-node=&quot;5&quot;&gt;Spring Bean을 내부에 저장하고, 생명주기를 총괄하며 관리하는 그 컨테이너의 실체&lt;/b&gt;는 무엇일까요? Spring Framework에서는 객체의 생성, 연결, 관리를 전담하는 핵심 구성 요소를 Spring 컨테이너(Spring Container)라고 부르며, 자바 코드 수준에서는 &lt;b&gt;BeanFactory&lt;/b&gt;와 &lt;b data-index-in-node=&quot;178&quot; data-path-to-node=&quot;5&quot;&gt;ApplicationContext&lt;/b&gt; 두 인터페이스로 구현됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 컨테이너의 역할과 구동 흐름&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 컨테이너는 애플리케이션 시작 시점에 &lt;b&gt;설정 정보&lt;/b&gt;(어노테이션, 자바 설정 클래스 등)를 읽어와 Bean을 생성하고 저장 공간을 구축합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-path-to-node=&quot;8&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;설정 정보 읽기(Config Reader)&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;8&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Container 생성 및 초기화&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;8&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Bean 객체 생성&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;8&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;의존 관계 주입(DI) 연결&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;8&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;애플리케이션 실행 준비 완료(Ready)&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQsgI&quot; data-hveid=&quot;0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 애플리케이션은 사실상 이 컨테이너가 중심축이 되어 모든 객체의 생명주기를 주도하고 관리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;BeanFactory&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;BeanFactory는 Spring 컨테이너의 최상위에 위치하는 &lt;b data-index-in-node=&quot;36&quot; data-path-to-node=&quot;13&quot;&gt;가장 기초적인 기본 인터페이스&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;핵심 역할:&lt;/b&gt; Bean 생성, Bean 저장, Bean 조회(`getBean()`), 의존 관계 관리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;특징:&lt;/b&gt; Bean을 관리하기 위한 최소한의 경량 기능만을 정의합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQswI&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;BeanFactory beanFactory = ...;

// 컨테이너가 관리하는 Bean을 직접 조회
MemberService memberService = beanFactory.getBean(MemberService.class);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이처럼 BeanFactory는 순수하게 &quot;Bean을 관리하고 제공하는 일&quot;에만 집중된 단순 컨테이너 인터페이스입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ApplicationContext&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실무 및 모든 상용 프로젝트에서 이야기하는 Spring 컨테이너는 99% 이상&lt;b&gt; ApplicationContext를 의미&lt;/b&gt;합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ApplicationContext는 BeanFactory 인터페이스를 상속받아 그 기능을 모두 포함하면서, &lt;b data-index-in-node=&quot;60&quot; data-path-to-node=&quot;20&quot;&gt;엔터프라이즈 애플리케이션 개발에 필요한 다채로운 부가 기능들을 추가로 확장한 인터페이스&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;346&quot; data-origin-height=&quot;332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qTJs4/dJMcaa7j4UO/CCC7mkCnoiLoIlUL06CZw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qTJs4/dJMcaa7j4UO/CCC7mkCnoiLoIlUL06CZw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qTJs4/dJMcaa7j4UO/CCC7mkCnoiLoIlUL06CZw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqTJs4%2FdJMcaa7j4UO%2FCCC7mkCnoiLoIlUL06CZw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;197&quot; height=&quot;189&quot; data-origin-width=&quot;346&quot; data-origin-height=&quot;332&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ApplicationContext가 추가로 제공하는 엔터프라이즈 기능&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ApplicationContext는 단순히 Bean 관리 기능만 제공하는 것에 그치지 않고, 다음과 같은 강력한 부가 기능들을 함께 지니고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;25&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,0,0&quot;&gt;국제화 및 다국어 지원 (MessageSource):&lt;/b&gt; 국가 및 언어 설정에 따른 메시지 처리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,1,0&quot;&gt;환경 변수 구분 관리 (EnvironmentCapable):&lt;/b&gt; 로컬, 개발, 운영 등 프로파일(Profile)별 설정 및 프로퍼티 분리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,2,0&quot;&gt;이벤트 발행 및 구독 (ApplicationEventPublisher):&lt;/b&gt; 애플리케이션 내부 구성 요소 간의 컴포넌트 이벤트 처리 지원&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,3,0&quot;&gt;리소스 조회 단순화 (ResourceLoader):&lt;/b&gt; 파일, 클래스패스, URL 등의 리소스를 일관되게 읽어오는 기능&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,4,0&quot;&gt;AOP 및 Bean 후처리기 자동 등록:&lt;/b&gt; 프록시 객체 생성 등 고차원적 기능 지원&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;왜 실무에서는 ApplicationContext를 사용하는가?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이론적으로는 BeanFactory만으로도 객체를 생성하고 DI를 수행할 수 있습니다.&lt;br /&gt;하지만 실제 애플리케이션 환경에서는 다음과 같은 기능들이 필연적으로 요구됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;29&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다국어 지원이 필요한 서비스 &amp;rarr; &lt;b data-index-in-node=&quot;18&quot; data-path-to-node=&quot;29,0,0&quot;&gt;MessageSource&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;개발/운영 환경 구분이 필요한 서비스 &amp;rarr; &lt;b data-index-in-node=&quot;23&quot; data-path-to-node=&quot;29,1,0&quot;&gt;Environment Profile&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이벤트 기반 비동기/decoupled 로직 처리 &amp;rarr; &lt;b data-index-in-node=&quot;29&quot; data-path-to-node=&quot;29,2,0&quot;&gt;ApplicationEventPublisher&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;외부 설정 파일이나 템플릿 로딩 &amp;rarr; &lt;b data-index-in-node=&quot;20&quot; data-path-to-node=&quot;29,3,0&quot;&gt;ResourceLoader&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 모든 부가 기능이 ApplicationContext 하나에 이미 녹아있기 때문에,&lt;br /&gt;&lt;b data-index-in-node=&quot;48&quot; data-path-to-node=&quot;30&quot;&gt;실무에서는 BeanFactory를 직접 사용하는 일이 없으며 ApplicationContext를 표준 컨테이너로 활용&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Boot와 ApplicationContext&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Boot 환경에서도 애플리케이션 구동 시 내부적으로 ApplicationContext를 생성하여 동작시킵니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQtQI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        // 내부적으로 ApplicationContext(컨테이너)를 생성 및 초기화함
        SpringApplication.run(Application.class, args);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`SpringApplication.run()` 메서드가 실행되면 적절한 ApplicationContext 구현체(예: 웹 환경인 경우 `AnnotationConfigServletWebServerApplicationContext`)를 자동으로 띄워 Bean을 등록하고 의존성을 주입합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bean 조회와 관리의 실체&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;38&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;모든 Bean은 ApplicationContext 내부의 Bean Registry(저장소)에 보관됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQtgI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;// 직접 컨테이너에서 Bean을 꺼내오는 방식 (지양 / 단순 검증용)
MemberService service = applicationContext.getBean(MemberService.class);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;40&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실무 개발에서는 위와 같이 `applicationContext.getBean()`을 직접 호출하는 코드를 작성할 일이 거의 없습니다. Spring이 생성자 주입(DI)을 통해 필요한 Bean을 인젝션해 주므로, 개발자는 컨테이너의 존재조차 신경 쓰지 않고 자바 코드를 작성할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;42&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;43&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;43,0,0&quot;&gt;Spring 컨테이너:&lt;/b&gt; Spring Bean의 생성, 저장, DI, 생명주기를 다루는 핵심 주체입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;43,1,0&quot;&gt;BeanFactory:&lt;/b&gt; Bean 생성 및 조회라는 가장 기본적인 기능만을 정의한 최상위 인터페이스입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;43,2,0&quot;&gt;ApplicationContext:&lt;/b&gt; BeanFactory를 상속받아 국제화, 환경 변수, 이벤트, 리소스 처리 등 다양한 부가 기능을 확정한 실무용 표준 컨테이너입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;43,3,0&quot;&gt;Spring Boot:&lt;/b&gt; 구동 시점에 ApplicationContext를 자동으로 구성하여 모든 Bean과 프레임워크 기능을 구동시킵니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;44&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 파트에서는 Spring 컨테이너에 Bean을 등록하는 핵심 방법인 컴포넌트 스캔(Component Scan)과 &lt;b data-index-in-node=&quot;65&quot; data-path-to-node=&quot;44&quot;&gt;자바 설정 파일(@Bean)&lt;/b&gt; 방식을 알아봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;46&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;47&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;47&quot;&gt;Q1. Spring 컨테이너란 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;48,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;48,0&quot;&gt;Spring Bean의 생성, 저장, 의존성 주입(DI), 그리고 생명주기를 총괄 관리하는 핵심 주체&lt;/b&gt;입니다. 대표적인 인터페이스로 BeanFactory와 ApplicationContext가 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;49&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;49&quot;&gt;Q2. BeanFactory와 ApplicationContext의 차이는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;50,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;BeanFactory&lt;/b&gt;는 Bean 관리 및 조회라는 최소한의 기능만 제공하는 상위 인터페이스입니다. &lt;b&gt;ApplicationContext&lt;/b&gt;는 BeanFactory를 상속받아 Bean 관리 기능은 물론, &lt;b data-index-in-node=&quot;117&quot; data-path-to-node=&quot;50,0&quot;&gt;국제화(MessageSource), 환경 변수 분리(Environment), 이벤트 처리, 리소스 로딩&lt;/b&gt; 등 애플리케이션 구축에 필수적인 부가 기능을 통합 제공하는 실무 표준 컨테이너입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;51&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;51&quot;&gt;Q3. 실무에서 BeanFactory 대신 ApplicationContext를 사용하는 이유는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;52,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;단순한 Bean 관리 외에도 &lt;b data-index-in-node=&quot;16&quot; data-path-to-node=&quot;52,0&quot;&gt;엔터프라이즈 환경 구축에 필수적인 국제화, 환경 프로파일 관리, 이벤트 발행, AOP 자동 처리 등 다양한 부가 기능이 통합 지원&lt;/b&gt;되기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;53&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;53&quot;&gt;Q4. Spring Boot 애플리케이션에서도 Spring 컨테이너가 작동하나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;54,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;네, 그렇습니다. SpringApplication.run()을 실행하면 내부적으로 &lt;b data-index-in-node=&quot;46&quot; data-path-to-node=&quot;54,0&quot;&gt;웹 환경에 맞는 ApplicationContext를 스스로 생성&lt;/b&gt;하여 모든 Bean을 등록하고 관리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2.5 Bean 등록 방법 (자동 등록과 수동 등록)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;앞서 &lt;b data-index-in-node=&quot;3&quot; data-path-to-node=&quot;4&quot;&gt;Spring Bean&lt;/b&gt;이 Spring IoC 컨테이너에 의해 생성되고 관리되는 객체임을 살펴보았습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그렇다면 Spring은 애플리케이션 내의 수많은 클래스 중 &lt;b data-index-in-node=&quot;33&quot; data-path-to-node=&quot;5&quot;&gt;&quot;어떤 객체를 Bean으로 등록해야 하는지&quot;&lt;/b&gt; 어떻게 식별할까요? Spring은 크게 두 가지 접근 방식으로 Bean을 등록합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;자동 등록 (Component Scan):&lt;/b&gt; 어노테이션 기반으로 Spring이 알아서 찾아서 등록하는 방식&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;수동 등록 (@Bean):&lt;/b&gt; 자바 설정 파일에서 개발자가 직접 코드로 등록하는 방식&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실무에서는 애플리케이션의 성격과 객체의 역할에 따라 두 방식을 적재적소에 혼용하여 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bean 등록이 필요한 이유&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Framework는 프로젝트 내의 모든 자바 객체를 무분별하게 관리하지 않습니다. &lt;br /&gt;&lt;b data-index-in-node=&quot;53&quot; data-path-to-node=&quot;10&quot;&gt;미리 설정된 규칙에 따라 Spring 컨테이너에 Bean으로 등록된 객체만이 다음과 같은 Framework 특권을 누릴 수 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;의존성 자동 주입 (DI)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;컨테이너 기반 생명주기(Lifecycle) 관리&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,2,0&quot;&gt;AOP 및 @Transactional 프록시 적용&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,3,0&quot;&gt;애플리케이션 이벤트 발행 및 수신&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;즉, 객체를 Bean으로 올바르게 등록하는 것은 Spring의 생태계 안에서 작동시키기 위한 필수 첫 단추입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 자동 등록 (Component Scan)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring이 특정 패키지 하위를 탐색(Scan)하면서 Bean으로 등록할 클래스를 자동으로 찾아 등록하는 방식입니다. 이를 컴포넌트 스캔(Component Scan)이라고 부릅니다. `&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Component` 및 이를 내포한 스테레오타입 어노테이션이 붙은 클래스는 모두 자동 등록 대상이 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ0AI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Controller
public class MemberController { ... }

@Service
public class MemberService { ... }

@Repository
public class JpaMemberRepository implements MemberRepository { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;애플리케이션 구동 시 Spring은 지정된 패키지 경로를 스캔하여 해당 클래스들의 인스턴스를 자동으로 생성하고 컨테이너에 Bean으로 등록합니다. 개발자가 직접 new를 호출하거나 별도의 설정 코드를 작성할 필요가 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`@Component` 계열 어노테이션&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;자동 등록의 근간이 되는 핵심 어노테이션은 `@Component`입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;21&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,0,0&quot;&gt;`@Component`:&lt;/b&gt; 컴포넌트 스캔의 기본 대상&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,1,0&quot;&gt;`@Controller`:&lt;/b&gt; Spring MVC 컨트롤러로 인식 및 웹 요청 매핑 기능 제공&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,2,0&quot;&gt;`@Service`:&lt;/b&gt; 비즈니스 로직 계층임을 명시 (별도 계층 표식 역할)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,3,0&quot;&gt;`@Repository`:&lt;/b&gt; 데이터 접근 계층임을 명시하며, 데이터 접근 예외를 Spring의 공통 예외로 변환&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,4,0&quot;&gt;`@Configuration`:&lt;/b&gt; Spring 설정 정보 클래스임을 명시하며, CGLIB 기반 싱글톤 프록시 보장&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`@Controller`, `@Service`, `@Repository`, `@Configuration`은 모두 내부적으로 `@Component`를 포함(Meta-Annotation)하고 있으므로 Component Scan에 의해 자동으로 수집됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 수동 등록 (`@Configuration`과 `@Bean`)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;개발자가 자바 설정 클래스를 작성하고, 메서드 상단에 &lt;b data-index-in-node=&quot;30&quot; data-path-to-node=&quot;25&quot;&gt;@Bean&lt;/b&gt; 어노테이션을 부착하여 직접 Bean 객체를 생성 및 반환하는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ0QI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class AppConfig {

    @Bean
    public MemberRepository memberRepository() {
        return new JpaMemberRepository();
    }

    @Bean
    public MemberService memberService(MemberRepository repository) {
        return new MemberService(repository);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;27&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,0,0&quot;&gt;@Configuration:&lt;/b&gt; 이 클래스가 Spring의 설정 정보(Config)를 담고 있는 클래스임을 컨테이너에 알립니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,1,0&quot;&gt;@Bean:&lt;/b&gt; 해당 메서드가 반환하는 객체를 Spring 컨테이너의 Bean으로 등록합니다. (기본 Bean 이름은 메서드명이 됩니다.)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 방식은 객체 생성 과정, 초기화 로직, 의존성 연결 방식을 &lt;b data-index-in-node=&quot;35&quot; data-path-to-node=&quot;28&quot;&gt;개발자가 자바 코드로 세밀하게 제어&lt;/b&gt;할 수 있다는 특징이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;자동 등록 vs 수동 등록 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;두 방식 모두 결과적으로 &lt;b data-index-in-node=&quot;14&quot; data-path-to-node=&quot;31&quot;&gt;Spring 컨테이너 내부의 Bean 저장소에 일관되게 보관&lt;/b&gt;되며, &lt;br /&gt;완료 이후에는 DI, AOP, 트랜잭션 등 프레임워크 기능 적용에 &lt;b data-index-in-node=&quot;91&quot; data-path-to-node=&quot;31&quot;&gt;아무런 차이가 없습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 132px;&quot; border=&quot;1&quot; data-path-to-node=&quot;32&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;자동 등록 (`@Component`)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;수동 등록 (`@Bean`)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;32,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;32,1,0,0&quot;&gt;등록 주체&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;32,1,1,0&quot;&gt;Spring 컨테이너 (자동 스캔)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;32,1,2,0&quot;&gt;개발자 (자바 설정 코드로 명시)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;32,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;32,2,0,0&quot;&gt;사용 어노테이션&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;32,2,1,0&quot;&gt;`@Component`, `@Service`, `@Repository` 등&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;32,2,2,0&quot;&gt;`@Configuration`, `@Bean`&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;32,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;32,3,0,0&quot;&gt;생성 과정 제어&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;32,3,1,0&quot;&gt;기본 생성자 또는 DI에 의존&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;32,3,2,0&quot;&gt;객체 생성 및 설정을 코드로 직접 구성 가능&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;32,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;32,4,0,0&quot;&gt;주된 사용 대상&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;32,4,1,0&quot;&gt;직접 작성하는 계층별 클래스 (Controller, Service 등)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;32,4,2,0&quot;&gt;외부 라이브러리 객체, 공통 설정 객체, 복잡한 인스턴스&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;32,5,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;32,5,0,0&quot;&gt;관리 편의성&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;32,5,1,0&quot;&gt;클래스 생성 후 어노테이션만 붙이면 되어 매우 편리함&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;32,5,2,0&quot;&gt;설정 파일 관리가 필요하나 명시적 파악이 용이함&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;각각 언제 사용하는가?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;자동 등록을 사용하는 경우&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;36&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;우리가 프로젝트에서 &lt;b data-index-in-node=&quot;11&quot; data-path-to-node=&quot;36&quot;&gt;직접 작성하는 일반적인 비즈니스 애플리케이션 코드&lt;/b&gt;는 예외 없이 자동 등록을 기본으로 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;37&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,0,0&quot;&gt;Controller / Service / Repository / 일반 Component&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;계층 구조가 명확하고 수량이 많은 도메인 로직 관련 클래스들은 컴포넌트 스캔을 통해 관리하는 것이 관리 포인트를 혁신적으로 줄여줍니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;38&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;수동 등록을 사용하는 경우&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;39&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;직접 작성한 코드가 아니거나, 생성 과정에 세밀한 개입이 필요한 경우에는 수동 등록이 적합합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;40&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;40,0,0&quot;&gt;외부 라이브러리 객체 등록&lt;/b&gt;&lt;/span&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ1AI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public ObjectMapper objectMapper() {
    return new ObjectMapper(); // 외부 라이브러리 객체를 Bean으로 수동 등록
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;외부 라이브러리 클래스(예: Jackson의 ObjectMapper)는 소스 코드를 수정할 수 없으므로 `@Component`를 붙일 수 없습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;40,1,0&quot;&gt;생성 과정 및 초기화 설정이 복잡한 객체&lt;/b&gt;&lt;/span&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ1QI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    // 타임아웃, 인터셉터 등 복잡한 설정 추가
    return restTemplate;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다양한 옵션 세팅이나 사전 작업이 필요한 경우 자바 코드로 깔끔하게 구성을 완성하여 반환할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;40,2,0&quot;&gt;다형성 활용 시 구현체를 명시적으로 관리하고 싶은 경우&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;상황이나 환경 설정에 따라 특정 구현체를 직관적으로 교체하고 싶을 때 설정 파일 한 곳에서 변경 사항을 조율할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;42&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실무에서의 전략적 선택 기준&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wd7gT/dJMcacYiLeP/00pXOdFNMNH15hZ4OXxc50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wd7gT/dJMcacYiLeP/00pXOdFNMNH15hZ4OXxc50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wd7gT/dJMcacYiLeP/00pXOdFNMNH15hZ4OXxc50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwd7gT%2FdJMcacYiLeP%2F00pXOdFNMNH15hZ4OXxc50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;519&quot; height=&quot;237&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;442&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ1gI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;44&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Boot 환경에서는 컴포넌트 스캔이 기본 설정으로 작동합니다. 따라서 &lt;b&gt;&quot;직접 작성하는 비즈니스 코드는 자동 등록, 외부 라이브러리 및 공통 기술 설정 객체는 수동 등록&quot;을 표준 원칙으로 삼으면 명확하고 깔끔한 구성이 완성&lt;/b&gt;됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;46&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;47&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;47,0,0&quot;&gt;Bean 등록 메커니즘:&lt;/b&gt; @Component 계열 기반의 &lt;b data-index-in-node=&quot;32&quot; data-path-to-node=&quot;47,0,0&quot;&gt;자동 등록&lt;/b&gt;과 `@Configuration` + `@Bean` 기반의 &lt;b data-index-in-node=&quot;66&quot; data-path-to-node=&quot;47,0,0&quot;&gt;수동 등록&lt;/b&gt; 두 가지 방식이 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;47,1,0&quot;&gt;등록 후 동작:&lt;/b&gt; 수동이든 자동이든 일단 컨테이너에 등록된 이후에는 동등하게 Spring 프레임워크의 생명주기 및 부가기능 관리를 받습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;47,2,0&quot;&gt;실무 원칙:&lt;/b&gt; 업무 로직 클래스는 자동 등록으로 생산성을 높이고, 기술적인 설정이나 외부 라이브러리는 수동 등록으로 명시적 관리와 제어권을 가져갑니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;48&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 파트에서는 자동 등록의 핵심 축인 &lt;b data-index-in-node=&quot;22&quot; data-path-to-node=&quot;48&quot;&gt;Component Scan이 애플리케이션 시작 시점에 내부적으로 어떤 순서와 원리로 동작하는지&lt;/b&gt; 상세히 살펴봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;50&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;51&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;51&quot;&gt;Q1. Spring에서 Bean을 등록하는 대표적인 두 가지 방식은 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;52,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;52,0&quot;&gt;컴포넌트 스캔을 통한 자동 등록&lt;/b&gt;(`@Component`, `@Service`, `@Repository` 등)과 &lt;b data-index-in-node=&quot;56&quot; data-path-to-node=&quot;52,0&quot;&gt;자바 설정 클래스를 통한 수동 등록&lt;/b&gt;(`@Configuration` + `@Bean`) 두 가지 방식이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;53&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;53&quot;&gt;Q2. @Component와 @Bean의 차이점은 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;54,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;@Component&lt;/b&gt;는 개발자가 직접 작성한 &lt;b data-index-in-node=&quot;28&quot; data-path-to-node=&quot;54,0&quot;&gt;클래스 단위&lt;/b&gt;에 선언하여 컴포넌트 스캔을 통해 자동으로 Bean에 등록되도록 하는 어노테이션입니다. 반면 `@Bean`은 설정 클래스(`@Configuration`) 내의 &lt;b data-index-in-node=&quot;123&quot; data-path-to-node=&quot;54,0&quot;&gt;메서드 단위&lt;/b&gt;에 선언하여, 외부 라이브러리 객체나 복잡한 생성 로직을 가진 객체를 개발자가 직접 코드로 반환하여 등록할 때 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;55&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;55&quot;&gt;Q3. 실무에서는 자동 등록과 수동 등록을 어떤 기준으로 구분하여 사용하나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;56,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;컨트롤러, 서비스, 리포지토리 등 &lt;b data-index-in-node=&quot;19&quot; data-path-to-node=&quot;56,0&quot;&gt;직접 작성하는 비즈니스 애플리케이션 코드는 생산성을 위해 자동 등록(`@Component`)을 기본&lt;/b&gt;으로 사용합니다. 반면 소스 코드를 수정할 수 없는 &lt;b data-index-in-node=&quot;101&quot; data-path-to-node=&quot;56,0&quot;&gt;외부 라이브러리 객체&lt;/b&gt;, &lt;b data-index-in-node=&quot;114&quot; data-path-to-node=&quot;56,0&quot;&gt;생성 로직이 복잡한 기술 지원 객체&lt;/b&gt;, 또는 &lt;b&gt;공통 환경 설정 객체는 수동 등록(`@Bean`)&lt;/b&gt;을 사용하여 명시적으로 관리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;57&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;57&quot;&gt;Q4. 수동으로 등록한 Bean과 자동으로 등록된 Bean은 컨테이너 내에서 다르게 동작하나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;58,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;아니오, 다르게 동작하지 않습니다. 등록 경로의 차이일 뿐 &lt;b data-index-in-node=&quot;33&quot; data-path-to-node=&quot;58,0&quot;&gt;컨테이너에 등록된 이후에는 모두 동일한 Spring Bean으로 관리&lt;/b&gt;되며, DI, 생명주기 콜백, AOP 및 트랜잭션 적용 등 모든 프레임워크 기능을 동일하게 제공받습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2.6 Component Scan (컴포넌트 스캔)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;앞서 Spring Bean을 등록하는 방법에는 &lt;b data-index-in-node=&quot;26&quot; data-path-to-node=&quot;4&quot;&gt;자동 등록&lt;/b&gt;과 &lt;b data-index-in-node=&quot;33&quot; data-path-to-node=&quot;4&quot;&gt;수동 등록&lt;/b&gt; 두 가지가 있음을 살펴보았습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 중 개발자가 일일이 설정 코드를 작성하지 않아도 Bean 자동 등록을 가능하게 만드는 Spring의 핵심 메커니즘이 바로 컴포넌트 스캔(Component Scan)입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Boot 프로젝트에서 별도의 설정 없이 클래스 상단에 `@Service`, `@Repository`, `@Controller`만 붙여도 알아서 Bean으로 등록 및 관리되는 이유가 바로 이 Component Scan 덕분입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Component Scan이란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Component Scan은 &lt;b data-index-in-node=&quot;16&quot; data-path-to-node=&quot;9&quot;&gt;지정된 패키지 경로 및 하위 패키지를 탐색하면서 Bean으로 등록할 대상 클래스들을 자동으로 찾아 식별하는 기능&lt;/b&gt;입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring은 구동 시점에 클래스에 붙어 있는 특정 어노테이션을 확인하고, 해당 메타데이터를 기반으로 객체를 생성하여 Spring IoC 컨테이너의 Bean으로 수집합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ9gI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Service
public class MemberService {
    // 개발자가 직접 AppConfig에 new MemberService()를 작성하지 않아도
    // Component Scan에 의해 Spring Bean으로 자동 등록됨
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Component Scan의 전체 동작 과정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Component Scan은 애플리케이션 시작 시점에 다음과 같은 순서로 실행되어 Bean 등록과 의존성 주입까지 완성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-path-to-node=&quot;14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Application 시작 (`@SpringBootApplication` 실행)&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Component Scan 탐색 개시 (기정 패키지 기준)&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;패키지 수집 및 `@Component` 계열 클래스 탐색&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Bean 생성 및 IoC 컨테이너 등록&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;의존성 주입(Dependency Injection) 연쇄 수행&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ9wI&quot; data-hveid=&quot;0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;16&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,0,0&quot;&gt;탐색 단계:&lt;/b&gt; 설정된 메인 패키지 하위의 모든 `.class` 파일을 스캔합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,1,0&quot;&gt;식별 단계:&lt;/b&gt; `@Component` 또는 이를 내포한 어노테이션이 부착된 클래스를 선별합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,2,0&quot;&gt;등록 단계:&lt;/b&gt; 선별된 클래스의 인스턴스를 생성하여 Spring 컨테이너의 Bean으로 등록합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,3,0&quot;&gt;연결 단계:&lt;/b&gt; 등록된 Bean들 간의 의존 관계(DI)를 파악하고 필요한 주입을 처리합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;탐색 대상 어노테이션 (Stereotype Annotation)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Component Scan의 기본 스캔 대상은 `@Component`입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;하지만 실제 개발 시에는 `@Component` 외에도 역할과 계층을 명확히 나타내는 &lt;b&gt;스테레오타입 어노테이션(Stereotype Annotation)을 사용&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;21&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,0,0&quot;&gt;`@Component`:&lt;/b&gt; 자동 등록의 가장 기본이 되는 어노테이션&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,1,0&quot;&gt;`@Controller`:&lt;/b&gt; Spring MVC 웹 요청 처리 계층&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,2,0&quot;&gt;`@Service`:&lt;/b&gt; 비즈니스 로직 계층&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,3,0&quot;&gt;`@Repository`:&lt;/b&gt; 데이터 접근 계층 (데이터베이스 예외 추상화 제공)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,4,0&quot;&gt;`@Configuration`:&lt;/b&gt; Spring 설정 정보 계층 (CGLIB 프록시 적용)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;스테레오타입 어노테이션의 내부 구조&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 어노테이션들이 Component Scan의 대상이 될 수 있는 이유는 &lt;b data-index-in-node=&quot;41&quot; data-path-to-node=&quot;23&quot;&gt;내부 메타 어노테이션으로 `@Component`를 포함&lt;/b&gt;하고 있기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ-AI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 메타 어노테이션으로 @Component를 포함
public @interface Service {
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;자바 언어 차원에서는 어노테이션의 상속이라는 개념이 없지만, Spring은 메타 어노테이션을 추적하여 `@Service`, `@Repository`, `@Controller` 등을 모두 `@Component`로 인식하고 Scan 대상에 포함시킵니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;탐색 시작 위치 (Base Package)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Component Scan은 프로젝트나 JVM 내의 모든 클래스를 무작정 탐색하지 않습니다. &lt;br /&gt;탐색 시간 효율과 오동작 방지를 위해 &lt;b data-index-in-node=&quot;73&quot; data-path-to-node=&quot;28&quot;&gt;기준이 되는 시작 패키지&lt;/b&gt;가 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ-QI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@SpringBootApplication // 내부적으로 @ComponentScan이 포함되어 있음
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Boot에서는 `@SpringBootApplication`이 위치한 클래스의 패키지가 Component Scan의 시작 기준점(Base Package)이 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;패키지 구조 배치가 중요한 이유&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;만약 다음과 같은 프로젝트 구조가 있다고 가정해 봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;804&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pAEkM/dJMcaiKU7br/O9DZdJBQPPMJRivYOSJUA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pAEkM/dJMcaiKU7br/O9DZdJBQPPMJRivYOSJUA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pAEkM/dJMcaiKU7br/O9DZdJBQPPMJRivYOSJUA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpAEkM%2FdJMcaiKU7br%2FO9DZdJBQPPMJRivYOSJUA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;490&quot; height=&quot;258&quot; data-origin-width=&quot;804&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;34&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`@SpringBootApplication이 com.example` 루트 패키지에 위치하므로, 하위의 member, order, common 패키지 내의 모든 `@Component` 계열 클래스가 정상적으로 탐색됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;만약 `Application.java`가 `com.example.member` 하위로 이동한다면, order나 common 패키지의 클래스들은 &lt;b data-index-in-node=&quot;76&quot; data-path-to-node=&quot;34,1,0&quot;&gt;Scan 범위를 벗어나 Spring Bean으로 등록되지 않습니다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;따라서 Spring Boot 프로젝트에서는 &lt;b data-index-in-node=&quot;24&quot; data-path-to-node=&quot;35&quot;&gt;메인 클래스를 항상 최상위(Root) 패키지에 위치시키는 것&lt;/b&gt;이 중요한 관례입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Component Scan과 의존성 주입(DI)의 차이&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;38&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;두 개념은 같이 일하지만 &lt;b data-index-in-node=&quot;14&quot; data-path-to-node=&quot;38&quot;&gt;명확히 분리된 역할&lt;/b&gt;을 수행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;39&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;39,0,0&quot;&gt;Component Scan:&lt;/b&gt; 컨테이너에 올릴 &lt;b data-index-in-node=&quot;25&quot; data-path-to-node=&quot;39,0,0&quot;&gt;Bean을 찾아서 등록&lt;/b&gt;하는 역할&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;39,1,0&quot;&gt;Dependency Injection (DI):&lt;/b&gt; 이미 등록된 &lt;b data-index-in-node=&quot;34&quot; data-path-to-node=&quot;39,1,0&quot;&gt;Bean 간의 관계를 연결&lt;/b&gt;해 주는 역할&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ-wI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class MemberService {
    private final MemberRepository repository;

    // 생성자 주입
    public MemberService(MemberRepository repository) {
        this.repository = repository;
    }
}

@Repository
public class JpaMemberRepository implements MemberRepository { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQ_AI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;[1단계: Component Scan]
MemberService 클래스 발견  ──&amp;gt; MemberService Bean 등록
JpaMemberRepository 발견 ──&amp;gt; JpaMemberRepository Bean 등록

[2단계: Dependency Injection]
MemberService 생성자 확인 ──&amp;gt; 등록된 JpaMemberRepository Bean을 찾아 주입
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;42&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Component Scan이 객체를 생성해 컨테이너에 먼저 채워놓아야, DI가 그 객체들을 찾아 서로 연결해 줄 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;44&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Component Scan의 장점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;45&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;45,0,0&quot;&gt;반복적인 설정 코드 제거:&lt;/b&gt; 새로운 클래스를 만들 때마다 설정 파일(AppConfig)을 수정할 필요가 없어 생산성이 극대화됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;45,1,0&quot;&gt;코드 일관성 유지:&lt;/b&gt; 계층별 어노테이션 명시 규칙을 통해 프로젝트 전반의 구조가 일관되게 유지됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;45,2,0&quot;&gt;Spring Boot와의 완벽한 조화:&lt;/b&gt; 별도의 복잡한 XML이나 자바 설정 없이 애플리케이션 개발에만 집중할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;47&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;48&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;48,0,0&quot;&gt;Component Scan 메커니즘:&lt;/b&gt; 지정된 패키지 경로 하위를 탐색하여 `@Component` 및 스테레오타입 어노테이션이 붙은 클래스를 찾아 자동으로 Bean에 등록합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;48,1,0&quot;&gt;루트 패키지 관례:&lt;/b&gt; Spring Boot는 `@SpringBootApplication`이 위치한 최상위 패키지를 기준으로 하위 경로 전체를 탐색하므로 메인 클래스의 위치 설정이 매우 중요합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;48,2,0&quot;&gt;역할 분담:&lt;/b&gt; Component Scan은 Bean의 &lt;b data-index-in-node=&quot;29&quot; data-path-to-node=&quot;48,2,0&quot;&gt;등록&lt;/b&gt;을 담당하고, DI는 등록된 Bean 간의 &lt;b data-index-in-node=&quot;55&quot; data-path-to-node=&quot;48,2,0&quot;&gt;연결&lt;/b&gt;을 담당합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;49&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 파트에서는 `@Component`라는 하나의 어노테이션으로 통일할 수 있음에도 왜 `@Controller`, `@Service`, `@Repository`로 &lt;b data-index-in-node=&quot;83&quot; data-path-to-node=&quot;49&quot;&gt;구분하여 사용하는지 각 어노테이션별 차이점과 부가 기능&lt;/b&gt;을 자세히 살펴봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;51&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;52&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;52&quot;&gt;Q1. Component Scan이란 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;53,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;지정된 시작 패키지 및 하위 경로를 탐색하여 `@Component` 및 스테레오타입 어노테이션이 부착된 클래스들을 발굴하고, Spring IoC 컨테이너의 Bean으로 자동 등록해 주는 기능입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;54&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;54&quot;&gt;Q2. `@Service`나 `@Repository`는 `@Component`가 아닌데 어떻게 Component Scan의 대상이 되나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;55,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`@Service`, `@Repository`, `@Controller` 등의 어노테이션 내부 메타 어노테이션으로 `@Component`가 포함되어 있기 때문입니다. Spring은 메타 어노테이션까지 추적하여 탐색 대상으로 인지합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;56&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;56&quot;&gt;Q3. Component Scan과 Dependency Injection(DI)의 차이는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;57,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Component Scan은 탐색을 통해 객체를 생성하고 &lt;b data-index-in-node=&quot;32&quot; data-path-to-node=&quot;57,0&quot;&gt;Spring Bean으로 등록하는 단계&lt;/b&gt;이며, DI는 등록된 &lt;b data-index-in-node=&quot;65&quot; data-path-to-node=&quot;57,0&quot;&gt;Bean들 간의 의존 관계를 파악하여 주입해 주는 단계&lt;/b&gt;입니다. Scan이 선행되어야 DI가 정상 동작할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;57,0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;58&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;58&quot;&gt;Q4. Spring Boot 메인 클래스를 프로젝트의 최상위 패키지에 두어야 하는 이유는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;59,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Boot의 `@SpringBootApplication` 내부에 포함된 Component Scan은 기본적으로 &lt;b data-index-in-node=&quot;66&quot; data-path-to-node=&quot;59,0&quot;&gt;메인 클래스가 속한 패키지를 기준점(Base Package)으로 하위 패키지를 탐색&lt;/b&gt;하기 때문입니다. 최상위에 두지 않으면 일부 패키지가 탐색 대상에서 누락되는 문제가 발생할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2.7 @Component, @Service, @Repository, @Controller의 차이&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;앞서 Component Scan은 @Component 계열의 어노테이션이 부착된 클래스를 탐색하여 자동으로 Spring Bean으로 등록한다는 것을 살펴보았습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그렇다면 다음과 같은 의문이 생깁니다. &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;i data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0&quot;&gt;&quot;어차피 모두 @Component를 기반으로 동작하여 Bean으로 등록된다면, 왜 굳이 어노테이션을 여러 개로 나누어 사용할까요?&quot; &lt;/i&gt;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;결론부터 말하면 &lt;b data-index-in-node=&quot;9&quot; data-path-to-node=&quot;7&quot;&gt;Bean 등록 메커니즘은 동일&lt;/b&gt;하지만, 클래스의 계층적 역할을 명확히 표현(가독성&amp;middot;유지보수성)하고 &lt;b data-index-in-node=&quot;63&quot; data-path-to-node=&quot;7&quot;&gt;Spring Framework 차원의 추가 기능(예외 변환 등)을 제공&lt;/b&gt;하기 위해 구분합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 모든 어노테이션의 뿌리는 `@Component`&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 네 가지 스테레오타입 어노테이션은 모두 Component Scan의 대상입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;`@Component`&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;`@Controller`&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,2,0&quot;&gt;`@Service`&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,3,0&quot;&gt;`@Repository`&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;예를 들어 `@Service` 어노테이션의 내부 선언을 살펴보면 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQlwM&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 메타 어노테이션으로 @Component를 내포
public @interface Service {
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;따라서 아래 두 코드는 Spring IoC 컨테이너에 Bean으로 등록된다는 관점에서는 완전하게 동일합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQmAM&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// 1. @Component 사용
@Component
public class MemberService { ... }

// 2. @Service 사용
@Service
public class MemberService { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 어노테이션별 역할과 특징&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`@Component` (기본 컴포넌트)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;19&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,0,0&quot;&gt;역할:&lt;/b&gt; 가장 기본이 되는 Bean 등록 어노테이션입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,1,0&quot;&gt;사용 대상:&lt;/b&gt; 웹, 비즈니스, 데이터 접근 계층 등 명확한 아키텍처 레이어에 속하지 않는 공통 유틸리티나 기술 지원 객체에 주로 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,2,0&quot;&gt;예시:&lt;/b&gt; JwtProvider, FileStorageManager, RedisClientWrapper 등&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`@Service` (비즈니스 계층)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;21&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,0,0&quot;&gt;역할:&lt;/b&gt; 비즈니스 로직 및 트랜잭션 경계를 담당하는 서비스 계층임을 명시합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,1,0&quot;&gt;특징:&lt;/b&gt; 기술적으로 Spring이 제공하는 특별한 추가 기능은 없습니다. 하지만 해당 클래스가 순수한 핵심 도메인 로직을 처리하는 주체임을 개발자에게 명확하게 전달합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`@Repository` (데이터 접근 계층)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;23&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23,0,0&quot;&gt;역할:&lt;/b&gt; 데이터 베이스 접근 계층(DAO, Repository)임을 명시합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23,1,0&quot;&gt;특징 (추가 기능 제공):&lt;/b&gt; 단순한 역할 명시를 넘어 &lt;b data-index-in-node=&quot;29&quot; data-path-to-node=&quot;23,1,0&quot;&gt;Spring의 예외 변환(Exception Translation) 기능&lt;/b&gt;이 적용됩니다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;23,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;JPA, Hibernate, JDBC 등 persistence 기술에서 발생하는 고유한 예외(e.g., SQLException, PersistenceException)를 Spring의 공통 예외 체계인 &lt;b data-index-in-node=&quot;113&quot; data-path-to-node=&quot;23,1,1,0,0&quot;&gt;DataAccessException 계층으로 추상화하여 변환&lt;/b&gt;해 줍니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 덕분에 DB 접근 기술이 바뀌더라도 서비스 계층의 예외 처리 로직은 일관되게 유지할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiAi8Sz6faVAxUAAAAAHQAAAAAQmQM&quot; data-hveid=&quot;0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`@Controller` &amp;amp; `@RestController` (표현 계층)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;26&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;26,0,0&quot;&gt;`@Controller`:&lt;/b&gt; Spring MVC의 웹 요청을 수신하고 View(HTML)를 반환하는 컨트롤러 계층에 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;26,1,0&quot;&gt;`@RestController`:&lt;/b&gt; RESTful API 웹 서비스를 구축할 때 사용하며, `@Controller` + `@ResponseBody`의 결합 형태입니다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;26,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;반환 객체가 View 페이지로 연결되지 않고, &lt;b data-index-in-node=&quot;26&quot; data-path-to-node=&quot;26,1,1,0,0&quot;&gt;HTTP Response Body에 JSON/XML 형태&lt;/b&gt;로 직접 직렬화되어 응답됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. 계층 구조와 어노테이션 매핑&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 애플리케이션은 아키텍처 관점에서 보통 다음과 같은 4계층(Layered Architecture) 구조를 따릅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2Fu0x/dJMcagfjTbx/qYEwhEzYaIB8yCZcbCUPtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2Fu0x/dJMcagfjTbx/qYEwhEzYaIB8yCZcbCUPtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2Fu0x/dJMcagfjTbx/qYEwhEzYaIB8yCZcbCUPtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2Fu0x%2FdJMcagfjTbx%2FqYEwhEzYaIB8yCZcbCUPtk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;562&quot; height=&quot;396&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 150px;&quot; border=&quot;1&quot; data-path-to-node=&quot;31&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;계층 (Layer)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;어노테이션&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;주요 역할 및 특징&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 62px;&quot;&gt;
&lt;td style=&quot;height: 62px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;31,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,1,0,0&quot;&gt;Presentation Layer&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 62px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`@Controller`,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`@RestController`&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 62px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;31,1,2,0&quot;&gt;HTTP 요청 매핑, 파라미터 검증, 응답 반환 (View 또는 JSON)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;31,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,2,0,0&quot;&gt;Business Layer&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;31,2,1,0&quot;&gt;`@Service`&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;31,2,2,0&quot;&gt;핵심 비즈니스 로직 수행, 트랜잭션 경계 설정 (`@Transactional`)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;31,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,3,0,0&quot;&gt;Persistence Layer&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;31,3,1,0&quot;&gt;`@Repository`&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;31,3,2,0&quot;&gt;DB CRUD 작업, persistence 예외를 DataAccessException으로 추상화 변환&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;31,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,4,0,0&quot;&gt;Common / Utility&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;31,4,1,0&quot;&gt;`@Component`&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;31,4,2,0&quot;&gt;특정 계층에 속하지 않는 유틸리티, 헬퍼 클래스, 기술 공통 객체&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. 실무에서의 선택 기준&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실무에서는 &lt;b data-index-in-node=&quot;6&quot; data-path-to-node=&quot;34&quot;&gt;클래스의 아키텍처 역할에 맞는 어노테이션을 사용하는 것을 엄격하게 권장&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;35&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;35,0,0&quot;&gt;컨트롤러:&lt;/b&gt; `@RestController` (REST API 기준)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;35,1,0&quot;&gt;비즈니스 로직:&lt;/b&gt; `@Service`&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;35,2,0&quot;&gt;데이터 접근:&lt;/b&gt; `@Repository`&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;35,3,0&quot;&gt;공통 컴포넌트:&lt;/b&gt; `@Component`&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;36&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;모든 클래스에 `@Component`만 붙여도 기술적으로 동작에는 문제가 없습니다. 그러나 역할에 맞는 어노테이션을 쓰면 &lt;b data-index-in-node=&quot;66&quot; data-path-to-node=&quot;36&quot;&gt;코드의 의미(Intent)가 명확해져 가독성과 유지보수성&lt;/b&gt;이 높아지며, `@Repository`의 예외 변환과 같이 &lt;b data-index-in-node=&quot;128&quot; data-path-to-node=&quot;36&quot;&gt;Spring이 레이어별로 제공하는 부가 기능&lt;/b&gt;을 안전하게 누릴 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;38&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;39&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;39,0,0&quot;&gt;공통점:&lt;/b&gt; `@Component`, `@Service`, `@Repository`, `@Controller`는 모두 내부적으로 `@Component`를 포함하므로 Component Scan에 의해 스캔 및 자동 등록됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;39,1,0&quot;&gt;차이점:&lt;/b&gt; 계층별 의미 전달(가독성)과 명확한 의도 구분을 가능하게 하며, 특히 `@Repository`는 데이터 접근 예외 추상화 변환 기능, `@RestController`는 HTTP 응답 본문 직렬화 기능 등을 선언적으로 제공합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;39,2,0&quot;&gt;실무 원칙:&lt;/b&gt; 코드 가독성, 예외 처리 구조화, 프레임워크 기능 활용을 위해 항상 계층에 부합하는 어노테이션을 선언하여 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;43&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;44&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44&quot;&gt;Q1. `@Component`와 `@Service`의 차이는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;45,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;기술적으로 Spring Bean으로 등록되는 메커니즘은 동일합니다. 하지만 `@Service`는 해당 클래스가 비즈니스 로직을 처리하는 서비스 계층임을 명시적으로 나타내어 가독성을 높이고 아키텍처 레이어를 명확히 하는 역할을 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;46&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;46&quot;&gt;Q2. 모든 데이터 접근 클래스에 `@Component` 대신 `@Repository`를 붙여야 하는 기술적인 이유는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;47,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;데이터 접근 계층임을 명시하는 것 외에도, Spring의 &lt;b data-index-in-node=&quot;32&quot; data-path-to-node=&quot;47,0&quot;&gt;예외 변환(Exception Translation) AOP 기능&lt;/b&gt;이 적용되기 때문입니다. 이를 통해 JPA나 JDBC 등 하위 데이터베이스 접근 기술에서 발생한 기술 종속적 예외를 Spring의 공통 예외 체계인 `DataAccessException`으로 추상화하여 변환해 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;48&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;48&quot;&gt;Q3. `@Controller`와 `@RestController`는 어떻게 다른가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;49,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`@Controller`는 전통적인 Spring MVC 어노테이션으로 메서드의 반환값을 View 이름으로 해석하여 HTML 화면을 렌더링합니다. 반면 `@RestController`는 `@Controller`에 `@ResponseBody`가 합성된 어노테이션으로, 반환 데이터를 View가 아닌 HTTP Response Body에 JSON/XML 형식으로 직접 직렬화하여 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;50&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;50&quot;&gt;Q4. 프로젝트의 모든 클래스에 @Component만 사용하면 어떻게 되나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;50&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #666666; text-align: start;&quot;&gt; 애플리케이션 실행 및 Bean 등록 자체는 정상적으로 동작합니다. 하지만 각 클래스의 역할과 아키텍처 계층을 한눈에 파악하기 힘들어 유지보수성이 저하되며, `@Repository`가 제공하는 예외 변환 프록시 기능 등 계층 특화 부가 기능을 활용할 수 없게 됩니다. &lt;/span&gt;&lt;/p&gt;</description>
      <category> SpringBoot</category>
      <category>Spring</category>
      <author>limdaeil</author>
      <guid isPermaLink="true">https://limdaeil.tistory.com/85</guid>
      <comments>https://limdaeil.tistory.com/85#entry85comment</comments>
      <pubDate>Wed, 29 Jul 2026 12:27:40 +0900</pubDate>
    </item>
    <item>
      <title>Spring: Part 1. Spring Framework의 이해</title>
      <link>https://limdaeil.tistory.com/84</link>
      <description>&lt;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Part 1. Spring Framework의 이해&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;h2 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1.1 Spring Framework란?&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Framework는 Java 기반 애플리케이션을 효율적으로 개발하기 위한 오픈소스 프레임워크입니다. 현재 Java 백엔드 개발에서 가장 널리 사용되며 웹, 배치, 마이크로서비스 등 다양한 환경에서 활용됩니다. Spring을 제대로 이해하기 위해서는 단순히 어노테이션 사용법을 익히는 것을 넘어&lt;b&gt;, &quot;Spring이 왜 등장했고 어떤 문제를 해결하려 했는가&quot;를 아는 것이 출발점&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Framework의 등장 배경&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;초기 Java 웹 애플리케이션은 Servlet과 JSP를 중심으로 개발되었습니다. &lt;br /&gt;당시에는 객체를 코드 내부에서 직접 생성하고 연결하는 방식이 일반적이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjM6Yveq_WVAxUAAAAAHQAAAAAQxwE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public class MemberService {

    // Service가 Repository를 직접 생성
    private final MemberRepository memberRepository = new MemberRepository();

    public void join(Member member) {
        memberRepository.save(member);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;겉보기에는 단순한 코드 같지만, 객체가 의존 객체를 직접 생성하는 구조에는 &lt;b data-index-in-node=&quot;42&quot; data-path-to-node=&quot;10&quot;&gt;3가지 치명적인 문제&lt;/b&gt;가 존재합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;책임의 과중:&lt;/b&gt; MemberService가 자신의 비즈니스 역할뿐만 아니라 MemberRepository의 생성과 연결까지 direct로 담당합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;높은 결합도(Coupling):&lt;/b&gt; 구현체가 변경되면 이를 사용하는 서비스 코드도 반드시 수정되어야 합니다.(예: 메모리 저장소를 JPA로 변경 시 `new JpaMemberRepository()`로 직접 수정 필요)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,2,0&quot;&gt;테스트의 어려움:&lt;/b&gt; 가짜 구현체(Mock/Fake)를 외부에서 주입하기 어렵기 때문에, 단순 단위 테스트를 진행하려 해도 실제 DB 환경에 의존해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring이 해결하려는 문제&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring은 &lt;b data-index-in-node=&quot;8&quot; data-path-to-node=&quot;14&quot;&gt;객체 생성과 객체 간 연결 책임을 애플리케이션 코드에서 완전히 분리&lt;/b&gt;합니다. &lt;br /&gt;개발자는 객체의 역할과 비즈니스 로직에만 집중하고, 객체를 만들어 이어주는 작업은 &lt;b data-index-in-node=&quot;96&quot; data-path-to-node=&quot;14&quot;&gt;Spring 컨테이너&lt;/b&gt;에 위임합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjM6Yveq_WVAxUAAAAAHQAAAAAQyAE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    private final MemberRepository memberRepository;

    // 외부(Spring)에서 생성된 객체를 주입받음
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 코드에는 `new MemberRepository()`가 존재하지 않습니다.&lt;br /&gt;Spring이 필요한 객체를 미리 생성해 두고 주입해 주기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이러한 제어의 흐름이 바뀐 구조를 &lt;b&gt;제어의 역전(IoC, Inversion of Control)&lt;/b&gt;이라 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;필요한 객체를 외부에서 전달받는 과정을 &lt;b&gt;의존성 주입(DI, Dependency Injection)&lt;/b&gt;이라 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;애플리케이션 코드와 인프라 코드의 분리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;개발 과정에서 반복적으로 작성되는 기술적 코드(트랜잭션 관리, DB 연결, 로깅, 보안 등) 역시 Spring이 대신 처리해 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;결과적으로 개발자는 비즈니스 핵심 로직에만 집중할 수 있게 되며, 다음과 같은 명확한 이점을 얻습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;21&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체 간 결합도가 낮아져 구현체 변경이 쉬워짐&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;외부에서 가짜 객체(Mock)를 주입할 수 있어 단위 테스트가 용이해짐&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;SOLID 같은 객체지향 설계 원칙을 자연스럽게 준수하게 됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Framework의 핵심은 단순히 &quot;객체를 대신 만들어 주는 편리함&quot;에 있지 않습니다. &lt;b data-index-in-node=&quot;56&quot; data-path-to-node=&quot;24&quot;&gt;객체의 생성과 사용을 분리하여 객체지향 설계를 구조적으로 지원하는 것&lt;/b&gt;이 본질입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 원리를 이해하면 이어지는 IoC, DI, Bean, AOP, Transaction, MVC까지의 모든 개념이 하나의 흐름으로 자연스럽게 이어지게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;28&quot;&gt;Q1. Spring Framework를 사용하는 본질적인 이유는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;29,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;단순히 어노테이션을 제공해 편의성을 높이는 것이 아니라, &lt;b data-index-in-node=&quot;32&quot; data-path-to-node=&quot;29,0&quot;&gt;객체의 생성과 관리를 컨테이너에 맡김으로써 객체지향 설계 원칙(SOLID)을 자연스럽게 지킬 수 있도록 돕기 때문&lt;/b&gt;입니다. 또한 트랜잭션, AOP 등 공통 인프라 코드를 프레임워크가 담당하여 개발자가 비즈니스 로직에만 집중할 수 있게 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;30&quot;&gt;Q2. Spring이 해결하고자 한 전통적인 Java 개발의 문제는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;31,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체가 다른 의존 객체를 직접 생성(new)하면서 발생하는 &lt;b data-index-in-node=&quot;33&quot; data-path-to-node=&quot;31,0&quot;&gt;높은 결합도와 테스트의 어려움&lt;/b&gt;입니다. 구현체가 변경될 때마다 비즈니스 코드까지 수정해야 하는 문제를 해결하기 위해, 객체의 생성과 연결 책임을 컨테이너로 넘기는 구조를 제시했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;32&quot;&gt;Q3. Spring에서 객체 생성과 의존성 관리는 어디서 담당하나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;33,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 컨테이너(IoC 컨테이너)가 담당합니다. 애플리케이션 실행 시 필요한 객체(Bean)를 직접 생성 및 관리하며, 필요한 곳에 주입(DI)하여 객체 간의 관계를 완성해 줍니다.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1.2 Spring의 핵심 개념&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Framework를 제대로 활용하려면 먼저 Spring을 받치고 있는&lt;b&gt; 5가지 핵심 개념&lt;/b&gt;을 이해해야 합니다. Spring은 단순히 웹 애플리케이션을 빠르게 개발하기 위한 도구 모음이 아니라, &lt;b data-index-in-node=&quot;113&quot; data-path-to-node=&quot;5&quot;&gt;객체지향 설계를 가장 효과적으로 적용할 수 있도록 지원하는 프레임워크&lt;/b&gt;입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring의 핵심 철학은 다음 다섯 가지로 요약할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;POJO (Plain Old Java Object)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;IoC (Inversion of Control)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,2,0&quot;&gt;DI (Dependency Injection)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,3,0&quot;&gt;AOP (Aspect-Oriented Programming)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,4,0&quot;&gt;PSA (Portable Service Abstraction)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 개념들은 서로 독립적으로 존재하는 것이 아니라, &lt;b&gt;객체지향 설계를 완성하기 위해 긴밀하게 연결되어 동작&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. POJO (Plain Old Java Object)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;POJO는 &lt;b data-index-in-node=&quot;6&quot; data-path-to-node=&quot;11&quot;&gt;특정 프레임워크나 기술에 강하게 의존하지 않는 순수한 Java 객체&lt;/b&gt;를 의미합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring이 등장하기 이전에는 EJB 같은 기술을 사용하기 위해 프레임워크 전용 클래스를 상속받거나 인터페이스를 강제로 구현해야 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjM6Yveq_WVAxUAAAAAHQAAAAAQ6wE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;// 프레임워크 기술에 강하게 결합된 형태
public class MemberService extends SomeFrameworkService {

    public void join(Member member) {
        ...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이러한 구조는 애플리케이션 코드가 특정 프레임워크와 강하게 결합되어 종속성을 만듭니다. &lt;br /&gt;반면, Spring은 개발자가 작성하는 비즈니스 코드가 순수한 Java 형태를 유지할 수 있도록 설계되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjM6Yveq_WVAxUAAAAAHQAAAAAQ7AE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;// 순수한 Java 객체 (POJO)
@Service
public class MemberService {

    public void join(Member member) {
        ...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;MemberService는 프레임워크의 특정 클래스를 상속받지 않습니다. 어노테이션을 제외하면 완전히 순수한 Java 코드입니다. &lt;b data-index-in-node=&quot;73&quot; data-path-to-node=&quot;16&quot;&gt;Spring은 객체 외부에서 필요한 인프라 기능을 제공하므로, 비즈니스 로직에만 전념하는 POJO 작성이 가능해집니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. IoC (Inversion of Control)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;IoC는 &lt;b data-index-in-node=&quot;5&quot; data-path-to-node=&quot;19&quot;&gt;제어의 역전&lt;/b&gt;을 의미합니다. 애플리케이션 흐름의 주도권이 개발자에서 프레임워크로 넘어간 상태입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;일반적인 Java 개발에서는 개발자가 필요한 객체를 직접 생성하고 관리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjM6Yveq_WVAxUAAAAAHQAAAAAQ7QE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;// 개발자가 직접 객체 생성 및 생명주기 관리
MemberRepository repository = new MemberRepository();
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;반면 Spring 환경에서는 &lt;b data-index-in-node=&quot;16&quot; data-path-to-node=&quot;22&quot;&gt;객체의 생성, 생명주기, 소멸까지의 관리 책임이 Spring 컨테이너로 이동&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjM6Yveq_WVAxUAAAAAHQAAAAAQ7gE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    private final MemberRepository repository;

    // 객체 생성은 컨테이너가 맡고, 서비스는 주입받아 사용만 함
    public MemberService(MemberRepository repository) {
        this.repository = repository;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;MemberService는 MemberRepository가 언제, 어떻게 만들어지는지 알 필요가 없습니다. &lt;br /&gt;객체 생성 및 관리에 대한 제어권이 개발자에서 컨테이너로 역전된 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. DI (Dependency Injection)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;DI는 &lt;b data-index-in-node=&quot;4&quot; data-path-to-node=&quot;27&quot;&gt;의존성 주입&lt;/b&gt;을 의미합니다. &lt;br /&gt;객체가 필요로 하는 의존 객체를 직접 만들지 않고, &lt;b data-index-in-node=&quot;48&quot; data-path-to-node=&quot;27&quot;&gt;외부(Spring 컨테이너)에서 생성하여 전달(주입)받는 방식&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;28&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;28,0,0&quot;&gt;IoC가 개념&lt;/b&gt;이라면, &lt;b data-index-in-node=&quot;12&quot; data-path-to-node=&quot;28,0,0&quot;&gt;DI는 그 개념을 실제로 구현하는 대표적인 구체적 방법&lt;/b&gt;입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjM6Yveq_WVAxUAAAAAHQAAAAAQ7wE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// 생성자를 통한 의존성 주입 (Constructor Injection)
public MemberService(MemberRepository repository) {
    this.repository = repository;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring은 MemberRepository를 먼저 생성한 후, 이를 필요로 하는 MemberService의 생성자를 통해 전달합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;31&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,0,0&quot;&gt;구현체 교체의 용이성:&lt;/b&gt; 설정 변경만으로 DB 접근 방식을 쉽게 교체 가능&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,1,0&quot;&gt;테스트 편의성:&lt;/b&gt; 단위 테스트 시 가짜 객체(Mock)를 전달하기 쉬움&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,2,0&quot;&gt;낮은 결합도:&lt;/b&gt; 인터페이스에 의존함으로써 코드 변경의 영향을 최소화&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. AOP (Aspect-Oriented Programming)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;AOP는 &lt;b data-index-in-node=&quot;5&quot; data-path-to-node=&quot;34&quot;&gt;관점 지향 프로그래밍&lt;/b&gt;으로, 핵심 비즈니스 로직과 공통으로 반복되는 기술(횡단 관심사)을 분리하는 개념입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;애플리케이션 개발 시 비즈니스 로직 외에도 다음과 같은 공통 기능이 끊임없이 반복됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;36&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;36,0,0&quot;&gt;로깅 (Logging)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;36,1,0&quot;&gt;트랜잭션 처리 (Transaction)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;36,2,0&quot;&gt;보안 검사 (Security)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;36,3,0&quot;&gt;실행 시간 측정 (Performance)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;AOP가 없다면 모든 메서드에 중복 코드를 작성해야 하므로 핵심 로직이 비대해지고 유지보수가 지저분해집니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjM6Yveq_WVAxUAAAAAHQAAAAAQ8AE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;// AOP 적용 전: 비즈니스 로직과 공통 로직이 섞임
public void join(Member member) {
    log.info(&quot;회원 가입 시작&quot;);          // 공통 로직
    memberRepository.save(member);     // 핵심 비즈니스 로직
    log.info(&quot;회원 가입 완료&quot;);          // 공통 로직
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;39&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring은 AOP를 통해 공통 기능을 한곳에 분리하여 관리합니다. 대표적인 사례가 바로 @Transactional 어노테이션입니다. 개발자가 트랜잭션 시작/커밋/롤백 코드를 작성하지 않아도, Spring이 메서드 실행 전후로 공통 로직을 자동 삽입해 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;41&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5. PSA (Portable Service Abstraction)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;42&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;PSA는 &lt;b data-index-in-node=&quot;5&quot; data-path-to-node=&quot;42&quot;&gt;일관된 방식으로 다양한 기술을 사용할 수 있도록 제공하는 서비스 추상화&lt;/b&gt;입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;데이터 접근 기술만 하더라도 JDBC, JPA, MyBatis 등 다양한 선택지가 존재하며 각각의 사용 문법이 다릅니다. Spring은 이 기술들을 일관된 인터페이스로 감싸 추상화(PSA)해 둡니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;44&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44,0,0&quot;&gt;대표 사례:&lt;/b&gt; PlatformTransactionManager&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;JPA를 쓰든, JDBC를 쓰든 @Transactional이라는 동일한 방식으로 트랜잭션을 처리할 수 있는 이유가 바로 PSA 덕분입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;45&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;기술이 변경되더라도 애플리케이션의 비즈니스 코드는 크게 수정할 필요 없이, 추상화된 인터페이스를 그대로 활용할 수 있어 &lt;b data-index-in-node=&quot;67&quot; data-path-to-node=&quot;45&quot;&gt;특정 기술에 종속되지 않는 유연성&lt;/b&gt;을 얻게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;47&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5가지 핵심 개념의 유기적 관계&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;48&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 다섯 가지 개념은 따로 놀지 않고 하나의 큰 그림으로 연결됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1332&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkJuzV/dJMcagl3JvN/KxvbeWIz3tUIjm6wJJiZSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkJuzV/dJMcagl3JvN/KxvbeWIz3tUIjm6wJJiZSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkJuzV/dJMcagl3JvN/KxvbeWIz3tUIjm6wJJiZSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkJuzV%2FdJMcagl3JvN%2FKxvbeWIz3tUIjm6wJJiZSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;661&quot; height=&quot;140&quot; data-origin-width=&quot;1332&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;50&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;개발자는 순수한 Java 객체(&lt;b data-index-in-node=&quot;17&quot; data-path-to-node=&quot;50,0,0&quot;&gt;POJO&lt;/b&gt;)로 비즈니스 로직을 작성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring의 &lt;b data-index-in-node=&quot;8&quot; data-path-to-node=&quot;50,1,0&quot;&gt;IoC/DI&lt;/b&gt;가 객체의 생명주기를 관리하고 의존성을 연결&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;반복되는 인프라 코드는 &lt;b data-index-in-node=&quot;13&quot; data-path-to-node=&quot;50,2,0&quot;&gt;AOP&lt;/b&gt;로 깔끔하게 분리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;외부 기술이나 DB 연결 기술은 &lt;b data-index-in-node=&quot;18&quot; data-path-to-node=&quot;50,3,0&quot;&gt;PSA&lt;/b&gt;로 추상화하여 일관되게 접근&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;52&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;53&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;53&quot;&gt;Q1. Spring의 5가지 핵심 개념은 무엇이며, 각각 어떤 역할을 하나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;54,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;POJO, IoC, DI, AOP, PSA입니다. &lt;b data-index-in-node=&quot;28&quot; data-path-to-node=&quot;54,0&quot;&gt;POJO&lt;/b&gt;는 특정 프레임워크에 종속되지 않는 순수 자바 객체를, &lt;b data-index-in-node=&quot;60&quot; data-path-to-node=&quot;54,0&quot;&gt;IoC/DI&lt;/b&gt;는 객체 생성과 의존성 관리의 주도권을 컨테이너로 넘기는 것을, &lt;b data-index-in-node=&quot;102&quot; data-path-to-node=&quot;54,0&quot;&gt;AOP&lt;/b&gt;는 로깅/트랜잭션 같은 공통 기능의 분리를, &lt;b data-index-in-node=&quot;130&quot; data-path-to-node=&quot;54,0&quot;&gt;PSA&lt;/b&gt;는 다양한 기술을 일관되게 다루기 위한 추상화를 담당합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;55&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;55&quot;&gt;Q2. IoC와 DI의 개념적 차이는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;56,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;IoC(Inversion of Control)&lt;/b&gt;는 객체의 생성과 관리 제어권이 개발자에서 프레임워크로 넘어간다는 '상위의 설계 원칙/개념'입니다. &lt;b&gt;DI(Dependency Injection)&lt;/b&gt;는 그 IoC 개념을 달성하기 위해 외부에서 의존 객체를 주입해 주는 '실제 구체적인 패턴/구현 방법'입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;57&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;57&quot;&gt;Q3. PSA(Portable Service Abstraction)란 무엇이며 왜 필요한가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;58,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;환경이나 밑단에 깔린 기술(JDBC, JPA 등)이 바뀌어도 개발자가 동일한 접근 방식(인터페이스)을 유지할 수 있도록 돕는 &lt;b data-index-in-node=&quot;70&quot; data-path-to-node=&quot;58,0&quot;&gt;서비스 추상화 layer&lt;/b&gt;입니다. 특정 기술에 코드가 종속되는 것을 막아 변경과 유연성에 강한 구조를 만들어 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1.3 객체지향과 Spring&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Framework를 제대로 이해하기 위해서는 객체지향 프로그래밍(OOP)의 기본 원칙을 함께 알아야 합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring은 세상에 없던 새로운 객체지향 개념을 만들어 낸 것이 아닙니다. &lt;b data-index-in-node=&quot;43&quot; data-path-to-node=&quot;6&quot;&gt;좋은 객체지향 설계를 실무 애플리케이션에 현실적이고 유연하게 적용할 수 있도록 지원하는 프레임워크&lt;/b&gt;입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;특히 Spring은 다음 3가지 핵심 원칙을 자연스럽게 준수할 수 있도록 돕습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;역할과 구현의 분리&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0&quot;&gt;DIP (Dependency Inversion Principle, 의존관계 역전 원칙)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,2,0&quot;&gt;OCP (Open-Closed Principle, 개방-폐쇄 원칙)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;역할과 구현의 분리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체지향 설계의 핵심은 &lt;b&gt;역할(Role)&lt;/b&gt;과 &lt;b&gt;구현(Implementation)&lt;/b&gt;을 명확히 분리하는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;역할:&lt;/b&gt; 객체가 제공해야 하는 기능의 명세 (Java의 `interface`)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0&quot;&gt;구현:&lt;/b&gt; 역할을 실제로 수행하는 구체적인 클래스 (Java의 `class`)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;예를 들어 회원을 저장하고 조회하는 &lt;b data-index-in-node=&quot;20&quot; data-path-to-node=&quot;13&quot;&gt;역할&lt;/b&gt;을 인터페이스로 정의합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjM6Yveq_WVAxUAAAAAHQAAAAAQmQI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface MemberRepository {
    void save(Member member);
    Member findById(Long id);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 역할은 다양한 방식으로 &lt;b data-index-in-node=&quot;15&quot; data-path-to-node=&quot;15&quot;&gt;구현&lt;/b&gt;될 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjM6Yveq_WVAxUAAAAAHQAAAAAQmgI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;// 메모리 저장 방식 구현체
public class MemoryMemberRepository implements MemberRepository {
    @Override
    public void save(Member member) { ... }
}

// JPA 데이터베이스 저장 방식 구현체
public class JpaMemberRepository implements MemberRepository {
    @Override
    public void save(Member member) { ... }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;역할과 구현을 분리하면, 데이터 저장 방식을 메모리에서 DB(JPA)로 변경하더라도 &lt;b data-index-in-node=&quot;47&quot; data-path-to-node=&quot;17&quot;&gt;역할을 사용하는 서비스 코드는 전혀 변경할 필요가 없습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;인터페이스에 의존하기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체는 구체적인 구현 클래스보다 &lt;b data-index-in-node=&quot;18&quot; data-path-to-node=&quot;20&quot;&gt;인터페이스(추상화)에 의존&lt;/b&gt;하는 것이 바람직합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Bad Case: 구현 클래스에 직접 의존하는 경우&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjM6Yveq_WVAxUAAAAAHQAAAAAQmwI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;public class MemberService {
    // 구현체에 직접 의존 -&amp;gt; 저장 기술 변경 시 Service 코드도 수정해야 함
    private final JpaMemberRepository repository = new JpaMemberRepository();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Good Case: 인터페이스에 의존하는 경우&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjM6Yveq_WVAxUAAAAAHQAAAAAQnAI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class MemberService {
    private final MemberRepository repository;

    // 외부(Spring)에서 인터페이스의 실제 구현체를 주입받음
    public MemberService(MemberRepository repository) {
        this.repository = repository;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;MemberService는 MemberRepository라는 인터페이스만 바라봅니다. &lt;br /&gt;실제 들어오는 객체가 메모리 저장소든 JPA든, 비즈니스 로직에는 아무런 영향을 주지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;DIP (Dependency Inversion Principle)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;DIP는 &lt;b data-index-in-node=&quot;5&quot; data-path-to-node=&quot;28&quot;&gt;의존관계 역전 원칙&lt;/b&gt;으로, SOLID 원칙 중 하나입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;29&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;상위 수준 모듈은 하위 수준 모듈에 의존해서는 안 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;29,1,0&quot;&gt;&quot;추상화(인터페이스)에 의존해야지, 구체화(구현 클래스)에 의존해서는 안 된다.&quot;&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;구현 클래스(JpaMemberRepository)에 직접 의존하면 DIP 위반입니다. 반면 MemberRepository 인터페이스에 의존하면 DIP를 만족합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring은 &lt;b data-index-in-node=&quot;8&quot; data-path-to-node=&quot;31&quot;&gt;DI(의존성 주입)를 통해 외부에서 구현체를 넣어줌으로써 개발자가 DIP를 자연스럽게 지키도록 유도&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;OCP (Open-Closed Principle)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;OCP는 &lt;b data-index-in-node=&quot;5&quot; data-path-to-node=&quot;34&quot;&gt;개방-폐쇄 원칙&lt;/b&gt;으로, 객체지향 설계의 가장 중요한 목표 중 하나입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;35&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;35,0,0&quot;&gt;확장에는 열려(Open)&lt;/b&gt; 있어야 하고, &lt;b data-index-in-node=&quot;22&quot; data-path-to-node=&quot;35,0,0&quot;&gt;수정에는 닫혀(Closed)&lt;/b&gt; 있어야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;즉, &lt;b data-index-in-node=&quot;3&quot; data-path-to-node=&quot;35,1,0&quot;&gt;기능을 추가하거나 변경해도 기존 비즈니스 코드는 수정하지 않아야 합니다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;36&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring 환경에서는 저장 기술을 변경할 때 서비스 코드를 건드리지 않고, &lt;b data-index-in-node=&quot;43&quot; data-path-to-node=&quot;36&quot;&gt;설정 파일(Bean 등록 정보)만 변경&lt;/b&gt;하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjM6Yveq_WVAxUAAAAAHQAAAAAQnQI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;// Java Configuration 파일에서 빈 교체만 진행
@Bean
public MemberRepository memberRepository() {
    // return new MemoryMemberRepository(); -&amp;gt; Jpa로 변경 시 한 줄만 수정
    return new JpaMemberRepository();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;38&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;새로운 기능을 추가하거나 구현체를 교체할 때 &lt;b data-index-in-node=&quot;25&quot; data-path-to-node=&quot;38&quot;&gt;기존 비즈니스 로직(MemberService)은 전혀 수정할 필요가 없으므로 OCP를 완벽히 만족&lt;/b&gt;하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;40&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring이 객체지향 설계를 지원하는 방식&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;41&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring은 좋은 설계를 개발자에게 강제하지 않습니다. 대신 &lt;b data-index-in-node=&quot;35&quot; data-path-to-node=&quot;41&quot;&gt;좋은 객체지향 설계를 적용하기 가장 편한 판을 깔아줍니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;42&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;42,0,0&quot;&gt;운영 환경:&lt;/b&gt; JPA 기반 저장소 주입&lt;/span&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public MemberRepository memberRepository() {
    return new JpaMemberRepository();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;42,1,0&quot;&gt;테스트 환경:&lt;/b&gt; 가짜/메모리 저장소 주입&lt;/span&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean
public MemberRepository memberRepository() {
    return new MemoryMemberRepository();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;43&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;어떤 환경이든 MemberService 코드는 단 한 줄도 수정하지 않습니다. Spring이 객체 생성과 연결을 대신해 주기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;45&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;45&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체지향 설계의 핵심은 &lt;b data-index-in-node=&quot;13&quot; data-path-to-node=&quot;46&quot;&gt;역할과 구현을 분리하여 추상화(인터페이스)에 의존하는 것&lt;/b&gt;입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring은 IoC와 DI라는 무기를 통해 이러한 객체지향 원칙(DIP, OCP)을 실무 개발에서 자연스럽게 적용할 수 있게 해줍니다. 우리가 Spring을 사용하는 진정한 가치는 단순히 기능이 많아서가 아니라, &lt;b data-index-in-node=&quot;120&quot; data-path-to-node=&quot;47&quot;&gt;유연하고 변경에 강한 좋은 객체지향 애플리케이션을 만들 수 있게 돕기 때문&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;49&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;49&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;50&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;50&quot;&gt;Q1. 역할과 구현을 분리하면 어떤 이점이 있나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;51,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;구현체가 바뀌거나 새로운 기능이 추가되어도 &lt;b data-index-in-node=&quot;24&quot; data-path-to-node=&quot;51,0&quot;&gt;역할(인터페이스)을 사용하는 클라이언트 코드를 전혀 수정할 필요가 없습니다.&lt;/b&gt; 이를 통해 시스템의 확장성과 유지보수성이 극대화됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;52&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;52&quot;&gt;Q2. DIP(의존관계 역전 원칙)란 무엇이며 Spring은 이를 어떻게 지원하나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;53,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;DIP는 구체적인 구현 클래스가 아닌 &lt;b data-index-in-node=&quot;21&quot; data-path-to-node=&quot;53,0&quot;&gt;추상화(인터페이스)에 의존해야 한다는 원칙&lt;/b&gt;입니다. Spring은 IoC 컨테이너가 DI(의존성 주입)를 통해 필요한 구현체 객체를 외부에서 주입해 줌으로써 개발자가 DIP를 쉽게 준수할 수 있도록 돕습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;54&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;54&quot;&gt;Q3. Spring 환경에서 OCP(개방-폐쇄 원칙)는 어떻게 달성되나요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;54&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; letter-spacing: 0px;&quot;&gt;DI와 Bean 설정 관리를 통해 달성됩니다. 새로운 구현체로 교체하더라도 설정 코드(Bean 등록 부분)만 변경하면 되며, 핵심 비즈니스 로직(Service)은 &lt;/span&gt;&lt;b data-index-in-node=&quot;91&quot; data-path-to-node=&quot;55,0&quot;&gt;수정에 닫힌 상태를 유지하면서 확장에는 열린 구조&lt;/b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; letter-spacing: 0px;&quot;&gt;를 완성할 수 있습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1.4 Spring Framework와 Spring Boot의 차이&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring을 처음 접할 때 가장 흔히 오해하는 것 중 하나가 &lt;b data-index-in-node=&quot;35&quot; data-path-to-node=&quot;4&quot;&gt;Spring Framework&lt;/b&gt;와 &lt;b data-index-in-node=&quot;53&quot; data-path-to-node=&quot;4&quot;&gt;Spring Boot&lt;/b&gt;를 서로 다른 별개의 기술로 보거나, 아예 같은 기술로 치부하는 것입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;결론부터 정리하자면, &lt;b data-index-in-node=&quot;12&quot; data-path-to-node=&quot;5&quot;&gt;Spring Framework가 애플리케이션 개발의 본질이자 기반 기술&lt;/b&gt;이라면, Spring Boot는 이 Spring Framework를 개발자가 훨씬 편리하게 다룰 수 있도록 돕는 도구(추가 도구)입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Framework란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Framework는 애플리케이션 개발에 필요한 핵심 기능 모음과 원칙을 제공하는 프레임워크입니다. &lt;br /&gt;우리가 흔히 이야기하는 Spring의 본질적 핵심 기능은 전부 Spring Framework가 담당합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;IoC 컨테이너 &amp;amp; DI&lt;/b&gt; (객체 생명주기 관리 및 주입)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;Bean 관리&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0&quot;&gt;AOP&lt;/b&gt; (공통 관심사 분리)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,3,0&quot;&gt;Transaction&lt;/b&gt; (선언적 트랜잭션 관리)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,4,0&quot;&gt;Spring MVC&lt;/b&gt; (웹 애플리케이션 구조)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,5,0&quot;&gt;Event, Test Support&lt;/b&gt; 등&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjM6Yveq_WVAxUAAAAAHQAAAAAQwwI&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class MemberService {

    private final MemberRepository memberRepository;

    // @Service를 통한 Bean 등록과 생성자 주입은 모두 Spring Framework의 기능
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이처럼 우리가 작성하는 자바 코드의 핵심 동작은 모두 Spring Framework 기반 위에서 작동합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Boot가 등장한 이유&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Framework는 매우 강력하지만, 과거에는 프로젝트 하나를 시작하기 위한 &lt;b data-index-in-node=&quot;50&quot; data-path-to-node=&quot;14&quot;&gt;초기 설정(Configuration)이 지나치게 복잡하고 번거롭다&lt;/b&gt;는 단점이 있었습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;과거에는 프로젝트 시작을 위해 개발자가 직접 다음과 같은 일들을 해주어야 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;16&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;수많은 라이브러리 간의 버전 호환성 직접 관리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;DispatcherServlet 및 각종 Resolver, Converter 수동 등록&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Component Scan 범위 및 XML/Java Config 설정&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;외장 WAS(Tomcat 등) 설치, 설정 및 WAR 파일 배포&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이러한 수많은 설정 작업은 개발 속도를 저하시켰고, 설정상의 자그마한 실수로 애플리케이션이 구동조차 되지 않는 일이 비일재했습니다. &lt;b data-index-in-node=&quot;74&quot; data-path-to-node=&quot;17&quot;&gt;Spring Boot는 바로 이러한 반복적이고 까다로운 설정을 자동화하여 개발 편의성을 높이기 위해 등장&lt;/b&gt;했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Boot가 추가한 핵심 기능&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Boot는 Spring Framework 위에 올려진 보조 프레임워크로, 개발 생산성을 대폭 향상시켜 주는 강력한 기능들을 탑재하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 자동 설정 (Auto Configuration)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;프로젝트에 포함된 라이브러리(Jar) 패스 상태를 분석하여, 개발자가 하나하나 지정하지 않아도 &lt;b data-index-in-node=&quot;53&quot; data-path-to-node=&quot;22&quot;&gt;필요한 Bean 설정을 자동으로 구성&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;23&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;예: 클래스패스에 spring-webmvc가 존재하면, DispatcherServlet, HandlerMapping 등을 보이지 않는 곳에서 알아서 빈으로 등록합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. Starters (의존성 관리 단순화)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;관련된 라이브러리 묶음을 하나의 스타터(Starter) 패키지로 묶어 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjM6Yveq_WVAxUAAAAAHQAAAAAQxAI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;// 단 한 줄의 선언으로 웹 개발에 필요한 핵심 라이브러리와 버전이 자동 호환 정리됨
implementation 'org.springframework.boot:spring-boot-starter-web'
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;개발자가 개별 라이브러리의 버전을 호환성 있게 일일이 맞출 필요 없이, 스타터 하나만 지정하면 안정된 버전 조합이 알아서 당겨져 옵니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. 내장 웹 서버 (Embedded Server)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;별도의 외부 Tomcat/Jetty 같은 WAS를 설치하고 설정할 필요 없이, &lt;b data-index-in-node=&quot;44&quot; data-path-to-node=&quot;29&quot;&gt;Spring Boot 내부에 Tomcat이 포함되어 작동&lt;/b&gt;합니다. 자바 메인 메서드만 실행하면 애플리케이션과 웹 서버가 동시에 구동됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. 실행 가능한 Fat JAR (Executable JAR)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;WAS 배포용 `WAR` 파일 대신, 내장 서버 및 모든 의존 라이브러리를 하나로 포함한 JAR 파일 생성을 지원합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjM6Yveq_WVAxUAAAAAHQAAAAAQxQI&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;java -jar application.jar
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;단 한 줄의 커맨드로 서버 환경 어디서든 복잡한 배포 과정 없이 앱을 바로 실행시킬 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5. production-ready 운영 기능 (Actuator)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;운영 환경에 필요한 Health Check, Metrics 정보, 애플리케이션 모니터링 환경 등을 &lt;b data-index-in-node=&quot;55&quot; data-path-to-node=&quot;35&quot;&gt;Spring Boot Actuator&lt;/b&gt;를 통해 손쉽게 구성할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Framework와 Spring Boot의 관계&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;38&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;38&quot;&gt;Spring Boot는 Spring Framework를 대체하는 기술이 결코 아닙니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjM6Yveq_WVAxUAAAAAHQAAAAAQxgI&quot; data-hveid=&quot;0&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;937&quot; data-origin-height=&quot;457&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4drMp/dJMcaa0s3XP/Q9MsK1kZI4eIHRzs4BOdv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4drMp/dJMcaa0s3XP/Q9MsK1kZI4eIHRzs4BOdv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4drMp/dJMcaa0s3XP/Q9MsK1kZI4eIHRzs4BOdv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4drMp%2FdJMcaa0s3XP%2FQ9MsK1kZI4eIHRzs4BOdv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;531&quot; height=&quot;259&quot; data-origin-width=&quot;937&quot; data-origin-height=&quot;457&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;40&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Boot 내부를 열어보면 실제 비즈니스 로직과 웹 요청을 처리하는 본체는 전부 Spring Framework입니다. Boot는 오직 이를 &quot;더 편하고 빠르게 사용할 수 있게 포장하고 조율해 주는 도구&quot;일 뿐입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실무 환경에서는 100%에 가깝게 Spring Boot를 사용하여 프로젝트를 구성합니다. 하지만 &lt;b data-index-in-node=&quot;54&quot; data-path-to-node=&quot;43&quot;&gt;Spring Boot가 뒤에서 알아서 자동화해 주는 원리를 이해하려면, 반드시 Spring Framework의 핵심 원리를 먼저 알아야 합니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;44&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성자 주입 원리를 모르면, Boot가 자동 주입해 주는 내부 동작을 이해할 수 없습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;DispatcherServlet의 역할을 모르면, Boot가 알아서 등록해 주는 Web MVC 설정의 의미를 파악할 수 없어 트러블슈팅에 한계가 옵니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;45&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;따라서 가장 올바른 학습 단계는 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;46&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;46,0,0&quot;&gt;Spring Framework의 핵심 원리&lt;/b&gt; (IoC, DI, AOP, Transaction 등) 체득&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;46,1,0&quot;&gt;Spring Boot의 자동 설정(Auto Configuration) 원리&lt;/b&gt; 이해&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;46,2,0&quot;&gt;Spring Boot를 활용한 실무 프로젝트 개발 및 응용&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;48&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;49&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;49,0,0&quot;&gt;Spring Framework:&lt;/b&gt; 애플리케이션 개발의 뼈대가 되는 핵심 기능(IoC, DI, AOP, MVC 등)을 다루는 프레임워크입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;49,1,0&quot;&gt;Spring Boot:&lt;/b&gt; Spring Framework를 시작할 때 발생하는 복잡한 설정 작업을 자동화(Auto-Config, Starter, 내장 Tomcat 등)하여 개발 생산성을 높여주는 도구입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;50&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실무 개발은 Spring Boot 환경에서 이루어지지만, 그 안에서 구동되는 모든 원리의 중심에는 Spring Framework가 존재한다는 점을 명심해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;52&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;52&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 질문&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;53&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;53&quot;&gt;Q1. Spring Framework와 Spring Boot의 핵심 차이는 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;54,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;54,0&quot;&gt;Spring Framework&lt;/b&gt;는 IoC/DI, AOP, Transaction, MVC 등 백엔드 개발에 필요한 핵심 기능 원천을 제공하는 프레임워크입니다. &lt;b data-index-in-node=&quot;87&quot; data-path-to-node=&quot;54,0&quot;&gt;Spring Boot&lt;/b&gt;는 이러한 Spring Framework를 더 쉽게 시작하고 사용할 수 있도록 자동 설정(Auto Configuration), Starter 의존성 관리, 내장 웹 서버(Tomcat) 등을 제공하는 프로젝트입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;55&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;55&quot;&gt;Q2. Spring Boot는 Spring Framework를 대체하는 기술인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;56,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;아니오, 그렇지 않습니다. &lt;b data-index-in-node=&quot;15&quot; data-path-to-node=&quot;56,0&quot;&gt;Spring Boot는 Spring Framework 위에서 구동되는 확장/보조 도구&lt;/b&gt;입니다. 실제로 애플리케이션을 지탱하고 비즈니스 로직을 처리하는 핵심 구조(IoC 컨테이너 등)는 모두 Spring Framework의 기능을 그대로 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;57&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;57&quot;&gt;Q3. Spring Boot가 제공하는 가장 큰 이점(장점)들은 무엇인가요?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;57&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; letter-spacing: 0px;&quot;&gt;1) 복잡한 설정을 자동화해 주는 &lt;/span&gt;&lt;b data-index-in-node=&quot;16&quot; data-path-to-node=&quot;58,0,0,0&quot;&gt;Auto Configuration&lt;/b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; letter-spacing: 0px;&quot;&gt;, 2) 라이브러리 버전 관리를 단순화하는 &lt;/span&gt;&lt;b data-index-in-node=&quot;58&quot; data-path-to-node=&quot;58,0,0,0&quot;&gt;Starter&lt;/b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; letter-spacing: 0px;&quot;&gt;, 3) 외부 WAS 설치가 필요 없는 &lt;/span&gt;&lt;b data-index-in-node=&quot;87&quot; data-path-to-node=&quot;58,0,0,0&quot;&gt;내장 웹 서버(Embedded Tomcat)&lt;/b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; letter-spacing: 0px;&quot;&gt;, 4) 단일 파일로 손쉽게 배포&amp;middot;실행 가능한 &lt;/span&gt;&lt;b data-index-in-node=&quot;137&quot; data-path-to-node=&quot;58,0,0,0&quot;&gt;Executable JAR&lt;/b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; letter-spacing: 0px;&quot;&gt; 제공을 통한 개발 생산성의 극대화입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> SpringBoot</category>
      <category>Spring</category>
      <author>limdaeil</author>
      <guid isPermaLink="true">https://limdaeil.tistory.com/84</guid>
      <comments>https://limdaeil.tistory.com/84#entry84comment</comments>
      <pubDate>Tue, 28 Jul 2026 21:57:13 +0900</pubDate>
    </item>
    <item>
      <title>누구나 이해하는 클라우드 인프라 지식 사전: 백엔드는 개발해봤는데, 서비스는 어떻게 안정적으로 운영하는 사고력 키우기</title>
      <link>https://limdaeil.tistory.com/80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;서평단 활동 소개&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&quot;한빛미디어 서평단 &amp;lt;나는리뷰어다&amp;gt; 활동을 위해서 책을 협찬 받아 작성된 서평입니다.&quot;&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;354&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3qXXQ/dJMcadQllLY/CnINHkKjp1icoORHp6RWPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3qXXQ/dJMcadQllLY/CnINHkKjp1icoORHp6RWPk/img.png&quot; data-alt=&quot;누구나 이해하는 클라우드 인프라 지식 사전&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3qXXQ/dJMcadQllLY/CnINHkKjp1icoORHp6RWPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3qXXQ%2FdJMcadQllLY%2FCnINHkKjp1icoORHp6RWPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;412&quot; height=&quot;428&quot; data-origin-width=&quot;354&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;누구나 이해하는 클라우드 인프라 지식 사전&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;백엔드는 개발해봤는데, 인프라는 얼마나 알고 있을까?&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;두 달만에..! 리뷰로 읽은 책은 &lt;b&gt;누구나 이해하는 클라우드 인프라 지식 사전 &lt;/b&gt;입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;저는 지금까지 백엔드 개발을 공부하면서 Java와 Spring Boot를 중심으로 API를 만들고, 데이터베이스를 연결하고, 인증/인가를 구현하는 등 애플리케이션 개발에 집중해왔습니다. 그리고 아시다시피, 백엔드 개발자 뿐만 아니라 모든 IT 개발 직무를 담당하시는 분들은 특이하게도 인프라 뿐만 아니라 솔루션 프로그램부터 문서 정리까지..? 전부 다 하는 경험은 모두 해봤을 거라고 슬픈 확신을 합니다... ㅎㅎ &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그렇게&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;프로젝트를 진행하다 보면 자연스럽게 Docker도 사용하게 되고, 서버를 배포하면서 클라우드도 접하게 됩니다. Nginx, DNS, HTTPS, CI/CD 같은 기술들도 하나씩 사용하게 되고요. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그런데 어느 순간 이런 생각이 들었습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;나는 이것들을 정말 이해하고 사용하는 것이기 보다는, 그냥 필요한 순간마다 검색해서 사용하고 있는데... 올바른 태도가 맞는걸까? 이러한 &quot;생각&quot;과 함께 다음과 같은 &quot;상황&quot;에 놓이면 어떻게 될지 가끔 고민이 들었어요.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그런데 서비스가 갑자기 느려지면 어디부터 확인해야 할까?&lt;/li&gt;
&lt;li&gt;사용자가 많아지면 서버 사양을 높여야 할까? 아니면 서버를 여러 대로 늘려야 할까?&lt;/li&gt;
&lt;li&gt;애플리케이션에서는 정상적으로 처리됐는데 사용자에게 응답이 제대로 전달되지 않는다면 어디에서 문제가 발생한 걸까?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;여기까지 생각해보니 단순히 백엔드 코드를 잘 작성하는 것과 &lt;b&gt;서비스를 안정적으로 운영하는 것은 또 다른 영역&lt;/b&gt;이라는 생각이 들었습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그래서 이번에는 애플리케이션을 만드는 방법보다, &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;&quot;내가 만든 애플리케이션은 실제로 어떤 환경에서 실행되고 운영되는가?&quot; &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 부분을 제대로 공부해보고 싶었습니다. 이때&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;감사하게도, 서평으로 읽게 된 책이 바로 한빛미디에서 새롭게 출간된 &lt;b&gt;누구나 이해하는 클라우드 인프라 지식 사전&lt;/b&gt; 입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 책의 가장 큰 특징은 수많은 인프라 관점에서 다루는 책들과 다르게, &lt;b&gt;인프라 담당자가 아니라 백엔드 개발자라는 입장에서 읽으면 정말 큰 도움이 되는 항목들로 구성을 했다는 점을 크게 느꼈습니다.&lt;/b&gt; 특히 개발만 하는 백엔드에서, 운영까지 생각하는 백엔드로 &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 책을 읽으면서 가장 크게 느낀 부분은 개발자가 생각해야 하는 범위가 생각보다 훨씬 넓다는 것이었습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;평소 백엔드 프로젝트를 진행하면 아무래도 코드에 가장 많은 관심을 가지게 됩니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;API 설계는 괜찮은지, 객체의 책임은 적절하게 나누어져 있는지, 쿼리 성능은 괜찮은지, 예외 처리는 제대로 되어 있는지 등을 고민합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;하지만 실제 서비스는 애플리케이션 코드 하나만으로 동작하지 않습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;서버가 있고, OS가 있고, 네트워크가 있고, DNS와 HTTP/HTTPS가 있고, 데이터베이스가 있고, 그 위에서 애플리케이션이 실행됩니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;여기에 Docker와 Kubernetes 같은 컨테이너 기술이 들어가고, 클라우드 환경이 들어가고, 실제 서비스를 운영하기 시작하면 모니터링과 로깅, 백업, 보안까지 필요해집니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;책의 목차를 처음 봤을 때도 조금 놀랐습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;서버와 네트워크 정도를 다루는 책이라고 생각했는데, 가용성과 신뢰성부터 성능 튜닝, OS, Docker, Kubernetes, 클라우드, 모니터링, DevOps, SRE, 보안, 로그, 백업, 릴리스 엔지니어링까지 다루고 있었습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;결국 우리가 서비스 하나를 운영하기 위해 알아야 하는 영역을 전체적으로 보여주는 책에 가깝습니다.&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;스케일 업? 스케일 아웃? 그래서 왜 하는데...&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;개인적으로 재미있게 읽었던 부분 중 하나는 &lt;b&gt;가용성, 신뢰성 그리고 용량과 부하 관리&lt;/b&gt;에 대한 내용이었습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;스케일 업과 스케일 아웃이라는 단어 자체는 백엔드를 공부하면서 정말 많이 들어봤습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;서버 성능을 높이는 것이 Scale Up이고, 서버의 수를 늘리는 것이 Scale Out..! &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;여기까지만 보면 사실 어려운 개념은 아닙니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그런데 실제 서비스에서는 단순히 서버를 늘린다고 모든 문제가 해결되는 것은 절대 아니라는 사실은 알고 있습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;어디에서 병목이 발생하고 있는지 확인해야 하고, 처리량과 응답 시간을 생각해야 하고, 어떤 부분을 수평 확장할 수 있는지도 체계적인 계획을 통해 여러 문제들을 고려해야 합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;특히 성능 튜닝 부분을 읽으면서 &lt;b&gt;&quot;느리니까 서버부터 늘리자&quot;가 정답이 아니라는 것&lt;/b&gt;을 다시 한번 느꼈습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;결국 중요한 것은 현재 시스템의 병목 지점을 찾아내는 것입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;애플리케이션이 문제인지, 데이터베이스가 문제인지, 네트워크가 문제인지, 아니면 단순히 서버의 자원이 부족한 것인지 먼저 알아야 합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;백엔드 개발을 하면서 성능 최적화라고 하면 쿼리 튜닝이나 캐시부터 떠올렸는데, 이 책을 읽으면서 조금 더 넓은 관점에서 성능을 바라볼 수 있었습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;가장 관심 있게 본 모니터링과 관찰 가능성&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1288&quot; data-origin-height=&quot;964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c22swk/dJMb998idcw/4nHBXn9ArdDuuKcg2dtcT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c22swk/dJMb998idcw/4nHBXn9ArdDuuKcg2dtcT0/img.png&quot; data-alt=&quot;14-6 감시&amp;amp;middot;관찰 가능성 및 장애 대응 툴 일부 페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c22swk/dJMb998idcw/4nHBXn9ArdDuuKcg2dtcT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc22swk%2FdJMb998idcw%2F4nHBXn9ArdDuuKcg2dtcT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;687&quot; height=&quot;514&quot; data-origin-width=&quot;1288&quot; data-origin-height=&quot;964&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;14-6 감시&amp;middot;관찰 가능성 및 장애 대응 툴 일부 페이지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이번 책에서 개인적으로 특히 관심 있게 본 부분은 &lt;b&gt;모니터링과 관찰 가능성(Observability)&lt;/b&gt;이었습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;서비스를 만드는 것까지는 프로젝트를 통해 여러 번 경험할 수 있지만, 실제 운영 환경에서 장애를 발견하고 원인을 추적하는 경험은 상대적으로 접하기 어렵습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;개발 환경에서는 에러가 발생하면 IDE의 콘솔을 확인하면 됩니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;하지만 실제 서비스에서는 그렇게 단순하지 않습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;여러 서버와 컨테이너에서 애플리케이션이 실행될 수도 있고, 특정 사용자에게만 문제가 발생할 수도 있고, CPU나 메모리 같은 시스템 자원이 원인일 수도 있습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그래서 단순히 &quot;서버가 살아 있는가?&quot;를 확인하는 것을 넘어서 &lt;b&gt;시스템 내부에서 어떤 일이 발생하고 있는지를 관찰할 수 있어야 한다&lt;/b&gt;는 점이 인상 깊었습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;로그, 메트릭과 같은 데이터를 통해 시스템의 상태를 확인하고 장애가 발생했을 때 원인을 추적할 수 있는 구조를 만드는 것 역시 개발자가 생각해야 하는 부분이라는 것을 알 수 있었습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;특히 장애 대응과 포스트 모템까지 연결해서 설명한다는 점도 좋았습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;장애를 단순히 고치고 끝내는 것이 아니라, &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;왜 발생했는지 &amp;rarr; 어떻게 대응했는지 &amp;rarr; 다시 발생하지 않으려면 무엇을 바꿔야 하는지&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;까지 이어지는 과정 자체가 운영이라는 것을 이해할 수 있었습니다.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;처음부터 끝까지 외울 필요가 없는 책&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그리고 이 책의 장점 중 하나는 제목 그대로 &lt;b&gt;'지식 사전'처럼 활용해야 좋다고 생각합니다.&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;23장까지 있기 때문에 처음 목차를 보면 공부해야 할 내용이 굉장히 많다는 것은 사실입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;하지만 모든 내용을 처음부터 끝까지 외울 필요는 없다고 강력하게 생각합니다!&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;오히려 항목마다 모두 외우려고 하지 않고, 여행 브로셔를 읽듯이 새로운 관점을 넓힌다는 생각으로 읽었습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Docker가 궁금하면 가상화와 컨테이너 부분을 읽고, &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;서비스가 느려졌다면 용량과 부하 관리, 성능 튜닝 부분을 찾아보고, &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;배포 이후 운영에 관심이 생겼다면 모니터링과 로그, DevOps와 SRE 부분을 읽어보는 식입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그리고 하나의 개념을 공부하다 보면 다른 개념과 자연스럽게 연결됩니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;성능을 공부하다 캐시를 만나고, HTTP를 공부하다 HTTPS와 TLS로 넘어가고, 서비스를 운영하다 보면 모니터링과 로그가 필요해지는 식입니다. 다시 한 번 말하지만, &lt;b&gt;각 토픽마다 깊게 공부하는 것은 이 책의 올바른 방향이 아니라고 생각하기 때문에 저처럼 다음 세 가지를 기준 삼아 책을 읽으면 훨씬 더 큰 도움이 될 것이라고 생각해요!!!!&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;모두&amp;nbsp;외우기&amp;nbsp;금지!&amp;nbsp;개발자가&amp;nbsp;알아야&amp;nbsp;할&amp;nbsp;영역을&amp;nbsp;훑어보며&amp;nbsp;시야&amp;nbsp;넓히기!&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;여행 브로셔처럼 읽으며, 내가 구현한 프로젝트 인프라의 전체 지도를 그리기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;지금까지 흩어져 있던 기술 지식을 하나의 서비스 관점으로 연결하기&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;개발자로서 기본적으로 알아야 할 영역이 무엇인지 파악한 뒤, 그중 내가 관심 있는 분야를 선택해서 더 깊게 공부하는 것은 가능하다고 생각합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그래서 저는 이 책을 단순히 클라우드 인프라 용어를 공부하기 위한 책보다는, &lt;b&gt;치열해지는 개발자 시장에서 앞으로 어떤 지식을 갖춰야 하는지 점검하고, 나만의 강점을 어디에서 더 깊게 만들어갈지 판단하기 위한 시작점이 될 수 있는 책&lt;/b&gt;으로 추천하고 싶습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;저 역시 앞으로 백엔드 프로젝트를 만들 때 단순히 &quot;기능이 동작한다&quot;에서 끝내는 것이 아니라, &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;&quot;이 기능이 실제 서비스 환경에서도 안정적으로 운영될 수 있을까?&quot; &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그리고 한 걸음 더 나아가, &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;&quot;나는 이것을 제대로 이해하고 설명할 수 있는 개발자인가?&quot; &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;까지 고민하면서 공부해야겠다는 생각을 하게 된 책이었습니다 :)&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;학습에 도움이 되는 참고 사항&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;figure id=&quot;og_1785040386127&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;RFC 3986: Uniform Resource Identifier (URI): Generic Syntax&quot; data-og-description=&quot;A Uniform Resource Identifier (URI) is a compact sequence of characters that identifies an abstract or physical resource. This specification defines the generic URI syntax and a process for resolving URI references that might be in relative form, along wit&quot; data-og-host=&quot;datatracker.ietf.org&quot; data-og-source-url=&quot;https://datatracker.ietf.org/doc/html/rfc3986&quot; data-og-url=&quot;https://datatracker.ietf.org/doc/html/rfc3986&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/crw37e/dJMb9jOAoti/JjEgui8487rqt7Cxyyvank/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc3986&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://datatracker.ietf.org/doc/html/rfc3986&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/crw37e/dJMb9jOAoti/JjEgui8487rqt7Cxyyvank/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;RFC 3986: Uniform Resource Identifier (URI): Generic Syntax&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A Uniform Resource Identifier (URI) is a compact sequence of characters that identifies an abstract or physical resource. This specification defines the generic URI syntax and a process for resolving URI references that might be in relative form, along wit&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;datatracker.ietf.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1785040369579&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;RFC 2026: The Internet Standards Process -- Revision 3&quot; data-og-description=&quot;This memo documents the process used by the Internet community for the standardization of protocols and procedures. It defines the stages in the standardization process, the requirements for moving a document between stages and the types of documents used &quot; data-og-host=&quot;datatracker.ietf.org&quot; data-og-source-url=&quot;https://datatracker.ietf.org/doc/html/rfc2026&quot; data-og-url=&quot;https://datatracker.ietf.org/doc/html/rfc2026&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dCBuhk/dJMb9eT20zS/juY7kJLkKWGpQpWYJVWqz0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc2026&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://datatracker.ietf.org/doc/html/rfc2026&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dCBuhk/dJMb9eT20zS/juY7kJLkKWGpQpWYJVWqz0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;RFC 2026: The Internet Standards Process -- Revision 3&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This memo documents the process used by the Internet community for the standardization of protocols and procedures. It defines the stages in the standardization process, the requirements for moving a document between stages and the types of documents used&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;datatracker.ietf.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1785040354741&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;RFC 9293: Transmission Control Protocol (TCP)&quot; data-og-description=&quot;This document specifies the Transmission Control Protocol (TCP). TCP is an important transport-layer protocol in the Internet protocol stack, and it has continuously evolved over decades of use and growth of the Internet. Over this time, a number of change&quot; data-og-host=&quot;datatracker.ietf.org&quot; data-og-source-url=&quot;https://datatracker.ietf.org/doc/html/rfc9293&quot; data-og-url=&quot;https://datatracker.ietf.org/doc/html/rfc9293&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iH9eH/dJMb9efrycu/nlZSl3inAIYxTrEU1dksP0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9293&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://datatracker.ietf.org/doc/html/rfc9293&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iH9eH/dJMb9efrycu/nlZSl3inAIYxTrEU1dksP0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;RFC 9293: Transmission Control Protocol (TCP)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This document specifies the Transmission Control Protocol (TCP). TCP is an important transport-layer protocol in the Internet protocol stack, and it has continuously evolved over decades of use and growth of the Internet. Over this time, a number of change&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;datatracker.ietf.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1785040335797&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;RFC 9112: HTTP/1.1 | RFC Editor&quot; data-og-description=&quot;The Hypertext Transfer Protocol (HTTP) is a stateless application-level protocol for distributed, collaborative, hypertext information systems. This document specifies the HTTP/1.1 message syntax, message parsing, connection management, and related securit&quot; data-og-host=&quot;www.rfc-editor.org&quot; data-og-source-url=&quot;https://www.rfc-editor.org/info/rfc9112/&quot; data-og-url=&quot;https://www.rfc-editor.org/info/rfc9112/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/birjye/dJMb83SwGsy/RCg5sNm6gD81cxPWIFlWMK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://www.rfc-editor.org/info/rfc9112/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.rfc-editor.org/info/rfc9112/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/birjye/dJMb83SwGsy/RCg5sNm6gD81cxPWIFlWMK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;RFC 9112: HTTP/1.1 | RFC Editor&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The Hypertext Transfer Protocol (HTTP) is a stateless application-level protocol for distributed, collaborative, hypertext information systems. This document specifies the HTTP/1.1 message syntax, message parsing, connection management, and related securit&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.rfc-editor.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1785040318795&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;RFC 5322: Internet Message Format&quot; data-og-description=&quot;This document specifies the Internet Message Format (IMF), a syntax for text messages that are sent between computer users, within the framework of &amp;quot;electronic mail&amp;quot; messages. This specification is a revision of Request For Comments (RFC) 2822, which itsel&quot; data-og-host=&quot;datatracker.ietf.org&quot; data-og-source-url=&quot;https://datatracker.ietf.org/doc/html/rfc5322&quot; data-og-url=&quot;https://datatracker.ietf.org/doc/html/rfc5322&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/X7cgh/dJMb8YXZtVn/FNgQb3sZ1ijKK5mq68Kbdk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc5322&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://datatracker.ietf.org/doc/html/rfc5322&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/X7cgh/dJMb8YXZtVn/FNgQb3sZ1ijKK5mq68Kbdk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;RFC 5322: Internet Message Format&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This document specifies the Internet Message Format (IMF), a syntax for text messages that are sent between computer users, within the framework of &quot;electronic mail&quot; messages. This specification is a revision of Request For Comments (RFC) 2822, which itsel&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;datatracker.ietf.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> Retrospective</category>
      <category>나는리뷰어다</category>
      <category>누구나 이해하는 클라우드 인프라 지식 사전</category>
      <category>서평단</category>
      <category>책</category>
      <category>한빛미디어</category>
      <author>limdaeil</author>
      <guid isPermaLink="true">https://limdaeil.tistory.com/80</guid>
      <comments>https://limdaeil.tistory.com/80#entry80comment</comments>
      <pubDate>Sun, 26 Jul 2026 13:29:06 +0900</pubDate>
    </item>
    <item>
      <title>동시성 문제(Concurrency Issue): 3편. 주문만 안전하면 끝일까? 시스템 전체를 관통하는 동시성 설계와 한계 회고</title>
      <link>https://limdaeil.tistory.com/79</link>
      <description>&lt;h2 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;들어가며&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1편에서는 동시성 테스트를 통해 재고 정합성이 깨지는 현상을 재현했고, 2편에서는 비관적 락(Pessimistic Lock)을 통해 정합성을 보장하는 과정을 다뤘습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;처음에는 비관적 락 적용으로 모든 문제가 해결되었다고 생각했습니다. 하지만 구현을 진행하고 서비스를 확장할수록 한 가지 중요한 사실을 깨달았습니다. 바로, &lt;/span&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0&quot;&gt;&quot;동시성은 주문 기능 하나만 안전하게 만든다고 해결되는 문제가 아니다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;재고 데이터를 변경하는 주체는 주문 로직만이 아닙니다. 관리자의 재고 수정, 사용자 취소로 인한 재고 복구, 취소/환불 도메인 등 &lt;b data-index-in-node=&quot;73&quot; data-path-to-node=&quot;8&quot;&gt;동일한 데이터(Inventory)에 접근하는 모든 경로&lt;/b&gt;가 같은 동시성 규칙 하에 제어되지 않는다면 정합성은 언제든 다시 깨질 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이번 3편에서는 특정 단일 기능을 넘어서 &lt;b data-index-in-node=&quot;23&quot; data-path-to-node=&quot;9&quot;&gt;프로젝트 전체 관점에서 동시성을 설계한 과정&lt;/b&gt;과 &lt;b data-index-in-node=&quot;49&quot; data-path-to-node=&quot;9&quot;&gt;비관적 락이 가지는 명확한 한계&lt;/b&gt;, 그리고 &lt;b data-index-in-node=&quot;72&quot; data-path-to-node=&quot;9&quot;&gt;아키텍처적 확장 방향&lt;/b&gt;에 대해 다뤄보고자 합니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 재고를 변경하는 모든 도메인 흐름의 통일&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;비관적 락을 적용한 뒤 가장 먼저 진행한 작업은 프로젝트 내에서 Inventory 데이터를 변경하는 모든 코드를 전수 조사하는 것이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;12&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;[주문 생성]&amp;nbsp;&amp;nbsp;---&amp;gt;&amp;nbsp;&amp;nbsp;재고 차감 (Inventory -)&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;12&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;[주문 취소]&amp;nbsp;&amp;nbsp;---&amp;gt;&amp;nbsp;&amp;nbsp;재고 복구 (Inventory +)&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;12&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;[관리자&amp;nbsp;수정]&amp;nbsp;---&amp;gt;&amp;nbsp;&amp;nbsp;재고&amp;nbsp;수량&amp;nbsp;변경&amp;nbsp;(Inventory&amp;nbsp;=)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1) 관리자의 재고 수정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;운영 중 관리자가 특정 상품의 재고 수량을 직접 수정하는 상황을 가정해 보겠습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;만약 사용자의 타임세일 주문(재고 차감)과 관리자의 재고 수량이 동시에 업데이트된다면, 락이 적용되지 않은 관리자의 트랜잭션으로 인해 Lost Update(갱신 손실)가 다시 발생할 수 있습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;따라서 관리자 재고 수정 서비스 역시 동일한 `findWithPessimisticLock` 메서드를 통해 DB 쓰기 락을 획득하도록 일관되게 설계했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2) 주문 취소와 재고 복구&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;주문 취소 시에도 동일합니다. 결제 실패나 사용자 요청으로 주문이 취소되면 차감되었던 재고를 다시 증가(+)시켜야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&quot;차감(감소) 작업만 락을 잡으면 되겠지&quot;라고 생각하기 쉽지만, 재고 증가 작업 역시 동일한 Row를 수정하는 Write 작업입니다. 주문 생성 트랜잭션과 주문 취소 트랜잭션이 충돌할 경우를 대비하여, &lt;b data-index-in-node=&quot;113&quot; data-path-to-node=&quot;19&quot;&gt;재고 증가 로직 역시 동일한 비관적 락 전략 안에서 격리&lt;/b&gt;되도록 통일했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 왜 끝까지 Atomic UPDATE 대신 비관적 락이었는가?&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;시스템 전체의 일관성을 맞추는 과정에서,&lt;br /&gt;&quot;일부 단일 재고 수정 로직은 Atomic UPDATE가 더 효율적이지 않을까?&quot;라는 고민을 계속했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwivs4b5ueqVAxUAAAAAHQAAAAAQyQI&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;-- Atomic UPDATE 예시
UPDATE inventory 
SET quantity = quantity - :quantity 
WHERE product_id = :productId AND quantity &amp;gt;= :quantity;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;조회와 수정을 하나의 SQL로 원자적으로 처리하는 Atomic UPDATE는 락 대기 시간을 최소화하고 DB 성능을 극대화할 수 있는 강력한 방법입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;하지만 본 프로젝트의 주문 도메인은 &lt;b data-index-in-node=&quot;20&quot; data-path-to-node=&quot;25&quot;&gt;단순 숫자 차감 그 이상의 비즈니스 트랜잭션&lt;/b&gt;을 품고 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1784872979997&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sequenceDiagram
    actor Client
    participant OrderService
    participant Inventory
    participant OrderRepository
    participant DB

    Client-&amp;gt;&amp;gt;OrderService: 주문 요청

    Note over OrderService,DB: Transaction Begin

    rect rgb(245,245,245)
        OrderService-&amp;gt;&amp;gt;Inventory: ① 재고 조회 + FOR UPDATE
        Inventory-&amp;gt;&amp;gt;DB: SELECT ... FOR UPDATE
        DB--&amp;gt;&amp;gt;Inventory: Lock 획득

        OrderService-&amp;gt;&amp;gt;OrderService: ② 판매 가능 상태 검증

        OrderService-&amp;gt;&amp;gt;Inventory: ③ 재고 차감
        Inventory-&amp;gt;&amp;gt;DB: UPDATE Inventory

        OrderService-&amp;gt;&amp;gt;OrderRepository: ④ Order / OrderItem 생성
        OrderRepository-&amp;gt;&amp;gt;DB: INSERT
    end

    Note over OrderService,DB: Commit

    OrderService--&amp;gt;&amp;gt;Client: 주문 완료&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;만약 3번 재고 차감만 Atomic UPDATE로 분리한다면, 이후 4번이나 5번 단계에서 예외가 발생했을 때 &lt;b data-index-in-node=&quot;62&quot; data-path-to-node=&quot;27&quot;&gt;재고는 이미 차감되었으나 주문은 생성되지 않는 비즈니스적 불일치&lt;/b&gt;를 수동으로 롤백하거나 복잡한 보상 트랜잭션을 구현해야 합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;결국 &lt;b data-index-in-node=&quot;3&quot; data-path-to-node=&quot;28&quot;&gt;여러 도메인의 상태 변화를 하나의 ACID 트랜잭션으로 보호해야 하는 구조&lt;/b&gt;였기에, 비관적 락으로 전체 흐름의 시작점부터 격리하는 방식이 가장 안전한 선택이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;30&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. 해결책이었던 비관적 락의 명확한 한계와 비용&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;재고 정합성은 확보했지만, 비관적 락은 &lt;b data-index-in-node=&quot;22&quot; data-path-to-node=&quot;31&quot;&gt;정합성을 얻는 대신 성능을 희생하는 전략&lt;/b&gt;입니다. &lt;br /&gt;실제 부하를 가해보며 체감했던 비관적 락의 명확한 한계 4가지는 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 대기 시간(Latency)의 선형적 증가&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;비관적 락은 선행 트랜잭션이 커밋/롤백될 때까지 후행 트랜잭션을 줄 세웁니다(Wait). &lt;br /&gt;동시 요청이 100건, 1000건으로 늘어날수록 뒤쪽 요청들의 대기 시간은 선형적으로 폭증합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 락 점유 시간 동안의 병목&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;락을 획득한 상태에서 외부 API를 호출하거나 불필요한 DB 조회를 수행하면, 뒤에서 대기하는 모든 요청의 응답 시간이 그만큼 지연됩니다. 이를 해결하기 위해 &lt;b data-index-in-node=&quot;89&quot; data-path-to-node=&quot;35&quot;&gt;락 획득 후 트랜잭션 내부에서 실행되는 코드 범위를 최소한으로 다이어트&lt;/b&gt;해야 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;36&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. Connection Pool 고갈 위험 (핵심 병목)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;가장 경계해야 할 부분입니다. 락을 기다리는 스레드는 아무 작업을 안 하는 것처럼 보이지만, &lt;b data-index-in-node=&quot;52&quot; data-path-to-node=&quot;37&quot;&gt;실제로는 DB Connection을 잡은 채 블로킹(Blocking)된 상태&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1784873170021&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;flowchart TD
    A[&quot;① 동시 주문 요청 30개 유입&quot;] --&amp;gt; B[&quot;② 각 요청이 SELECT ... FOR UPDATE 실행&quot;]
    B --&amp;gt; C[&quot;③ 락을 기다리면서 DB Connection 점유&quot;]
    C --&amp;gt; D[&quot;④ HikariCP Connection 30개 전부 사용&quot;]
    D --&amp;gt; E[&quot;유휴 Connection 0개&quot;]

    F[&quot;⑤ 31번째 일반 상품 조회 요청&quot;] --&amp;gt; G[&quot;Connection 획득 요청&quot;]
    E --&amp;gt; G
    G --&amp;gt; H[&quot;Connection 획득 대기&quot;]
    H --&amp;gt; I[&quot;Connection Timeout&quot;]
    I --&amp;gt; J[&quot;일반 상품 조회 실패&quot;]

    J --&amp;gt; K[&quot;무관한 기능까지 장애 확산&quot;]
    K --&amp;gt; L[&quot;Cascading Failure&quot;]

    N[&quot;HikariCP Maximum Pool Size = 30&quot;] -.-&amp;gt; D&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;39&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;락 경쟁이 심해지면 단순히 특정 기능이 느려지는 것을 넘어, &lt;b data-index-in-node=&quot;34&quot; data-path-to-node=&quot;39&quot;&gt;시스템 전체의 DB 커넥션 풀이 고갈되어 다른 무관한 서비스까지 마비되는 쇄도(Cascading Failure) 현상&lt;/b&gt;이 발생할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;40&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. `LockTimeoutException` 예외 처리 설계의 필요성&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;41&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;무한 대기를 막기 위해 `LockTimeoutException`을 설정하지만, 이는 &lt;b data-index-in-node=&quot;45&quot; data-path-to-node=&quot;41&quot;&gt;시간이 초과된 요청은 실패한다는 의미&lt;/b&gt;입니다. 비관적 락이 완벽한 성공을 보장하지는 않으므로, 백엔드에서는 다음과 같은 에러 핸들링 대책을 세워야만 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;42&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;사용자 안내 (&quot;현재 주문 요청이 몰리고 있습니다. 잠시 후 다시 시도해 주세요.&quot;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;지능적 재시도(Backoff Retry) 정책 도입 여부 판단&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;44&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. 또 다른 아키텍처적 대안: 메시지 큐(MQ)를 통한 요청 직렬화&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;45&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;비관적 락의 한계를 체감하며 &quot;애초에 DB 수정을 동시에 두드리지 않게 만들 수는 없을까?&quot;라는 아키텍처적 질문에 도달했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;46&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;동시성 제어가 'DB 수준에서 동시 접근을 막는 방식'이라면, &lt;b data-index-in-node=&quot;35&quot; data-path-to-node=&quot;46&quot;&gt;메시지 큐(Kafka, RabbitMQ)를 이용한 비동기 처리&lt;/b&gt;는 '동시 접근 자체를 없애는 방식'입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1784873252550&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sequenceDiagram
    actor Client as 사용자
    participant API as Order API
    participant Kafka as Kafka Topic
    participant Consumer as Order Consumer
    participant DB as Database
    participant Result as 주문 상태 저장소

    Client-&amp;gt;&amp;gt;API: ① 주문 요청
    API-&amp;gt;&amp;gt;Kafka: ② 주문 메시지 발행
    Kafka--&amp;gt;&amp;gt;API: 메시지 적재 완료
    API--&amp;gt;&amp;gt;Client: ③ 주문 접수 응답&amp;lt;br/&amp;gt;requestId 반환

    Note over Kafka,Consumer: 동일 상품 메시지는 동일 Partition에서 순서 보장

    Kafka-&amp;gt;&amp;gt;Consumer: ④ 주문 메시지 순차 전달

    Consumer-&amp;gt;&amp;gt;DB: ⑤ 재고 차감
    DB--&amp;gt;&amp;gt;Consumer: 차감 결과

    Consumer-&amp;gt;&amp;gt;DB: ⑥ Order / OrderItem 생성
    DB--&amp;gt;&amp;gt;Consumer: 주문 생성 완료

    Consumer-&amp;gt;&amp;gt;Result: ⑦ 처리 결과 저장

    Client-&amp;gt;&amp;gt;API: ⑧ 주문 상태 조회
    API-&amp;gt;&amp;gt;Result: requestId로 결과 조회
    Result--&amp;gt;&amp;gt;API: 처리 상태 반환
    API--&amp;gt;&amp;gt;Client: 주문 완료 / 실패 응답&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;48&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;메시지 큐 방식의 장단점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;49&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;49,0,0&quot;&gt;장점:&lt;/b&gt; 동시 요청이 메시지 큐에 순차적으로 적재되므로 DB 스레드 간 락 경쟁이 발생하지 않습니다. DB 커넥션 풀 고갈 위험이 사라지고, Peak 트래픽을 큐가 완충해 줍니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;49,1,0&quot;&gt;단점:&lt;/b&gt; 응답이 비동기로 처리되므로 사용자 화면에서 폴링(Polling)이나 Webhook/SSE 구조가 필요합니다. 메시지 중복 발행 방지(Idempotency), Consumer 장애 시 재처리 등 아키텍처적 복잡도가 상승합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;50&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;트래픽이 폭증하는 대규모 이커머스/티켓팅 시스템이라면 DB 비관적 락만으로 버티기보다는, &lt;b data-index-in-node=&quot;50&quot; data-path-to-node=&quot;50&quot;&gt;MQ를 도입하여 락 경쟁 발생 자체를 차단하는 아키텍처 전환&lt;/b&gt;이 필수적이라는 점을 배울 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;52&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;마치며: 기술 적용을 넘어 엔지니어링 의사결정으로&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;53&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이번 동시성 시리즈를 진행하며 거친 흐름은 다음과 같았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;54&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;54,0,0&quot;&gt;1편:&lt;/b&gt; 동시성 테스트 코드를 통해 재고 정합성이 깨지는 문제를 재현하고 원인을 분석했습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;54,1,0&quot;&gt;2편:&lt;/b&gt; JPA 비관적 락을 적용해 문제를 해결하고, 락 획득 순서 정렬을 통해 데드락(Deadlock) 위험을 제거했습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;54,2,0&quot;&gt;3편:&lt;/b&gt; 주문 외 모든 도메인의 재고 제어 흐름을 통일하고, 비관적 락의 한계(커넥션 풀 고갈)와 MQ 기반 아키텍처라는 다음 단계까지 시야를 넓혔습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;55&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;돌이켜보면 이번 프로젝트의 가장 큰 수확은 단순히 @Lock(LockModeType.PESSIMISTIC_WRITE) 어노테이션의 사용법을 익힌 것이 아니었습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;56,0&quot;&gt;&quot;동시성 해결은 은총탄(Silver Bullet) 같은 특정 기술을 적용하는 것이 아니라, 비즈니스 요구사항 속에서 '정합성'과 '성능(처리량)' 사이의 균형점을 찾는 엔지니어링 의사결정 과정이다.&quot;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;55&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;57&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;비관적 락은 저희 프로젝트의 현재 규모와 비즈니스 트랜잭션 복잡도 안에서 가장 합리적인 균형점이었습니다. 향후 트래픽이 커진다면 메시지 큐를 활용한 비동기 아키텍처나 Redis 기반의 분산 락 전략으로 전환하겠지만, 어떤 선택을 하든 그 뒤에 존재하는 트레이드오프를 명확히 이해하고 설계할 수 있는 시각을 갖게 된 뜻깊은 경험이었습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category> Retrospective</category>
      <category>Concurrency</category>
      <author>limdaeil</author>
      <guid isPermaLink="true">https://limdaeil.tistory.com/79</guid>
      <comments>https://limdaeil.tistory.com/79#entry79comment</comments>
      <pubDate>Fri, 24 Jul 2026 15:08:03 +0900</pubDate>
    </item>
    <item>
      <title>동시성 문제(Concurrency Issue): 2편. 비관적 락(Pessimistic Lock)으로 재고 정합성 해결하기</title>
      <link>https://limdaeil.tistory.com/78</link>
      <description>&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;들어가며&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1편의 동시성 테스트를 통해 재고 정합성이 실제로 깨지는 현상을 확인했습니다. 재고는 10개뿐이었지만 99개의 주문이 성공했고, 단순히 `@Transactional`을 적용하는 것만으로는 이 문제를 막을 수 없었습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;문제는 명확했습니다. &lt;b data-index-in-node=&quot;12&quot; data-path-to-node=&quot;6&quot;&gt;여러 트랜잭션이 동시에 같은 재고를 수정하지 못하도록 동시성을 제어해야 한다&lt;/b&gt;는 점이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;처음에는 &quot;단순히 락을 걸면 되지 않을까?&quot;라고 가볍게 생각했습니다. 하지만 막상 구현해 보니 락에도 다양한 종류가 존재했고, 방식마다 장단점이 명확했습니다. 이번 글에서는 여러 대안 중&lt;b&gt; 비관적 락(Pessimistic Lock)을 선택한 이유와 실제 적용 과정, 그리고 적용 후 직면했던 데드락 해결 과정까지 정리해 보겠습니다.&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 동시성 제어를 위한 대안 검토&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;동시성 문제를 해결하는 방법은 다양합니다. 대표적으로 다음과 같은 방식들을 떠올려볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;synchronized&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Redis Lock (분산 락)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Optimistic Lock (낙관적 락)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Pessimistic Lock (비관적 락)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Atomic UPDATE&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;가장 먼저 떠올린 것은 `synchronized` 키워드였습니다. &lt;br /&gt;하지만 여러 애플리케이션 서버를 띄워 운영하는 다중 서버 환경에서는 단일 JVM 내부의 락만으로 동시성을 제어할 수 없습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Redis Lock 역시 훌륭한 대안이지만, 현재 프로젝트 규모에서 단지 락 제어를 위해 새로운 인프라를 도입하고 관리 포인트를 늘리는 것은 과하다고 판단했습니다. 결국 RDB 수준에서 JPA를 통해 즉시 적용할 수 있는 락 전략을 먼저 검토하기로 했습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 왜 비관적 락(Pessimistic Lock)을 선택했을까?&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;JPA에서 지원하는 대표적인 락 전략은 두 가지입니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,0,0&quot;&gt;Optimistic Lock (낙관적 락):&lt;/b&gt; &quot;충돌은 거의 발생하지 않을 것이다&quot;라고 가정&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,1,0&quot;&gt;Pessimistic Lock (비관적 락):&lt;/b&gt; &quot;충돌이 반드시 발생할 것이다&quot;라고 가정하고 먼저 막음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cC9QQi/dJMcabrv71l/Y9d38V3bYcUsZKggVQPdy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cC9QQi/dJMcabrv71l/Y9d38V3bYcUsZKggVQPdy1/img.png&quot; data-alt=&quot;예시 사례: 급상승 중인 리센느의 굿즈 인형&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cC9QQi/dJMcabrv71l/Y9d38V3bYcUsZKggVQPdy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcC9QQi%2FdJMcabrv71l%2FY9d38V3bYcUsZKggVQPdy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;585&quot; height=&quot;408&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;836&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예시 사례: 급상승 중인 리센느의 굿즈 인형&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;본 프로젝트는 &lt;b data-index-in-node=&quot;8&quot; data-path-to-node=&quot;18&quot;&gt;인기 상품이나 한정판 타임세일&lt;/b&gt;처럼 많은 사용자가 동시에 동일한 상품을 주문하는 상황을 전제로 합니다. 즉, 충돌은 예외적인 상황이 아니라 &lt;b data-index-in-node=&quot;89&quot; data-path-to-node=&quot;18&quot;&gt;자주 발생하는 일반적인 상황&lt;/b&gt;입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;낙관적 락을 사용할 경우 빈번한 충돌로 인해 실패와 재시도(Retry) 로직이 대량으로 발생하여 오히려 성능이 저하될 위험이 컸습니다. 따라서 데이터 접근 단계에서부터 확실하게 동시 처리를 차단하는 &lt;b data-index-in-node=&quot;111&quot; data-path-to-node=&quot;19&quot;&gt;비관적 락&lt;/b&gt;이 더 적합하다고 판단했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. 락의 대상과 적용 위치 선정&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 고민은 &lt;b&gt;&quot;어디에 락을 걸어야 하는가?&quot;&lt;/b&gt;였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;처음에는 주문(Order) 엔티티에 락을 거는 방식도 생각했습니다. 하지만 실제로 동시성 경합이 발생하는 주체는 주문이 아닌 재고(Inventory)입니다. 데이터베이스 관점에서도 동시 수정이 일어나는 Row는 Inventory이므로, 재고를 조회하는 시점에 락을 획득해야 했습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;기존 Repository는 단순히 조회만 수행하고 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwivs4b5ueqVAxUAAAAAHQAAAAAQkQI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt; List&amp;lt;Inventory&amp;gt; inventories = inventoryRepository.findAllByProductIdInForUpdate(productIds);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이를 다음과 같이 `@Lock` 어노테이션을 추가하여 변경했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWS3lN/dJMcaaTFcK9/oukz13sPL9arUdZ5beiGjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWS3lN/dJMcaaTFcK9/oukz13sPL9arUdZ5beiGjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWS3lN/dJMcaaTFcK9/oukz13sPL9arUdZ5beiGjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWS3lN%2FdJMcaaTFcK9%2Foukz13sPL9arUdZ5beiGjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;491&quot; height=&quot;248&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwivs4b5ueqVAxUAAAAAHQAAAAAQkgI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; letter-spacing: 0px;&quot;&gt;조회 메서드가 호출되는 순간, DB는 해당 Row에 대해 쓰기 락(Write Lock)을 획득합니다.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 락이 유지되는 동안 다른 트랜잭션은 해당 재고 데이터를 수정하거나 동일한 쓰기 락을 획득할 수 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;30&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. 서비스 로직 변경 및 비관적 락의 동작 원리&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;서비스 로직의 변화&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;서비스 레이어의 전체 구조는 유지한 채, 조회 메서드만 변경했습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;32&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;[ 기존 ] `inventoryRepository.findAllByProductIdIn(...)`&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;32&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;[ 변경 ] `inventoryRepository.findAllByProductIdInForUpdate(...)`&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;재고 조회 (Pessimistic Lock 획득)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;재고 검증&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;재고 차감&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;주문 생성&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실제 DB와 Hibernate 수준에서의 동작&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;36&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;비관적 락을 선언하면 Hibernate는 단순 SELECT문 대신 `&lt;b data-index-in-node=&quot;37&quot; data-path-to-node=&quot;36&quot;&gt;FOR UPDATE`&lt;/b&gt; 구문이 포함된 SQL을 실행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwivs4b5ueqVAxUAAAAAHQAAAAAQlQI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;SELECT * 
FROM inventory
WHERE product_id = ? FOR UPDATE;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;38&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 FOR UPDATE 구문에 의해 DB 수준에서 해당 Row에 쓰기 락이 설정됩니다. &lt;br /&gt;이에 따라 동시 요청 시 다음과 같이 순차적인 흐름이 만들어집니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1784872016918&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sequenceDiagram
    participant A as Thread A
    participant DB as Inventory(DB)
    participant B as Thread B

    A-&amp;gt;&amp;gt;DB: SELECT ... FOR UPDATE
    Note right of A: Lock 획득

    A-&amp;gt;&amp;gt;DB: 재고 조회
    DB--&amp;gt;&amp;gt;A: 재고 = 10

    B-&amp;gt;&amp;gt;DB: SELECT ... FOR UPDATE
    Note right of B: Lock 대기 (Thread A Commit까지)

    A-&amp;gt;&amp;gt;DB: 재고 1개 차감
    DB--&amp;gt;&amp;gt;A: 재고 = 9

    A-&amp;gt;&amp;gt;DB: COMMIT
    Note over DB: Lock 해제

    DB--&amp;gt;&amp;gt;B: Lock 획득

    B-&amp;gt;&amp;gt;DB: 재고 조회
    DB--&amp;gt;&amp;gt;B: 재고 = 9 (감소된 값)

    B-&amp;gt;&amp;gt;DB: 재고 1개 차감
    DB--&amp;gt;&amp;gt;B: 재고 = 8

    B-&amp;gt;&amp;gt;DB: COMMIT

    Note over A,B: Lost Update 발생하지 않음
    Note over DB: 최종 재고 = 8&lt;/code&gt;&lt;/pre&gt;
&lt;p data-path-to-node=&quot;40&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Thread B는 Thread A의 트랜잭션이 완료될 때까지 대기한 후, &lt;b data-index-in-node=&quot;41&quot; data-path-to-node=&quot;40&quot;&gt;이미 차감된 최신 재고 데이터&lt;/b&gt;를 조회하게 됩니다. &lt;br /&gt;결과적으로 두 트랜잭션이 동시에 동일 데이터를 수정하며 발생하는 &lt;b data-index-in-node=&quot;105&quot; data-path-to-node=&quot;40&quot;&gt;Lost Update(갱신 손실) 문제가 근본적으로 차단&lt;/b&gt;됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;42&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5. 테스트 재실행 및 결과 검증&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;43&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;비관적 락을 적용한 뒤, 1편에서 작성했던 동일한 동시성 테스트를 다시 실행했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;44&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;44,0&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44,0&quot;&gt;테스트 조건&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;44,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;초기 재고: 10개&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;동시 요청: 200명의 사용자 (스레드 200개)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;주문 수량: 1개씩&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;45&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;테스트 결과&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6qxW7/dJMcaiRIEwJ/KRTwFDuKMeeI7a4ktgWuVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6qxW7/dJMcaiRIEwJ/KRTwFDuKMeeI7a4ktgWuVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6qxW7/dJMcaiRIEwJ/KRTwFDuKMeeI7a4ktgWuVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6qxW7%2FdJMcaiRIEwJ%2FKRTwFDuKMeeI7a4ktgWuVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;747&quot; height=&quot;351&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;46&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;46,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;46,1,0,0&quot;&gt;초기 재고&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;46,1,1,0&quot;&gt;10개&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;46,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;46,2,0,0&quot;&gt;주문 성공&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;46,2,1,0&quot;&gt;10건&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;46,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;46,3,0,0&quot;&gt;주문 실패 (재고 부족)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;46,3,1,0&quot;&gt;190건&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;46,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;46,4,0,0&quot;&gt;최종 남은 재고&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;46,4,1,0&quot;&gt;0개&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-path-to-node=&quot;47&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;성공 건수(10) + 남은 재고(0) = 초기 재고(10)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;48&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;락 어노테이션 하나를 적절한 위치에 추가함으로써 재고 정합성이 완벽하게 보장되는 것을 검증할 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;50&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;6. 비관적 락 적용 시 고려해야 할 점 (비용과 타임아웃)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;51&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;문제가 완전히 해결된 것처럼 보였지만, 비관적 락에는 &lt;b data-index-in-node=&quot;30&quot; data-path-to-node=&quot;51&quot;&gt;'대기(Waiting)'에 따른 비용&lt;/b&gt;이 발생합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;락을 보유한 트랜잭션의 처리 시간이 길어질수록 뒤따르는 모든 요청은 그만큼 대기해야 합니다. 대기하는 요청이 많아지면 DB 커넥션 풀을 계속 점유하게 되어, 시스템 전체의 응답 속도가 느려지거나 커넥션 고갈 문제로 이어질 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;53&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Lock Timeout 설정의 필요성&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;54&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;무한정 대기하는 상황을 방지하기 위해 JPA와 DB는 &lt;b data-index-in-node=&quot;30&quot; data-path-to-node=&quot;54&quot;&gt;락 대기 시간(Timeout)&lt;/b&gt; 설정을 지원합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwivs4b5ueqVAxUAAAAAHQAAAAAQmQI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Lock(LockModeType.PESSIMISTIC_WRITE)
@QueryHints({@QueryHint(name = &quot;javax.persistence.lock.timeout&quot;, value = &quot;3000&quot;)}) // 3초 대기
Optional&amp;lt;Inventory&amp;gt; findWithPessimisticLock(Long productId);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;56&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;지정한 대기 시간을 초과하면 `LockTimeoutException`이 발생하며 트랜잭션이 종료됩니다. &lt;br /&gt;따라서 비관적 락을 설계할 때는 다음과 같은 2가지 원칙을 지켜야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;57&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;57,0,0&quot;&gt;최소한의 점유 시간&lt;br /&gt;&lt;/b&gt;- 락을 잡은 상태에서 외부 API 호출, 복잡한 연산, 불필요한 조회를 수행하지 않고 빠른 시간 내에 트랜잭션을 끝내야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;57,1,0&quot;&gt;예외 처리&lt;br /&gt;&lt;/b&gt;- 락 획득 실패(`LockTimeoutException`) 시 사용자에게 적절한 안내나 재시도 방안을 제공해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-path-to-node=&quot;59&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;7. 예상치 못한 두 번째 문제: 데드락(Deadlock)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;60&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;단일 상품 주문 환경에서는 비관적 락으로 모든 문제가 해결되었습니다. &lt;br /&gt;하지만&lt;b&gt; &quot;사용자가 한 번에 여러 상품을 장바구니에 담아 주문하는 기능&quot;을 추가하면서 새로운 문제가 발생했습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;61&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;데드락 발생 시나리오&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;62&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;두 사용자가 서로 다른 순서로 여러 상품을 동시에 주문할 때 문제가 생깁니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;63&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;63,0,0&quot;&gt;사용자 A:&lt;/b&gt; 상품 A &amp;rarr; 상품 B 순서로 주문&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;63,1,0&quot;&gt;사용자 B:&lt;/b&gt; 상품 B &lt;span style=&quot;font-family: 'Noto Sans Light'; color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;/span&gt; 상품 A 순서로 주문&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1784872314021&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sequenceDiagram
    participant A as Thread A (사용자 A)
    participant DB as Database
    participant B as Thread B (사용자 B)

    Note over A,B: 사용자 A : 상품 A &amp;rarr; 상품 B 순서 주문
    Note over A,B: 사용자 B : 상품 B &amp;rarr; 상품 A 순서 주문

    A-&amp;gt;&amp;gt;DB: 상품 A Lock 획득
    DB--&amp;gt;&amp;gt;A: Lock Granted

    B-&amp;gt;&amp;gt;DB: 상품 B Lock 획득
    DB--&amp;gt;&amp;gt;B: Lock Granted

    A-&amp;gt;&amp;gt;DB: 상품 B Lock 요청
    Note right of A: 상품 B는 Thread B가 점유 중\n&amp;rarr; 대기(Waiting)

    B-&amp;gt;&amp;gt;DB: 상품 A Lock 요청
    Note left of B: 상품 A는 Thread A가 점유 중\n&amp;rarr; 대기(Waiting)

    Note over A,B: 서로 상대방의 Lock을 기다림
    Note over DB: Deadlock 발생

    DB--&amp;gt;&amp;gt;B: Deadlock 감지
    DB--&amp;gt;&amp;gt;B: Victim Transaction Rollback

    DB--&amp;gt;&amp;gt;A: Lock 획득 가능
    A-&amp;gt;&amp;gt;DB: Transaction Commit&lt;/code&gt;&lt;/pre&gt;
&lt;p data-path-to-node=&quot;65&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Thread A는 Thread B가 가진 상품 B의 락을 기다리고, Thread B는 Thread A가 가진 상품 A의 락을 기다리는 교착 상태(Deadlock)에 빠지게 됩니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;DB(e.g., MySQL)는 데드락을 감지하면 한쪽 트랜잭션을 강제로 롤백(Victim Transaction)시켜 시스템 멈춤을 방지하지만, 이로 인해 사용자는 알 수 없는 주문 실패 오류를 경험하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;67&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;해결책: 락 획득 순서의 정렬 (Ordering)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;68&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;데드락을 방지하는 가장 확실한 방법은 &lt;b data-index-in-node=&quot;21&quot; data-path-to-node=&quot;68&quot;&gt;모든 트랜잭션이 항상 동일한 순서로 락을 획득하게 만드는 것&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;69&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;주문 요청으로 들어온 상품 ID 목록을 조회 전에 미리 &lt;b data-index-in-node=&quot;31&quot; data-path-to-node=&quot;69&quot;&gt;오름차순 정렬&lt;/b&gt;하도록 코드를 수정했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3C0bI/dJMcaasD0ty/K0CfTisusv2JsEKvgIQoo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3C0bI/dJMcaasD0ty/K0CfTisusv2JsEKvgIQoo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3C0bI/dJMcaasD0ty/K0CfTisusv2JsEKvgIQoo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3C0bI%2FdJMcaasD0ty%2FK0CfTisusv2JsEKvgIQoo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;686&quot; height=&quot;134&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwivs4b5ueqVAxUAAAAAHQAAAAAQmwI&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 요청된 상품 ID 리스트를 정렬하여 락 획득 순서를 고정
List&amp;lt;Long&amp;gt; sortedProductIds = productIds.stream()
    .sorted()
    .toList();

for (Long productId : sortedProductIds) {
    inventoryRepository.findWithPessimisticLock(productId);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;71&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;요청 순서가 [15, 3, 7]로 들어오더라도 실제 락은 반드시 [3 -&amp;gt; 7 -&amp;gt; 15] 순서로 획득합니다. &lt;br /&gt;이 작은 정렬 로직 하나로 교착 상태 발생 조건 자체를 원천 차단할 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;73&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;8. 비관적 락이 항상 정답일까? (트레이드오프 분석)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;74&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;비관적 락을 통해 정합성 문제와 데드락까지 해결했지만, 기술 선택에 절대적인 정답은 없습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;74&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다른 대안들과 비교했을 때 비관적 락의 입지를 재확인해 보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;75&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1) Optimistic Lock과의 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;76&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;76,0,0&quot;&gt;낙관적 락:&lt;/b&gt; DB 락을 잡지 않으므로 동시 처리량이 높음. 하지만 충돌 발생 시 애플리케이션에서 재시도(Retry)를 처리해야 함.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;76,1,0&quot;&gt;선택 기준:&lt;/b&gt; 충돌이 드문 시스템이라면 낙관적 락이 효율적이지만, &lt;b data-index-in-node=&quot;36&quot; data-path-to-node=&quot;76,1,0&quot;&gt;동시 주문이 몰리는 타임세일 환경에서는 충돌 폭발로 인한 재시도 비용&lt;/b&gt;이 비관적 락의 대기 비용보다 훨씬 커집니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;77&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2) Atomic UPDATE와의 비교&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;78&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;재고 차감을 SQL 수준에서 직접 처리하는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwivs4b5ueqVAxUAAAAAHQAAAAAQnAI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;UPDATE inventory 
SET quantity = quantity - :quantity 
WHERE product_id = :productId AND quantity &amp;gt;= :quantity;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;80&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 방식은 별도의 락을 명시하지 않아도 DB가 원자적으로 단일 Row를 수정하므로 성능이 매우 뛰어납니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;81&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;하지만 본 프로젝트의 주문 흐름은 단순히 숫자만 바꾸는 작업이 아니었습니다. &lt;b data-index-in-node=&quot;43&quot; data-path-to-node=&quot;81&quot;&gt;재고 조회 -&amp;gt; 판매 가능 상태/기간 검증 -&amp;gt; 재고 차감 -&amp;gt; 주문 생성 -&amp;gt; 쿠폰/포인트 차감&lt;/b&gt; 등 복잡한 비즈니스 로직들이 하나의 트랜잭션 안에서 묶여있었습니다. 단순 Atomic UPDATE 방식만으로는 전체 비즈니스 흐름의 일관성을 보호하기 어려웠기에 &lt;b data-index-in-node=&quot;188&quot; data-path-to-node=&quot;81&quot;&gt;트랜잭션 범위 전체를 안전하게 제어하는 비관적 락&lt;/b&gt;을 최종 선택했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;83&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2편을 마치면서&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;84&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1편에서는 동시성 문제의 원인을 재현했고, 2편에서는 &lt;b data-index-in-node=&quot;30&quot; data-path-to-node=&quot;84&quot;&gt;비관적 락&lt;/b&gt;을 도입하여 정합성을 보장하고 데드락 문제까지 해결해 나간 과정을 다루었습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;단순히 @Lock 어노테이션 하나를 붙이는 것에 그치지 않고, &lt;b data-index-in-node=&quot;35&quot; data-path-to-node=&quot;85&quot;&gt;락의 점유 시간, 타임아웃, 락 획득 순서, 그리고 아키텍처적 트레이드오프&lt;/b&gt;까지 깊이 있게 고민해 볼 수 있었던 계기였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;86&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;하지만 동시성은 주문 기능 하나만으로 끝나지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;87&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;i data-index-in-node=&quot;0&quot; data-path-to-node=&quot;87,0,0&quot;&gt;관리자가 동시 접근하여 재고를 수정할 때는 어떻게 처리할 것인가?&lt;/i&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;i data-index-in-node=&quot;0&quot; data-path-to-node=&quot;87,1,0&quot;&gt;주문 취소 및 결제 실패 시 재고 복구 로직에서의 동시성은 안전한가?&lt;/i&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;i data-index-in-node=&quot;0&quot; data-path-to-node=&quot;87,2,0&quot;&gt;모든 곳에 비관적 락을 쓰는 것이 과연 최선인가?&lt;/i&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;88&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 &lt;b data-index-in-node=&quot;3&quot; data-path-to-node=&quot;88&quot;&gt;3편&lt;/b&gt;에서는 프로젝트 전체 관점에서 동시성 제어 전략을 회고하고, 비관적 락과 Atomic UPDATE를 혼용하는 설계 개선 방안 및 확장 가능성에 대해 정리해 보겠습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category> Retrospective</category>
      <category>Concurrency</category>
      <author>limdaeil</author>
      <guid isPermaLink="true">https://limdaeil.tistory.com/78</guid>
      <comments>https://limdaeil.tistory.com/78#entry78comment</comments>
      <pubDate>Fri, 24 Jul 2026 14:54:39 +0900</pubDate>
    </item>
    <item>
      <title>동시성 문제(Concurrency Issue): 1편. 문제 도출! 주문 재고 동시성 문제는 어떻게 발견했는가?</title>
      <link>https://limdaeil.tistory.com/77</link>
      <description>&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 문제의 발단: &quot;재고가 10개인데 주문은 99개가 성공했다&quot;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;단순히 재고를 확인하고 차감하는 일반적인 주문 API를 구현했습니다. Postman이나 Swagger로 테스트했을 때는 아무 문제 없이 동작했고, 기능 테스트도 모두 통과했죠. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;하지만 &lt;b&gt;&quot;200명의 사용자가 동시에 주문하면 어떻게 될까?&quot;&lt;/b&gt;라는 의문이 생겨 동시성 테스트를 진행했고, 충격적인 결과를 확인했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BOVLl/dJMcafOis74/ngfEakqihvIvTA2Bs2rnx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BOVLl/dJMcafOis74/ngfEakqihvIvTA2Bs2rnx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BOVLl/dJMcafOis74/ngfEakqihvIvTA2Bs2rnx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBOVLl%2FdJMcafOis74%2FngfEakqihvIvTA2Bs2rnx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;672&quot; height=&quot;343&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;초기 재고:&lt;/b&gt; 10개&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0&quot;&gt;동시 요청:&lt;/b&gt; 200명 (1인당 1개 주문)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,2,0&quot;&gt;주문 성공:&lt;/b&gt; &lt;b data-index-in-node=&quot;7&quot; data-path-to-node=&quot;8,2,0&quot;&gt;100개&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,3,0&quot;&gt;재고 부족 실패:&lt;/b&gt; 100개&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,4,0&quot;&gt;최종 재고:&lt;/b&gt; 0개&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;최종 재고가 0이라 얼핏 정상처럼 보이지만, 실제로는 &lt;b data-index-in-node=&quot;30&quot; data-path-to-node=&quot;9&quot;&gt;존재하지도 않는 90개의 재고가 팔려나간 셈&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 기존 주문 생성 로직의 허점&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;당시 작성했던 로직은 다음과 같이 지극히 평범했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;12&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;[주문&amp;nbsp;요청]&amp;nbsp;──&amp;gt;&amp;nbsp;[상품/재고&amp;nbsp;조회]&amp;nbsp;──&amp;gt;&amp;nbsp;[주문&amp;nbsp;가능&amp;nbsp;검증]&amp;nbsp;──&amp;gt;&amp;nbsp;[재고&amp;nbsp;차감]&amp;nbsp;──&amp;gt;&amp;nbsp;[주문&amp;nbsp;저장] &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwivs4b5ueqVAxUAAAAAHQAAAAAQgAE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// 기존 주문 처리 코드 예시
List&amp;lt;Inventory&amp;gt; inventories = inventoryRepository.findAllByProductIdIn(productIds);

inventory.validateEnoughStock(quantity); // 재고 검증
inventory.decrease(quantity);           // 재고 차감

purchaseOrderRepository.save(order);      // 주문 저장&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;단일 요청에서는 아주 잘 작동합니다. 하지만 이 코드에는 한 가지 치명적인 가정이 숨어 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-path-to-node=&quot;15&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;항상 한 번에 한 명의 사용자만 주문을 요청한다.&quot;&lt;/span&gt;&lt;/blockquote&gt;
&lt;h2 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. 왜 문제가 발생했을까? (Lost Update 현상)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 문제는 데이터베이스의 &lt;b data-index-in-node=&quot;14&quot; data-path-to-node=&quot;19&quot;&gt;Lost Update (수정 손실)&lt;/b&gt; 현상 때문에 발생합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;두 개 이상의 트랜잭션이 거의 동시에 동일한 데이터를 읽고(Read) 수정(Write)할 때, &lt;b data-index-in-node=&quot;53&quot; data-path-to-node=&quot;20&quot;&gt;먼저 수행된 변경 사항이 나중의 변경 사항에 의해 덮어씌워지는 현상&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; &lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;  동시 요청 시 일어나는 일&lt;/span&gt; &lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1784870439614&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sequenceDiagram
    participant A as Thread A
    participant DB as Inventory(DB)
    participant B as Thread B

    Note over A,B: 동시 요청 시작

    A-&amp;gt;&amp;gt;DB: 재고 조회
    DB--&amp;gt;&amp;gt;A: 재고 = 10

    B-&amp;gt;&amp;gt;DB: 재고 조회
    DB--&amp;gt;&amp;gt;B: 재고 = 10

    A-&amp;gt;&amp;gt;DB: 재고 1개 차감 후 저장 (10 &amp;rarr; 9)
    DB--&amp;gt;&amp;gt;A: 저장 완료 (재고 = 9)

    B-&amp;gt;&amp;gt;DB: 재고 1개 차감 후 저장 (10 &amp;rarr; 9)
    Note right of B: 이전에 조회한 값(10)을 기준으로 계산
    DB--&amp;gt;&amp;gt;B: 저장 완료 (재고 = 9)

    Note over A,B: 주문은 2건 모두 성공
    Note over DB: 최종 재고 = 9 (Lost Update 발생)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Clearly, 주문은 2건이 처리되었지만 DB의 재고는 1개만 줄어들었습니다.&lt;br /&gt;Thread A가 차감한 기록이&lt;b&gt; Thread B에 의해 사라진 것(Lost)&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-path-to-node=&quot;25&quot; data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;❓ &quot;`@Transactional`을 걸었는데 왜 안 막힐까요?&quot;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring의 `@Transactional`은 단일 트랜잭션 내 작업의 원자성(All or Nothing)을 보장할 뿐,&amp;nbsp;&lt;br /&gt;&lt;b&gt;서로 다른 트랜잭션이 같은 데이터에 동시에 접근하는 것까지 막아주지는 않기 때문입니다.&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;h2 data-path-to-node=&quot;27&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. 문제를 재현하기 위한 동시성 테스트 설계&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 현상을 이론이 아닌 &lt;b data-index-in-node=&quot;13&quot; data-path-to-node=&quot;28&quot;&gt;테스트 코드로 완벽히 재현&lt;/b&gt;해 보기로 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 핵심 불변식 (Invariant) 정의&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;가장 먼저 동시성 정합성을 판가름할 기준을 세웠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-path-to-node=&quot;31&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-math=&quot;\text{초기 재고} = \text{성공한 주문 수량} + \text{최종 재고}&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;초기 재고 = 성공한 주문 수량 + 최종 재고&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;32&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;32,0,0&quot;&gt;정상 케이스:&lt;/b&gt; 10 = 10 + 0 (정합성 만족 TRUE)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;32,1,0&quot;&gt;오류 케이스:&lt;/b&gt; 10 = 99 + 0 (정합성 붕괴 FALSE)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 테스트 성공 조건&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;34&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;성공한 주문 수량 = &lt;b data-index-in-node=&quot;12&quot; data-path-to-node=&quot;34,0,0&quot;&gt;최대 10개&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;재고 부족으로 실패한 수량 = &lt;b data-index-in-node=&quot;17&quot; data-path-to-node=&quot;34,1,0&quot;&gt;190개&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;최종 잔여 재고 = &lt;b data-index-in-node=&quot;11&quot; data-path-to-node=&quot;34,2,0&quot;&gt;0개&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;34,3,0&quot;&gt;재고 불변식 만족 여부 = True&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;6,0&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0&quot;&gt;3. 동시성 환경 조성을 위한 고민&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;6,1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;단순히 반복문으로 200개의 스레드를 생성한다고 해서 실제 동시 요청이 만들어지지는 않습니다. &lt;b data-index-in-node=&quot;53&quot; data-path-to-node=&quot;6,1&quot;&gt;스레드를 많이 생성한다고 해서 반드시 동시에 실행되는 것은 아니기 때문입니다.&lt;/b&gt; OS 스케줄링 과정에서 스레드가 하나씩 순차적으로 처리될 가능성이 크기 때문이었습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;따라서 모든 스레드가 준비를 마칠 때까지 기다렸다가, 한 번에 '땅!' 하고 출발시킬 수 있는 동시성 제어 도구(예: `CountDownLatch`)가 필요했습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5. 동시성 테스트 구현: ExecutorService &amp;amp; CountDownLatch&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;문제의 가설을 세웠으니, 이제 코드로 직접 200명의 동시 요청을 만들어낼 차례입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;❌ 실패했던 첫 번째 시도: 단순 스레드 생성&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwivs4b5ueqVAxUAAAAAHQAAAAAQogE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 단순히 스레드만 200개 만드는 잘못된 접근
for (int i = 0; i &amp;lt; 200; i++) {
    executorService.submit(() -&amp;gt; {
        createOrder(...);
    });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 방식은 매 실행마다 결과가 천차만별이었습니다. 스레드만 200개 만든다고 끝이 아니었기 때문입니다. &lt;b data-index-in-node=&quot;58&quot; data-path-to-node=&quot;9&quot;&gt;운영체제의 CPU 스케줄링에 의해 스레드들이 순차적으로 실행되면 동시성이 옅어져 Lost Update가 제대로 발생하지 않습니다. 따라서 스레드를 많이 생성한다고, 모두 동시에 실행된다는 것은 절대 아니라는 점을 파악할 수 있었습니다.&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;58&quot; data-path-to-node=&quot;9&quot;&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;⭕ 해결책: 동시 출발선(CountDownLatch) 구축&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;모든 스레드가 생성된 후 출발선 앞에서 대기하다가, 신호가 떨어지는 순간 동시에 달리도록 구조를 설계했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;12&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;[200개&amp;nbsp;스레드&amp;nbsp;생성]&amp;nbsp;──&amp;gt;&amp;nbsp;[출발선&amp;nbsp;대기]&amp;nbsp;──&amp;gt;&amp;nbsp;[startLatch.countDown()]&amp;nbsp;──&amp;gt;&amp;nbsp;[200개&amp;nbsp;동시에&amp;nbsp;출발!] &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwivs4b5ueqVAxUAAAAAHQAAAAAQpAE&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;int threadCount = 200;
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);

CountDownLatch startLatch = new CountDownLatch(1);          // 출발 신호용 Latch
CountDownLatch endLatch = new CountDownLatch(threadCount); // 작업 종료 대기용 Latch

for (int i = 0; i &amp;lt; threadCount; i++) {
    executorService.submit(() -&amp;gt; {
        try {
            startLatch.await(); // 메인 스레드가 신호를 줄 때까지 대기
            createOrder(...);
        } catch (Exception e) {
            // 예외 수집
        } finally {
            endLatch.countDown();
        }
    });
}

startLatch.countDown(); //   200개 스레드 동시 출발!
endLatch.await();      // 모든 스레드의 작업 완료 대기&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;blockquote data-path-to-node=&quot;16&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;  `CyclicBarrier` 대신 `CountDownLatch`를 선택한 이유&lt;/b&gt;&lt;br /&gt;둘 다 동시 실행을 조율하는 도구이지만, 이번 테스트는 &lt;b&gt;&quot;단 한 번, 요청을 동시에 쏘아 보내는 작업&quot;&lt;/b&gt;이었기 때문에 1회성 제어에 적합한 `CountDownLatch`가 훨씬 가볍고 직관적이었습니다.&lt;/blockquote&gt;
&lt;h2 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;6. 동시성 테스트보다 더 오래 걸렸던 트러블슈팅&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;동시성 코드를 짜자마자 Lost Update를 볼 수 있을 줄 알았지만, 진짜 난관은 &lt;b data-index-in-node=&quot;48&quot; data-path-to-node=&quot;19&quot;&gt;테스트 환경 제어&lt;/b&gt;였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1) Fixture가 대량 동시 요청을 견디지 못함&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;21&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,0,0&quot;&gt;문제:&lt;/b&gt; 200명의 회원을 생성할 때 `Duplicate entry 'member@test.com'` 예외 발생&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,1,0&quot;&gt;원인:&lt;/b&gt; 기존 `MemberFixture`가 고정된 이메일만 리턴하도록 설계되어 있었음&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,2,0&quot;&gt;해결:&lt;/b&gt; Fixture가 매번 고유한 값(`member1@test.com`, `member2@test.com`)을 생성하도록 개편&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2) 테스트 재실행 시 데이터 충돌 (Rollback의 한계)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;23&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23,0,0&quot;&gt;문제:&lt;/b&gt; 첫 번째 실행은 성공하지만, 두 번째 실행부터 DB 충돌로 실패&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23,1,0&quot;&gt;원인:&lt;/b&gt; 동시성 테스트는 &lt;b data-index-in-node=&quot;13&quot; data-path-to-node=&quot;23,1,0&quot;&gt;각 스레드가 별도의 DB 트랜잭션/커넥션을 사용&lt;/b&gt;하기 때문에, &lt;br /&gt;테스트 클래스의 `@Transactional` 자동 롤백이 적용되지 않고 DB에 데이터가 남아있음&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23,2,0&quot;&gt;해결:&lt;/b&gt; `AtomicLong` 기반의 시퀀스를 도입하여 실행할 때마다 항상 유일한 고유값으로 생성&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwivs4b5ueqVAxUAAAAAHQAAAAAQpQE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;private static final AtomicLong sequence = new AtomicLong();

public static Member createMember() {
    long id = sequence.incrementAndGet();
    return new Member(&quot;member&quot; + id + &quot;@test.com&quot;, &quot;010-0000-&quot; + String.format(&quot;%04d&quot;, id));
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3) 동시성 테스트의 비결정성 (Flaky Test)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;테스트를 돌릴 때마다 성공건수가 87개, 95개, 102개처럼 매번 다르게 나왔습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;처음엔 실패인가 싶었지만, &lt;b&gt;동시성 테스트는 본질적으로 비결정적(Non-deterministic)&lt;/b&gt;입니다. 중요한 것은 &lt;b&gt;&quot;정확히 몇 개가 성공했는가&quot;라는 숫자가 아니라, &quot;재고 불변식(`10 = 성공수 + 최종재고`)이 깨졌는가&quot;라는 정합성 붕괴 자체를 증명하는 것이었습니다.&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;29&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;7. 최종 테스트 결과 및 정리&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;모든 환경을 정비한 뒤 동시성 테스트를 최종 실행했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;  테스트 실행 결과&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BOVLl/dJMcafOis74/ngfEakqihvIvTA2Bs2rnx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BOVLl/dJMcafOis74/ngfEakqihvIvTA2Bs2rnx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BOVLl/dJMcafOis74/ngfEakqihvIvTA2Bs2rnx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBOVLl%2FdJMcafOis74%2FngfEakqihvIvTA2Bs2rnx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;672&quot; height=&quot;343&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;❌ 재고 불변식 검증&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div data-path-to-node=&quot;34&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-math=&quot;\text{초기 재고 (10)} \neq \text{성공한 주문 (99)} + \text{최종 재고 (0)}&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`초기 재고 (10) != 성공한 주문 (99) + 최종 재고 (0)`&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;재고 10개짜리 상품에 99개의 성공 주문이 발생하는 정합성 붕괴를 완벽하게 재현했습니다. &lt;br /&gt;여기에서 반드시 파악해야만 하는 것은 바로 이 두 가지였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;37&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,0,0&quot;&gt;`@Transactional`은 만능이 아니다:&lt;/b&gt; 단일 트랜잭션의 원자성만 보장할 뿐, 동시 접근을 제어해 주지는 않습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;37,1,0&quot;&gt;재현 가능한 버그는 해결할 수 있다:&lt;/b&gt; &quot;재수가 없으면 터지는 버그&quot;로 치부되던 동시성 문제를 테스트 코드로 완벽히 재현했습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category> Retrospective</category>
      <category>Concurrency</category>
      <author>limdaeil</author>
      <guid isPermaLink="true">https://limdaeil.tistory.com/77</guid>
      <comments>https://limdaeil.tistory.com/77#entry77comment</comments>
      <pubDate>Fri, 24 Jul 2026 14:38:24 +0900</pubDate>
    </item>
    <item>
      <title>상품 상세 조회 API 성능 개선기: DB 부하 줄이고 데이터 정합성 지키기 (Redis, k6, Cache)</title>
      <link>https://limdaeil.tistory.com/76</link>
      <description>&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;안녕하세요! 이번 글에서는 서비스 내에서 가장 많은 조회 트래픽을 받는 '상품 상세 조회 API'의 성능을 개선하고, &lt;b data-index-in-node=&quot;66&quot; data-path-to-node=&quot;4&quot;&gt;Redis 캐시 도입 과정에서 겪었던 데이터 정합성 이슈와 해결 과정&lt;/b&gt;을 기록해보려 합니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;단순히 &quot;Redis를 붙였더니 몇 배 빨라졌다&quot;에서 그치지 않고, &lt;b data-index-in-node=&quot;37&quot; data-path-to-node=&quot;5&quot;&gt;왜 이 전략을 선택했는지&lt;/b&gt;, &lt;b data-index-in-node=&quot;52&quot; data-path-to-node=&quot;5&quot;&gt;트랜잭션과의 연계에서 발생할 수 있는 엣지 케이스를 어떻게 방지했는지&lt;/b&gt;, 그리고 &lt;b data-index-in-node=&quot;96&quot; data-path-to-node=&quot;5&quot;&gt;k6를 통해 지표로 어떻게 검증했는지&lt;/b&gt; 상세히 나누어 보겠습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 개요 및 배경 (Problem Statement)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;상품 상세 조회 API의 특성&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnn3nG/dJMcaixqda8/NmdYymuBD5SxXftfUu0k8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnn3nG/dJMcaixqda8/NmdYymuBD5SxXftfUu0k8K/img.png&quot; data-alt=&quot;예시 사례: 급상승 중인 리센느의 인형 굿즈&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnn3nG/dJMcaixqda8/NmdYymuBD5SxXftfUu0k8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbnn3nG%2FdJMcaixqda8%2FNmdYymuBD5SxXftfUu0k8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;588&quot; height=&quot;410&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;836&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예시 사례: 급상승 중인 리센느의 인형 굿즈&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;서비스 내의 다양한 API 중 &lt;b data-index-in-node=&quot;17&quot; data-path-to-node=&quot;9&quot;&gt;상품 상세 조회 API&lt;/b&gt;는 독특한 특징을 가집니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;10&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,0&quot;&gt;압도적인 읽기 요청(Read Demand)&lt;/b&gt;&lt;br /&gt;- 사용자가 메인 페이지, 검색 페이지, 장바구니 등을 거치며 가장 자주 호출하는 대표적인 조회 중심 API입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,0&quot;&gt;낮은 수정 빈도(Low Write Demand)&lt;/b&gt;&lt;br /&gt;- 반면 상품명, 가격, 기본 설명 등의 데이터는 조회 빈도에 비해 변경되는 일이 매우 드뭅니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;문제 상황: 모든 요청이 DB로 몰린다!&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;기존 구조에서는 캐시 레이어가 없어 &lt;b data-index-in-node=&quot;20&quot; data-path-to-node=&quot;12&quot;&gt;모든 상세 조회 요청이 데이터베이스(RDB)까지 직접 전달&lt;/b&gt;되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwj7qpKIzeiVAxUAAAAAHQAAAAAQVg&quot; data-hveid=&quot;0&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1992&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcp0Ri/dJMcagsNDeE/YiBHO6U9K7VNE5sdm4ECik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcp0Ri/dJMcagsNDeE/YiBHO6U9K7VNE5sdm4ECik/img.png&quot; data-alt=&quot;개선 전 아키텍처&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcp0Ri/dJMcagsNDeE/YiBHO6U9K7VNE5sdm4ECik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbcp0Ri%2FdJMcagsNDeE%2FYiBHO6U9K7VNE5sdm4ECik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;714&quot; height=&quot;268&quot; data-origin-width=&quot;1992&quot; data-origin-height=&quot;748&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;개선 전 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;당장 현재 트래픽에서는 DB 조회만으로도 응답 속도가 크게 나쁘지 않을 수 있습니다. 하지만 문제는 &lt;b data-index-in-node=&quot;56&quot; data-path-to-node=&quot;14&quot;&gt;트래픽이 폭증하는 이벤트 상황&lt;/b&gt;이나 &lt;b data-index-in-node=&quot;75&quot; data-path-to-node=&quot;14&quot;&gt;동시 접속자가 늘어나는 시점&lt;/b&gt;입니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;DB가 조회 트래픽을 감당하느라 Connection을 점유하고 CPU/메모리 부하가 높아지면, 동일한 DB를 공유하는 상품 수정, 주문, 결제 등 핵심 트랜잭션 기능까지 응답 지연이 전파(Cascading Failure)되는 치명적인 위험이 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;해결해야 할 과제&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;17&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,0,0&quot;&gt;반복되는 조회 트래픽을 DB 이전 레이어에서 차단하여 DB 읽기 부하 감소&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,1,0&quot;&gt;상품 수정 시 캐시와 DB 간 데이터 불일치(Stale Data) 문제 완벽 해결&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-path-to-node=&quot;19&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 기술적 의사결정과 캐시 전략 (Technical Decision)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;조회 성능과 데이터 정합성이라는 두 마리 토끼를 잡기 위해 몇 가지 핵심 기술적 의사결정을 내렸습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2.1 캐시 패턴: Cache-Aside (Lazy Loading) 전략&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;캐시 패턴에는 Read-Through, Write-Through, Write-Behind 등 다양한 방식이 있지만, &lt;b data-index-in-node=&quot;65&quot; data-path-to-node=&quot;22&quot;&gt;Cache-Aside 패턴&lt;/b&gt;을 선택했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;23&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23,0,0&quot;&gt;예상된 동작 방식&lt;/b&gt;&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;23,0,1&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Application은 먼저 Redis(캐시)에서 데이터를 조회합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23,0,1,1,0&quot;&gt;Cache Hit:&lt;/b&gt; 캐시에 데이터가 존재하면 즉시 반환합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23,0,1,2,0&quot;&gt;Cache Miss:&lt;/b&gt; 캐시에 데이터가 없으면 DB에서 데이터를 가져와 Redis에 저장한 뒤 결과를 반환합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23,1,0&quot;&gt;선택한 이유&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;23,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23,1,1,0,0&quot;&gt;안전성(Fall-back)&lt;/b&gt;&lt;br /&gt;- Redis 장애가 발생해도 서비스가 완전히 마비되지 않고,&lt;br /&gt;DB 조회를 통해 비즈니스 로직을 유지할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23,1,1,1,0&quot;&gt;메모리 효율성&lt;/b&gt;&lt;br /&gt;- 모든 상품을 미리 적재(Warm-up)하지 않고, &lt;br /&gt;실제로 요청이 들어오는 '주요 상품'만 캐시에 적재되므로 Redis 메모리를 효율적으로 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;25&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2.2 직렬화 방식: GenericJackson2JsonRedisSerializer (JSON)&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Redis에 Java 객체를 저장할 때 직렬화 방식 선택이 중요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;27&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,0,0&quot;&gt;JDK 기본 직렬화 (JdkSerializationRedisSerializer)&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;27,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;설정은 간편하지만 데이터가 Binary 형태로 저장되어 Redis CLI에서 사람이 읽을 수 없습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;클래스의 `serialVersionUID`나 내부 구조가 변경되면 `DeserializationException`이 터지는 단점이 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,1,0&quot;&gt;JSON 직렬화 (GenericJackson2JsonRedisSerializer)&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;27,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;데이터를 사람이 읽을 수 있는 JSON 형태로 저장합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,1,1,1,0&quot;&gt;장점:&lt;/b&gt; Redis CLI로 직접 데이터 조회가 가능하여 운영 중 장애 분석과 디버깅이 매우 용이합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;29&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2.3 데이터 정합성 및 캐시 무효화 (Cache Eviction)&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;캐시 도입 시 가장 위험한 순간은 &quot;DB 데이터는 바뀌었는데, 캐시에는 옛날 데이터가 남아있을 때(Stale Data)&quot;입니다. 예를 들어 가격이 10,000원에서 12,000원으로 변경되었는데 캐시에서 10,000원이 조회되면 심각한 비즈니스 오류가 발생합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;저희는 이를 해결하기 위해 '상품 수정 시 해당 상품 ID의 캐시를 즉시 삭제(Evict)'하는 방식을 채택했습니다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-path-to-node=&quot;31&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;   왜 캐시를 직접 수정(Update)하지 않고 삭제(Evict)하나요?&lt;/b&gt;&lt;br /&gt;수정 API 내에서 캐시 데이터까지 직접 업데이트 하려면 수정 로직이 캐시의 데이터 구조까지 깊게 알아야 해서 결합도가 높아집니다. 또한 DB 수정은 성공했는데 캐시 업데이트만 실패했을 때 정합성을 맞추기 매우 까다로워집니다. 삭제(Evict) 방식을 사용하면&amp;nbsp;DB를 단일 진실 공급원(Single Source of Truth)으로 유지하면서, 다음 조회 시점에 최신 데이터가 자연스럽게 캐싱되므로 훨씬 안전합니다.&lt;/blockquote&gt;
&lt;h3 data-path-to-node=&quot;34&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2.4 트랜잭션과 캐시 제거 시점 동기화 (transactionAware)&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 부분이 구현 과정에서 가장 섬세하게 다뤄야 했던 대목입니다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;36&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring의 `@CacheEvict`를 메서드에 선언하면, 기본적으로 &lt;b data-index-in-node=&quot;38&quot; data-path-to-node=&quot;36&quot;&gt;메서드가 실행되는 시점&lt;/b&gt;에 캐시 삭제 명령을 Redis로 보냅니다. &lt;br /&gt;하지만 메서드에 `@Transactional`이 걸려있다면 어떻게 될까요?&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-path-to-node=&quot;36&quot; data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;[문제&amp;nbsp;상황&amp;nbsp;예시] &lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 상품 수정 메서드 호출 (`@Transactional` 시작) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2.&amp;nbsp;DB&amp;nbsp;데이터&amp;nbsp;UPDATE&amp;nbsp;쿼리&amp;nbsp;실행 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. `@CacheEvict` 작동 ──&amp;gt; Redis 캐시 삭제 완료 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4.&amp;nbsp;예외&amp;nbsp;발생으로&amp;nbsp;인한&amp;nbsp;DB&amp;nbsp;Transaction&amp;nbsp;ROLLBACK! &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;  결과: DB 데이터는 수정되지 않았는데, 캐시만 억울하게 삭제됨 (불필요한 Cache Miss 발생)&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-path-to-node=&quot;38&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;더 복잡한 캐시 갱신 전략에서는 DB 커밋 전 캐시가 지워진 틈을 타 다른 조회 요청이 들어와 롤백 전의 예전 데이터를 다시 캐싱해버리는 문제(Race Condition)가 터질 수도 있습니다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 문제를 막기 위해 `RedisCacheManager` 설정에 `setTransactionAware(true)`를 활성화했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4Jmwv/dJMcahZvB6P/yu2M1A31y9nFfvUTDMYs1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4Jmwv/dJMcahZvB6P/yu2M1A31y9nFfvUTDMYs1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4Jmwv/dJMcahZvB6P/yu2M1A31y9nFfvUTDMYs1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4Jmwv%2FdJMcahZvB6P%2Fyu2M1A31y9nFfvUTDMYs1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;757&quot; height=&quot;155&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;41&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 설정을 통해 DB 트랜잭션이 안전하게 `COMMIT` 된 것을 확인한 후 Redis에 삭제 명령이 내려가도록 구현했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;43&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. 주요 구현 코드 (Implementation)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;44&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;RedisCacheConfig.java&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwj7qpKIzeiVAxUAAAAAHQAAAAAQWQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@EnableCaching
@Profile(&quot;cache&quot;) // 테스트 및 비교 환경 분리를 위해 Profile 제어
public class RedisCacheConfig {

    @Bean
    public CacheManager productCacheManager(RedisConnectionFactory connectionFactory) {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule()); // LocalDate 등 날짜 타입 지원
        objectMapper.activateDefaultTyping(
            LaissezFaireSubTypeValidator.instance, 
            ObjectMapper.DefaultTyping.NON_FINAL
        );

        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofHours(1)) // 1시간 TTL 설정 (보조 안전장치)
            .disableCachingNullValues()  // Null 값 캐싱 방지
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)));

        return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(config)
            .transactionAware() // DB 트랜잭션 커밋 후 캐시 작업 실행
            .build();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;46&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;46,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;46,0&quot;&gt;`disableCachingNullValues()`를 적용한 이유&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;46,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;존재하지 않는 상품 ID 조회 결과(Null)까지 캐싱해 버리면, 추후 해당 ID로 신규 상품이 생겼을 때 캐시 만료 전까지 상품이 조회되지 않는 side-effect를 방지하기 위함입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ng70X/dJMcajbUNd3/UnXieL7BszCh2DSaRJP8Sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ng70X/dJMcajbUNd3/UnXieL7BszCh2DSaRJP8Sk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ng70X/dJMcajbUNd3/UnXieL7BszCh2DSaRJP8Sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fng70X%2FdJMcajbUNd3%2FUnXieL7BszCh2DSaRJP8Sk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;775&quot; height=&quot;154&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwj7qpKIzeiVAxUAAAAAHQAAAAAQWg&quot; data-hveid=&quot;0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwj7qpKIzeiVAxUAAAAAHQAAAAAQWg&quot; data-hveid=&quot;0&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zShm1/dJMcad3RbYo/q2kbgtgH0IC2MpuJCPJemk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zShm1/dJMcad3RbYo/q2kbgtgH0IC2MpuJCPJemk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zShm1/dJMcad3RbYo/q2kbgtgH0IC2MpuJCPJemk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzShm1%2FdJMcad3RbYo%2Fq2kbgtgH0IC2MpuJCPJemk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;780&quot; height=&quot;146&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. k6를 활용한 성능 검증 (Performance Testing)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;개선 효과를 검증하기 위해 OpenSource 부하 테스트 도구인 &lt;b data-index-in-node=&quot;37&quot; data-path-to-node=&quot;4&quot;&gt;k6&lt;/b&gt;를 도입했습니다. 단순 호출이 아닌, &lt;b data-index-in-node=&quot;60&quot; data-path-to-node=&quot;4&quot;&gt;실제 운영 환경의 트래픽 조회 편향(파레토 법칙)을 모사&lt;/b&gt;하고 정확한 비교 측정을 위한 디테일한 옵션들을 스크립트에 반영했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4.1&amp;nbsp; k6 부하 테스트 스크립트의 주요 설계 포인트&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;현실적인 상품 조회 트래픽 편향 (80/15/5 파레토 법칙 적용)&lt;/span&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실제 이커머스 환경에서는 특정 인기 상품에 조회가 몰립니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`selectProductId()` 함수를 통해 상위 20개 인기 상품이 전체 요청의 80%를 차지하고, &lt;br /&gt;일반 상품(15%), 롱테일 상품(5%)이 나머지를 구성하도록 분포를 모델링했습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`setup()` 함수를 활용한 Cold / Warm Cache 조건 통제&lt;/span&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;환경 변수(`WARM_CACHE=true`)에 따라 본 테스트 시작 전 `setup()` 단계에서 `http.batch()`를 통해 인기 상품 1~20번의 캐시를 미리 적재합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`setup()` 단계의 요청은 &lt;b data-index-in-node=&quot;16&quot; data-path-to-node=&quot;7,1,1,1,0&quot;&gt;본 테스트의 메트릭 집계(`http_req_duration`)에 영향을 주지 않으므로&lt;/b&gt;, &lt;br /&gt;오직 순수한 Warm Cache 상태의 응답 속도만 깔끔하게 측정할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,2,0&quot;&gt;`discardResponseBodies: true` 옵션을 통한 k6 리소스 오버헤드 방지&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;본 테스트는 성능(Latency/RPS) 측정 목적이므로 응답 본문을 메모리에 보관하지 않습니다. 이를 통해 k6 실행 머신의 CPU/RAM 사용량을 줄여 &lt;b data-index-in-node=&quot;87&quot; data-path-to-node=&quot;7,2,1,0,0&quot;&gt;측정 도구 때문에 발생할 수 있는 데이터 왜곡을 방지&lt;/b&gt;했습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,3,0&quot;&gt;constant-arrival-rate 실행기 및 상세 메트릭(Counter/Rate) 수집&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7,3,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Virtual User(VU)의 수와 무관하게 &lt;b data-index-in-node=&quot;26&quot; data-path-to-node=&quot;7,3,1,0,0&quot;&gt;초당 200 RPS의 일정 부하&lt;/b&gt;를 지속적으로 주입하도록 설정했습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4xx, 5xx 에러 및 커스텀 검증 비율(product_detail_valid_responses)을 추적하는 커스텀 메트릭을 추가하여 테스트의 정확도를 높였습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;k6 테스트 스크립트(script.js)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwj7qpKIzeiVAxUAAAAAHQAAAAAQ3QE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import http from 'k6/http';
import { check } from 'k6';
import { Counter, Rate } from 'k6/metrics';

const BASE_URL = __ENV.BASE_URL || 'http://localhost:8080';

const RATE = Number(__ENV.RATE || 200);
const DURATION = __ENV.DURATION || '3m';
const PRE_ALLOCATED_VUS = Number(__ENV.PRE_ALLOCATED_VUS || 50);
const MAX_VUS = Number(__ENV.MAX_VUS || 300);

const WARM_CACHE = (__ENV.WARM_CACHE || 'false').toLowerCase() === 'true';

// 커스텀 지표 정의
const requestFailures = new Counter('product_detail_failures');
const clientErrors = new Counter('product_detail_4xx_errors');
const serverErrors = new Counter('product_detail_5xx_errors');
const validResponses = new Rate('product_detail_valid_responses');

export const options = {
  /*
   * k6 자체의 메모리/CPU 사용량을 줄여 서버 성능 측정 왜곡을 방지
   */
  discardResponseBodies: true,

  scenarios: {
    product_detail_read: {
      /*
       * 캐시 적용 전후를 동일한 요청량(200 RPS)으로 비교하기 위한 constant-arrival-rate
       */
      executor: 'constant-arrival-rate',
      rate: RATE,
      timeUnit: '1s',
      duration: DURATION,
      preAllocatedVUs: PRE_ALLOCATED_VUS,
      maxVUs: MAX_VUS,
      gracefulStop: '10s',
    },
  },

  thresholds: {
    // 전체 HTTP 실패율 0.1% 미만
    http_req_failed: ['rate&amp;lt;0.001'],

    // 엔드포인트 별 Latency 임계치 (p95 &amp;lt; 200ms, p99 &amp;lt; 500ms)
    'http_req_duration{endpoint:product-detail}': [
      'p(95)&amp;lt;200',
      'p(99)&amp;lt;500',
    ],

    // 상태 코드 검증 성공률 99.9% 이상
    product_detail_valid_responses: ['rate&amp;gt;0.999'],

    // 실패 요청 및 5xx 서버 에러 0건
    product_detail_failures: ['count==0'],
    product_detail_5xx_errors: ['count==0'],
  },

  summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(90)', 'p(95)', 'p(99)'],
};

function randomInteger(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

/**
 * 실제 서비스의 상품 조회 편향을 단순화한 분포
 * 80%: 인기 상품 (1~20)
 * 15%: 일반 상품 (21~1000)
 *  5%: 롱테일 상품 (1001~10000)
 */
function selectProductId() {
  const probability = Math.random();

  if (probability &amp;lt; 0.80) {
    return randomInteger(1, 20);
  }
  if (probability &amp;lt; 0.95) {
    return randomInteger(21, 1000);
  }
  return randomInteger(1001, 10000);
}

/**
 * WARM_CACHE=true 설정 시 실행되는 사전 워밍업
 * setup() 실행 결과는 메트릭 집계(http_req_duration)에서 제외됨
 */
export function setup() {
  if (!WARM_CACHE) {
    console.log('Warm-up disabled: Cold Cache 또는 No Cache 테스트 진행');
    return;
  }

  console.log('Warm-up enabled: 인기 상품 1~20 캐시 적재 시작');

  const requests = [];
  for (let productId = 1; productId &amp;lt;= 20; productId += 1) {
    requests.push({
      method: 'GET',
      url: `${BASE_URL}/api/v1/products/${productId}`,
      params: {
        tags: { endpoint: 'cache-warmup', name: 'Cache warm-up' },
        timeout: '5s',
      },
    });
  }

  const responses = http.batch(requests);
  const warmupSuccess = responses.every((response) =&amp;gt; response.status === 200);

  if (!warmupSuccess) {
    const failures = responses
      .map((response, index) =&amp;gt; ({
        productId: index + 1,
        status: response.status,
      }))
      .filter((result) =&amp;gt; result.status !== 200);

    throw new Error(`캐시 워밍업 실패: ${JSON.stringify(failures)}`);
  }

  console.log('Warm-up completed: 인기 상품 1~20 캐시 적재 완료');
}

export default function () {
  const productId = selectProductId();

  const response = http.get(`${BASE_URL}/api/v1/products/${productId}`, {
    tags: {
      name: 'GET /api/v1/products/{productId}',
      endpoint: 'product-detail',
    },
    timeout: '5s',
  });

  const success = check(response, {
    '상품 상세 응답 상태가 200이다': (res) =&amp;gt; res.status === 200,
  });

  validResponses.add(success);

  if (success) {
    return;
  }

  // 예외 및 에러 카운팅
  requestFailures.add(1);

  if (response.status &amp;gt;= 400 &amp;amp;&amp;amp; response.status &amp;lt; 500) {
    clientErrors.add(1);
  }

  if (response.status &amp;gt;= 500) {
    serverErrors.add(1);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4.2 테스트 결과 비교: No Cache vs Warm Cache&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cL7H95/dJMcajpp0wz/m4sLJwK7ezuilfJV1zSBdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cL7H95/dJMcajpp0wz/m4sLJwK7ezuilfJV1zSBdK/img.png&quot; data-alt=&quot;캐시 미적용 (No Cache)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cL7H95/dJMcajpp0wz/m4sLJwK7ezuilfJV1zSBdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcL7H95%2FdJMcajpp0wz%2Fm4sLJwK7ezuilfJV1zSBdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;694&quot; height=&quot;406&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;632&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;캐시 미적용 (No Cache)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;620&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6GlQr/dJMcafAGbnN/nYkVv4nsTZHKINYrcdeL50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6GlQr/dJMcafAGbnN/nYkVv4nsTZHKINYrcdeL50/img.png&quot; data-alt=&quot;Warmming Cache (캐시 적용 후 최초 실행)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6GlQr/dJMcafAGbnN/nYkVv4nsTZHKINYrcdeL50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6GlQr%2FdJMcafAGbnN%2FnYkVv4nsTZHKINYrcdeL50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;705&quot; height=&quot;427&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;620&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Warmming Cache (캐시 적용 후 최초 실행)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m4izx/dJMcaiYmNY9/fwyZS3gjKBd1klYVjFXY71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m4izx/dJMcaiYmNY9/fwyZS3gjKBd1klYVjFXY71/img.png&quot; data-alt=&quot;Warm Cache (적용 후)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m4izx/dJMcaiYmNY9/fwyZS3gjKBd1klYVjFXY71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm4izx%2FdJMcaiYmNY9%2FfwyZS3gjKBd1klYVjFXY71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;711&quot; height=&quot;441&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Warm Cache (적용 후)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;위 스크립트를 통해 &lt;b data-index-in-node=&quot;11&quot; data-path-to-node=&quot;13&quot;&gt;캐시 미적용(No Cache)&lt;/b&gt; 상태와 사전 워밍업이 완료된 &lt;b data-index-in-node=&quot;44&quot; data-path-to-node=&quot;13&quot;&gt;Warm Cache(`WARM_CACHE=true`)&lt;/b&gt; 상태에서 각각 3분간 200 RPS 부하를 가하여 얻은 지표 비교입니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;14&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;지표 (Metrics)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;캐시 미적용 (No Cache)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Warm Cache (적용 후)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;개선율&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0,0&quot;&gt;평균 응답시간 (Avg Latency)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,1,1,0&quot;&gt;5.19 ms&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,1,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,2,0&quot;&gt;1.71 ms&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,1,3,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,3,0&quot;&gt;약 67% 감소 ⚡&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,2,0,0&quot;&gt;중앙값 (Median Latency)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,2,1,0&quot;&gt;4.26 ms&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,2,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,2,2,0&quot;&gt;1.37 ms&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,2,3,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,2,3,0&quot;&gt;약 68% 감소 ⚡&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,3,0,0&quot;&gt;p95 Latency&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,3,1,0&quot;&gt;6.75 ms&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,3,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,3,2,0&quot;&gt;5.32 ms&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,3,3,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,3,3,0&quot;&gt;약 21% 감소&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,4,0,0&quot;&gt;p99 Latency&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,4,1,0&quot;&gt;11.33 ms&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,4,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,4,2,0&quot;&gt;6.82 ms&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,4,3,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,4,3,0&quot;&gt;약 40% 감소  &lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,5,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,5,0,0&quot;&gt;요청 누락 (Dropped Iterations)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,5,1,0&quot;&gt;27 건&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,5,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,5,2,0&quot;&gt;0 건&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,5,3,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,5,3,0&quot;&gt;100% 해so (완성도 향상)  &lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,6,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,6,0,0&quot;&gt;성공률 / 에러건수&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,6,1,0&quot;&gt;100% (0건)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,6,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,6,2,0&quot;&gt;100% (0건)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot; data-path-to-node=&quot;14,6,3,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,6,3,0&quot;&gt;기준 충족&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;결과 분석 및 인사이트&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;17&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,0,0&quot;&gt;상위 80% 인기 상품의 In-Memory 반환 효과 (Avg 67% 감소)&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;`selectProductId()`로 모델링한 인기 상품(1~20번)들이 `setup()` 단계에서 Redis에 완벽하게 Warm-up되어, 트래픽의 80% 이상이 DB I/O 없이 &lt;b data-index-in-node=&quot;98&quot; data-path-to-node=&quot;17,0,1,0,0&quot;&gt;1.37ms(중앙값) 내외의 초저지연으로 처리&lt;/b&gt;되었습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,1,0&quot;&gt;p99 지연 시간 및 Dropped Iterations 100% 해소&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;No Cache 상태에서는 초당 200건의 요청이 지속적으로 DB Connection을 점유하면서 27건의 Dropped Iterations(k6가 설정된 200 RPS 비율을 맞추지 못하고 요청을 놓치는 현상)가 발생했습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;반면 Warm Cache 환경에서는 Redis가 읽기 부하를 흡수해 줌에 따라 &lt;b data-index-in-node=&quot;44&quot; data-path-to-node=&quot;17,1,1,1,0&quot;&gt;단 한 건의 Dropped Iteration 없이 200 RPS 부하를 완벽히 소화&lt;/b&gt;했으며, 상위 1% Latency(p99)도 11.33ms에서 6.82ms로 크게 안정화되었습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,2,0&quot;&gt;재현성 검증 (Repeatability)&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Warm Cache 스크립트를 동일 조건으로 3회 반복 실행하였으며, 평균 응답 시간 1.63ms ~ 1.76ms, p99 6.62ms ~ 6.95ms, Dropped Iterations 0건으로 지표가 일관되게 재현되어 &lt;b data-index-in-node=&quot;124&quot; data-path-to-node=&quot;17,2,1,0,0&quot;&gt;시스템 차원의 구조적 안정성이 입증&lt;/b&gt;되었습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-path-to-node=&quot;64&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5. 마치며 (Takeaways)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;65&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이번 Redis 캐시 도입 과정을 통해 배운 점은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;66&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;66,0,0&quot;&gt;캐시는 속도 향상만의 도구가 아니다:&lt;/b&gt; 단순히 API 응답 속도를 빠르게 만드는 것을 넘어, &lt;b data-index-in-node=&quot;51&quot; data-path-to-node=&quot;66,0,0&quot;&gt;DB 부하를 차단하여 시스템 전체의 안정성을 끌어올리는 방화벽 역할&lt;/b&gt;을 수행함을 체감했습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;66,1,0&quot;&gt;트랜잭션과의 연계 고려:&lt;/b&gt; Framework가 제공하는 @CacheEvict를 무작정 사용하기보다, &lt;b data-index-in-node=&quot;55&quot; data-path-to-node=&quot;66,1,0&quot;&gt;DB 커밋 시점과 캐시 삭제 시점 간의 시차/롤백 가능성&lt;/b&gt;을 고려해 transactionAware 설정을 챙기는 철저함이 중요하다는 것을 배웠습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;66,2,0&quot;&gt;수치 기반의 검증:&lt;/b&gt; &quot;좋아졌을 것이다&quot;라는 추측 대신 k6 부하 테스트를 통한 객관적 지표(Latency, Dropped Iterations)로 성과를 증명하는 포터블한 아키텍처 검증 능력을 기를 수 있었습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category> Retrospective</category>
      <category>cache</category>
      <category>redis</category>
      <author>limdaeil</author>
      <guid isPermaLink="true">https://limdaeil.tistory.com/76</guid>
      <comments>https://limdaeil.tistory.com/76#entry76comment</comments>
      <pubDate>Thu, 23 Jul 2026 20:35:07 +0900</pubDate>
    </item>
  </channel>
</rss>