목차
- DI(Dependency Injection)란?
- DI 구현 방식 종류
- 스프링의 IoC 컨테이너
- 스프링 내부 동작 원리
- 현재의 방법
DI란
객체간의 의존관계를 외부에서 생성해주는 패턴
A에서 B 객체를 사용해야할 때, A 안에서 B b = new B()를 통해서 B를 먼저 생성해주고 b.xx 형식으로 B의 메소드를 호출해서 사용하는 방법이 쉽게 떠오르는 방법이다.
하지만 이 방법의 단점은 B의 변경사항이 A에 영향을 미치는 것이다. 이 말을 처음들었을 때는 무슨 변경사항이 영향을 미친다는 것인지 이해가 되지 않아 예시를 찾아봤다.
직접 생성하는 방법
public class MemberRepository {
public void save() {
System.out.println("DB에 저장");
}
}
public class MemberService {
private MemberRepository memberRepository = new MemberRepository(); // 직접 생성
public void register() {
memberRepository.save();
}
}
public class MemoryMemberRepository extends MemberRepository {
@Override
public void save() {
System.out.println("메모리에 저장");
}
}
// 수동으로 바꿔야 함
private MemberRepository memberRepository = new MemoryMemberRepository();
위 코드는 직접생성하는 방법을 통해서 A가 B에게 의존하는 방법이다. 만약 MemberRepository가 MemoryMemberRepository로 바뀐다면 MemberService에서 new MemberRepository()를 new MemoryMemberRepository로 바꿔줘야 한다.'
DI를 사용한 방식
public class MemberService {
private final MemberRepository memberRepository;
// 생성자로 주입받음
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
public void register() {
memberRepository.save();
}
}
public class MySQLMemberRepository implements MemberRepository {
public void save() {
System.out.println("MySQL에 저장");
}
}
@Configuration
public class AppConfig {
@Bean
public MemberRepository memberRepository() {
return new MySQLMemberRepository(); // 여기만 변경하면 됨
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
}
DI를 사용한 구조에서는 생성자를 통해서 MemberRepository를 주입받기 때문에 B(MemberRepository)가 바뀌어도 A(MemberService)의 코드에는 변경사항이 없다.
𝐐. 실무에서 실제로 A가 B에 의존적인 상황에서 B가 바뀌는 상황이 생길까?
여러 상황이 있겠지만 대표적인 몇가지만 꼽자면
- 외부 API 변경 (ex 카카오 로그인 -> 네이버 로그인)
- 라이브러리 교체 (ex RestTemplate -> WebClinet)
- Mock 객체 주입(테스트용)
등이 있을 것이다.
DI 구현 종류
의존성 주입을 구현하는 방법에는 4가지가 있다.
- 생성자 주입
- 세터 주입
- 필드 주입
- xml 주입
생성자 주입
A 객체의 생성자에 B 객체를 받아서 A가 생성될때 B를 주입하는 방식
@Component
public class OrderService {
private final PaymentService paymentService;
@Autowired
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
세터 주입
A가 생성되고 후에 세터를 통해 B객체를 주입받는 방식
@Component
public class OrderService {
private PaymentService paymentService;
@Autowired
public void setPaymentService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
필드 주입
@Autowired가 자동으로 의존성을 주입해줌. 스프링 컨테이너가 없으면 구현 불가능함.
@Component
public class OrderService {
@Autowired
private PaymentService paymentService;
}
xml 주입
xml의 설정을 보고 id, class 등의 정보를 파싱해서 빈을 생성하는 BeanFactory 클래스에서 수동으로 빈을 등록해 주는 방법
<bean id="orderService" class="com.example.OrderService">
<property name="paymentService" ref="paymentService" />
</bean>
IoC 컨테이너
스프링 프레임워크에서 IoC 원칙을 실현하는 핵심 컴포넌트로, 객체의 생성과 의존성 주입을 담당하는 컨테이너
IoC란
Inversion of Control의 약자로 번역하면 제어의 역전 이라는 뜻이다.
프로그램의 흐름(제어권)을 개발자가 아닌 프레임워크가 제어 하도록 맡기는 원칙이다.
IoC 컨테이너의 역할
- 객체 생성 : 클래스들을 스캔하고, 필요한 객체들을 생성함
- 의존성 주입 : 생성한 객체들 간의 의존 관계를 주입해줌 (@Autowired, 생성자 등)
- 빈 생명주기 관리 : 초기화 → 사용 → 소멸까지의 전 과정을 관리
- 싱글톤 관리 : 기본적으로 하나의 Bean만 만들어서 공유 (singleton scope)
𝐐. IoC와 DI가 같은 것인가
IoC의 구현 방법 중 하나가 DI인 것으로 개념 자체가 다름.
IoC는 객체의 제어권을 프레임워크에 넘기는 것. -> IoC 컨테이너가 빈의 생명주기를 관리하고 의존성을 주입해주기 때문에 우리가 제어 하지 않음.
DI는 필요한 의존 객체를 외부에서 주입 받는 것.
IoC 컨테이너가 DI를 사용하지 않을 수도 있음. (Service Locator라는 방식을 사용해도 IoC 컨테이너 구현 가능)
💡 Service Locator = 객체가 직접컨테이너에 요청해서 필요한 의존성을 가져오는 방식
스프링 내부 동작
스프링의 IoC 컨테이너
스프링 1.x 버전에서는 BeanFactory를 사용했지만 스프링 2.0에 들어서면서 ApplicationContext를 사용하며 현재는 ApplicationContext가 표준으로 자리 잡았다.
💡 스프링 프레임워크 버전과 스프링 부트 버전은 다른 것임. 우리가 주로 사용하는 스프링부트의 1.0 버전은 스프링 프레임워크 4.xx 버전이었음. (현재 스프링부트는 3.xx 버전 스프링 프레임워크는 6.xx 버전까지 나옴)
𝐐. BeanFactory와 ApplicationContext의 차이점이 뭘까
ApplicationContext는 BeanFactory를 확장시킨 컨테이너이다.
첫번째로는 기능적인 차이가 있다.
대표적으로는 AOP 자동 연동, 어플리케이션 이벤트, 환경 추상화, 빈 즉시 로딩 등이 있는데 이것 말고도 되게 많은 기능들이 추가로 있다.
ApplicationContext를 하나의 주제로 따로 다뤄도 될 정도의 양이므로 따로 찾아보는 시간을 가지는 것도 좋을 것 같다.
두번째는 빈의 생명주기 차이가 있다.
BeanFactory는 객체가 호출됐을때 빈을 생성하지만 ApplicationContext는 어플리케이션이 시작될 때 @ComponentScan으로 모든 @Component들을 찾아서 빈으로 등록시킨다. @ComponentScan이라는 기능 덕분에 빈을 수동으로 등록하지 않아도 되지만 BeanFactory는 수동으로 등록해 줘야 한다. 추가로 ApplicationContext는 라이프사이클 인터페이스를 지원해준다. (자동으로 처리)
스프링 내부 동작
public class SpringApplication {
public static void main(String[] args) {
SpringApplication.run(SpringApplication.class, args);
}
}
SpringApplication.run() 실행 시
public ConfigurableApplicationContext run(String... args) {
// 1. ApplicationContext 인스턴스 생성
ConfigurableApplicationContext context = createApplicationContext();
// 2. ApplicationContext에 필요한 설정
prepareContext(context, ...);
// 3. Bean 등록 및 초기화
refreshContext(context); // <- 여기서 context.refresh() → 본격적으로 Bean 생성 시작
return context;
}
createApplicationContext 내부
protected ConfigurableApplicationContext createApplicationContext() {
if (webApplicationType == WebApplicationType.SERVLET) {
return new AnnotationConfigServletWebServerApplicationContext();
}
return new AnnotationConfigApplicationContext();
}
이 시점에 ApplicationContext 객체가 생성됨. 생성은 되었지만 빈 생성이나 주입하기 전.
𝐐. 실제로 빈은 어떻게 생성되는가
위에서 refreshContext(context)를 하면 다음과 같은 메소드가 호출된다.
private void refreshContext(ConfigurableApplicationContext context) {
context.refresh();
}
이때 ConfigurableApplicationContext의 refresh를 호출하게 된다. ConfigurableApplicationContext는 인터페이스이고 실제 구현은 AbstractApplicationContext에 있다.
public void refresh() throws BeansException, IllegalStateException {
this.startupShutdownLock.lock();
try {
this.startupShutdownThread = Thread.currentThread();
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// 1. 환경, 리소스, 속성 초기화
this.prepareRefresh();
// 2. BeanFactory 생성 및 리셋
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
// 3. BeanFactory에 기본 설정 적용
this.prepareBeanFactory(beanFactory);
try {
// 4. 사용자 정의 후처리기 hook (선택적으로 오버라이드 가능)
this.postProcessBeanFactory(beanFactory);
// 5. BeanFactoryPostProcessor, BeanPostProcessor 실행
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
this.invokeBeanFactoryPostProcessors(beanFactory); // @Configuration 등 처리
this.registerBeanPostProcessors(beanFactory); // @Autowired, AOP 등 준비
beanPostProcess.end();
// 6. 메시지 처리기 및 이벤트 브로드캐스터 초기화
this.initMessageSource();
this.initApplicationEventMulticaster();
// 7. 서브클래스 확장 지점 (웹서버 초기화 등)
this.onRefresh();
// 8. 이벤트 리스너 등록
this.registerListeners();
// 9. Bean 생성 및 의존성 주입 시작
this.finishBeanFactoryInitialization(beanFactory);
// 10. 컨텍스트 초기화 완료 (이벤트 발행 등)
this.finishRefresh();
} catch (Error | RuntimeException ex) {
// 예외 발생 시 초기화 취소 및 Bean 파괴
...
throw ex;
} finally {
contextRefresh.end();
}
} finally {
this.startupShutdownThread = null;
this.startupShutdownLock.unlock();
}
}
refresh 동작 요약
1. prepareRefresh → 환경 초기화
2. obtainFreshBeanFactory → BeanFactory 생성/리셋
3. prepareBeanFactory → 기본 설정
4. postProcessBeanFactory → 사용자 정의 후처리기
5. invoke/register PostProcessors→ DI, AOP 준비
6. initMessageSource → 다국어 처리
7. initApplicationEventMulticaster → 이벤트 처리기 등록
8. onRefresh → 웹 서버 초기화 등
9. finishBeanFactoryInitialization → Bean 생성 및 주입
10. finishRefresh → 완료 이벤트 발행
AbstractApplicationContext 경로
ApplicationContext가 어떻게 구현되어 있는지 확인 하고 싶다면
spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java 에서 확인할 수 있다.
𝐐. 실제로 스프링부트를 실행하면 어떤 클래스가 실행되어서 IoC 컨테이너의 역할을 하는걸까
AnnotationConfigServletWebServerApplicationContext가 최종 구현체이고 내장 톰캣, DispatcherServlet 등록 등 웹 관련 구성 수행하고 내부적으로 refresh()를 호출하여 모든 Bean 생성 및 주입 수행한다.
아래의 패키지 안에 들어 있다.
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/context/AnnotationConfigServletWebServerApplicationContext.java
AnnotationConfigServletWebServerApplicationContext의 상속 관계
AnnotationConfigServletWebServerApplicationContext
↓
ServletWebServerApplicationContext
↓
GenericWebApplicationContext
↓
GenericApplicationContext
↓
AbstractApplicationContext
💻 참고 자료
https://inpa.tistory.com/entry/GOF-💠-팩토리-메서드Factory-Method-패턴-제대로-배워보자
https://lincoding.tistory.com/76
https://www.youtube.com/watch?v=BO7QFUnVdjc
https://www.youtube.com/watch?v=bJfbPWEMj_c
'Spring' 카테고리의 다른 글
스프링 AOP (1) | 2025.05.14 |
---|---|
주문상태 동시성 처리 (0) | 2025.05.05 |
스레드풀 전략 (0) | 2025.05.02 |
서블릿 컨테이너의 이해 (0) | 2025.04.04 |
엔티티 DTO 변환 위치 (2) | 2025.01.10 |