IoC · DI · Bean — Spring 의 *심장*
IoC · DI · Bean — Spring 의 *심장*
🎯 이 lesson 을 읽고 나면
이 lesson 을 다 읽고 나면 아래 3가지를 자신 있게 할 수 있습니다.
- ▸✅ @Autowired 가 어떻게 Bean 을 주입하는지 (3가지 방법)
- ▸✅ 같은 타입 Bean 충돌 시 @Qualifier / @Primary 해결
- ▸✅ @Configuration + @Bean 으로 외부 라이브러리 등록
학습 목표를 체크리스트로 두고 다 답할 수 있게 되면 lesson 을 닫으세요.
IoC (제어의 역전) — *내가 만들지 않고 받아쓴다*
핵심 한 줄
IoC (Inversion of Control) = 객체를 내가 직접 만드는 게 아니라 프레임워크가 만들어서 내게 건네주는 방식. 제어의 흐름이 뒤집혔다 는 뜻.
옛 방식 — 모든 걸 직접 만들기
겉보기엔 깔끔하지만 큰 문제 가 있습니다:
- ▸테스트 어려움:
OrderService만 테스트하고 싶은데 진짜EmailService가 같이 동작 — 메일이 실제 발송됨 - ▸수정 어려움:
EmailService가 SmsService 로 바뀌면 모든 호출 코드 수정 - ▸의존성 추적 어려움: 어디서 누가 누구를 만드는지 추적 불가
새 방식 — 받아쓰기
이제 OrderService 는 어디서 만들어졌는지 신경 안 씁니다. 그냥 받아서 씁니다. 누가 만들지는 Spring 의 책임.
이게 IoC. 객체 생성의 제어권을 프레임워크에 넘긴 것.
왜 좋은가
1. 테스트가 쉽다:
2. 구현 교체가 쉽다: EmailService → KakaoMessageService 로 바꾸려면 Spring 설정 한 줄 만. 호출 코드는 그대로.
3. 의존성이 명확하다: 생성자 시그니처만 봐도 무엇이 필요한지 한눈에.
IoC 컨테이너 — Spring 의 공장
객체들을 만들고 보관하고 연결해주는 Spring 의 핵심 부품을 IoC 컨테이너 또는 ApplicationContext 라 합니다.
동작 흐름:
1. 앱 시작 시 Spring 이 @Component·@Service·@Repository·@Controller 가 붙은 클래스를 모두 스캔
2. 발견한 각 클래스의 인스턴스를 1개씩 만들어 컨테이너에 보관
3. 다른 곳에서 그 객체가 필요하면 생성자·@Autowired 로 주입
> 💡 컨테이너에 보관된 객체를 Bean 이라고 부릅니다. 콩 (bean) 처럼 작은 단위 객체 모음이라는 비유.
한 번 정리
- ▸IoC = 객체 생성을 프레임워크에 위임
- ▸장점 = 테스트·교체·추적이 모두 쉬워짐
- ▸Spring 컨테이너 = Bean 들을 만들고 연결하는 공장
이게 Spring 의 가장 근본 사상. DI·AOP·트랜잭션 모두 IoC 위에서 동작합니다.
DI (의존성 주입) — *세 가지 방법*
DI 와 IoC 의 관계
IoC 가 더 큰 개념 이고, DI (Dependency Injection) 는 그 실현 수단 중 하나입니다. 객체에게 필요한 의존성을 외부에서 주입 하는 구체적 기법.
3가지 주입 방법
1. 생성자 주입 (가장 권장)
장점:
- ▸
final가능 → 불변 보장 - ▸객체 생성 시점에 모든 의존성 보장
- ▸순환 참조 시 컴파일 타임 에 발견
- ▸테스트 시 가짜 객체 주입 쉬움
Spring 4.3+ 부터 @Autowired 생략 가능. Lombok @RequiredArgsConstructor 와 결합하면 더 짧음:
2. Setter 주입
장점: 선택적 의존성 (있을 수도 없을 수도) 표현 가능
단점: final 못 씀. 호출 안 되면 null. 거의 안 씀.
3. 필드 주입 (지양)
겉보기엔 간결하지만:
- ▸테스트 어려움 (Reflection 으로만 주입 가능)
- ▸순환 참조 런타임 까지 모름
- ▸의존성이 너무 쉬워서 무분별하게 추가
> 💡 현장 합의: 항상 생성자 주입. 필드 주입은 옛 코드에만 보임.
같은 타입이 여러 개일 때
Spring 이 어느 걸 주입 해야 할지 모릅니다. 3가지 해결:
1. @Primary — 기본값 지정:
2. @Qualifier — 명시적 선택:
3. List 로 받기 — 모든 구현 받기:
흔한 함정
순환 참조: A 가 B 를 주입받고, B 가 A 를 주입받는 경우. 생성자 주입에선 시작 시 에러. 설계가 잘못된 신호 — 공통 부분을 새 클래스로 분리 가 정답.
@Autowired vs @Resource vs @Inject: Spring 표준 @Autowired 만 알아도 무방. 다른 둘은 Java 표준 인데, 실무에선 거의 안 쓰임.
한 번 정리
- ▸생성자 주입이 표준. Lombok
@RequiredArgsConstructor와 결합 추천 - ▸같은 타입 여러 개면
@Primary또는@Qualifier - ▸순환 참조는 설계 신호 — 리팩토링 필요
Bean — *Spring 이 관리하는 객체*
Bean 이 뭐냐
Spring IoC 컨테이너에 등록되어 관리되는 객체 가 Bean 입니다. 콩 처럼 작은 단위 객체 모음이라는 비유. 일반 자바 객체와 다른 점은 Spring 이 생성·라이프사이클·주입을 모두 관리 한다는 것.
Bean 등록 방법
1. @Component 계열 (가장 흔함)
이름은 다르지만 기본적으로 Bean 등록 이라는 점은 같습니다. 의미적 구분 + 일부 기능 차이 (@Repository 는 예외 변환).
2. @Bean (메서드 단위)
설정 클래스의 메서드 리턴 값을 Bean 으로:
내 코드가 아닌 외부 라이브러리 클래스 (RestTemplate·ObjectMapper 등) 를 Bean 으로 만들 때 사용.
3. 자동 설정 — Spring Boot Starter
spring-boot-starter-data-jpa 같은 starter 의존성이 자동으로 수많은 Bean 등록 (DataSource·EntityManager·TransactionManager 등).
Bean Scope — 언제 새로 만드나
기본은 singleton — 컨테이너에 하나만 만들고 모두 공유. 하지만 다른 옵션도 있습니다.
가장 흔한 함정: singleton Bean 에 변경되는 필드 두기. 멀티스레드 환경에서 데이터 깨짐. 항상 불변 또는 외부 저장소 (DB·Redis) 사용.
Bean 라이프사이클
Bean 은 다음 단계를 거칩니다:
1. 인스턴스 생성 (생성자 호출)
2. 의존성 주입 (@Autowired 등)
3. 초기화 (@PostConstruct 또는 InitializingBean.afterPropertiesSet())
4. 사용
5. 소멸 (@PreDestroy 또는 DisposableBean.destroy())
@PostConstruct 는 자주 씁니다 — 자원 준비·캐시 워밍 등. @PreDestroy 는 Graceful Shutdown 에 중요.
@Configuration vs @Component
둘 다 Bean 으로 등록되지만 역할이 다름.
- ▸@Configuration — 설정 클래스. 안의
@Bean메서드를 프록시로 감싸 동일 객체 반환 보장 - ▸@Component — 일반 Bean
@Configuration 안에서 @Bean 메서드를 서로 호출 하면 같은 인스턴스 가 리턴됩니다. @Component 면 매번 새 객체가 생기는 버그 가능성.
한 번 정리
- ▸Bean = Spring 이 관리하는 객체
- ▸등록:
@Service등 어노테이션 또는@Bean메서드 - ▸기본 Scope 는 singleton (대부분 OK)
- ▸
@PostConstruct·@PreDestroy로 라이프사이클 훅 가능
@Bean · @Configuration · @Qualifier · @Primary 충돌 해결
@Component vs @Bean — 언제 뭘 쓰나
- ▸@Component (@Service·@Repository·@Controller 포함) — 내가 만든 클래스 에. Spring 이 자동 스캔.
- ▸@Bean — 외부 라이브러리 객체 또는 조건부 등록 이 필요할 때.
@Configuration + @Bean 예시
외부 라이브러리 클래스 (RestTemplate·ObjectMapper) 는 내가 @Component 를 붙일 수 없으니 @Bean 으로 등록.
같은 타입 Bean 이 2개 — 충돌
같은 타입이 여러 개 → Spring 이 어떤 걸 주입해야 할지 모름.
해결 1 — @Qualifier 로 이름 지정
Bean 이름은 메서드명 이 기본 (cardPayment·kakaoPayment).
해결 2 — @Primary 로 기본 선택
둘 중 뭘 쓰나
- ▸기본/주력 구현이 명확 →
@Primary - ▸상황별로 다른 걸 주입 →
@Qualifier - ▸둘 다 안 쓰고 이름으로 자동 매칭 도 가능 (
@Autowired PaymentService cardPayment;)
조건부 등록 — @ConditionalOnProperty
application.yml 의 payment.provider=kakao 일 때만 Bean 등록. 프로파일별 다른 구현 을 깔끔하게 분리할 수 있어요.
🤖 AI 에게 이렇게 요청해보세요
이 lesson 의 개념을 알면 AI 에게 구체적으로 지시할 수 있습니다. 막연한 "고쳐줘" 가 아니라 어휘를 가진 요청 — 그게 토큰 절약의 출발점입니다.
- ▸"이 PaymentService 의 구현체가 2개인데 @Qualifier 로 분기해줘"
- ▸"이 RestTemplate 를 @Configuration + @Bean 으로 등록해줘"
- ▸"@ConditionalOnProperty 로 카카오 결제 모듈을 조건부 활성화해줘"
왜 이게 토큰을 줄이나
개념을 모를 땐 AI 답변을 받고도 "그게 뭐예요?" 를 다시 물어야 합니다. 그 "다시 물음" 이 토큰을 잡아먹습니다. 개념 한 번 익혀두면 대화가 한 번에 끝납니다.