Spring은 자바 기반 애플리케이션 개발에서 효율성을 높이고, 코드의 유연성과 재사용성을 극대화하는 데 중점을 둔다. 이 중심에는 Bean, Spring IoC(Inversion of Control), 그리고 의존성 주입(Dependency Injection, DI)이라는 개념이 있다. 이 글에서는 이 세 가지 개념을 하나씩 살펴보고, 스프링이 이를 통해 어떻게 애플리케이션을 관리하는지 설명한다.
Bean
Spring에서 Bean은 스프링 IoC 컨테이너가 관리하는 객체를 의미한다. 애플리케이션 실행 과정에서 생성되고, 필요한 곳에 주입되며, 생명 주기를 관리받는 객체이다. 아래는 Bean의 대표적인 특징 3가지이다.
- Bean은 스프링 컨테이너에 의해 등록되고, 생성되며, 관리된다.
- 기본적으로 싱글턴(Singleton)으로 생성되어 컨테이너 내에서 하나의 인스턴스만 유지된다.
- Bean은 보통 @Component, @Service, @Repository, @Controller와 같은 애노테이션으로 정의하거나, @Configuration 클래스 내의 @Bean 메서드로 등록된다.
@Component
public class MyBean {
public void doSomething() {
System.out.println("Bean is working");
}
}
Spring IoC(Inversion of Control)
IoC(Inversion of Control)는 제어의 역전이라는 의미로, 객체의 생성과 생명 주기의 제어를 개발자가 아닌 프레임워크(즉, 스프링 컨테이너)가 담당하는 것을 말한다. 이 개념은 객체 간의 의존성을 효율적으로 관리하며, 코드의 결합도를 낮추는 데 도움을 준다.
IoC 컨테이너
Spring의 IoC 컨테이너는 애플리케이션의 구성 요소를 생성하고 관리하며, 이를 애플리케이션 실행 중 필요한 곳에 제공한다. 주요 IoC 컨테이너는 다음과 같다.
- BeanFactory: 기본 IoC 컨테이너로, 초기화 속도가 빠르다.
- ApplicationContext: BeanFactory를 확장한 컨테이너로, 더 많은 기능을 제공한다.
예) 이벤트 처리, 국제화 지원(MessageSource) 등
위 언급된 컨테이너들을 기반으로 아래와 같은 역할을 수행한다.
- 객체의 생성 및 관리
- ApplicationContext를 사용하여 빈(Bean)을 생성하고, 관리한다.
- 빈은 일반적으로 Spring이 제어하며, 개발자는 객체의 생성과 관리를 직접 처리하지 않는다. - 의존성 관리
- 객체 간의 의존성을 Spring이 주입(DI)한다.
- 객체가 필요로 하는 다른 객체를 직접 생성하거나 찾는 대신, Spring 컨테이너가 의존성을 주입해 준다. - 제어 흐름의 역전
- 개발자가 코드의 제어 흐름을 결정하지 않고, 프레임워크가 객체의 라이프사이클 및 실행 흐름을 관리한다.
IoC의 작동 원리
- 개발자는 객체의 생성 방식을 정의하지 않고, 필요한 객체를 요청만 한다.
- IoC 컨테이너가 객체를 생성하고 필요한 의존성을 주입한다.
- 개발자는 컨테이너가 제공하는 객체를 사용하기만 하면 된다.
의존성 주입(Dependency Injection, DI)
의존성 주입(DI)은 객체 간의 의존성을 스프링 컨테이너가 자동으로 주입하는 방식이다. 이로 인해 객체 간의 결합도가 낮아지고, 코드가 더 유연하고 테스트하기 쉬워진다. Spring은 다양한 방식으로 의존성을 주입한다.
DI의 종류를 아래에서 살펴보자.
생성자 주입(Constructor Injection)
생성자 주입은 의존성을 생성자 매개변수로 전달받는다. 아래는 예시이다.
@Component
public class Service {
private final Repository repository;
@Autowired
public Service(Repository repository) {
this.repository = repository;
}
}
세터 주입(Setter Injection)
세터 주입은 세터 메서드를 통해 의존성을 주입한다. 예시는 아래와 같다.
@Component
public class Service {
private Repository repository;
@Autowired
public void setRepository(Repository repository) {
this.repository = repository;
}
}
필드 주입(Field Injection)
필드 주입은 필드에 직접 의존성을 주입한다. 예시는 아래와 같다.
@Component
public class Service {
@Autowired
private Repository repository;
}
DI는 아래와 같은 장점을 가지고 있다.
- 결합도 감소: 객체 간의 의존성을 코드에서 직접 생성하지 않고, 컨테이너가 관리하기 때문에 결합도가 낮아진다.
- 테스트 용이성: 의존성을 쉽게 모의(Mock) 객체로 대체할 수 있다.
- 유지보수성 향상: 의존성을 외부에서 주입받기 때문에 변경 사항에 더 유연하다.
Bean, IoC, DI의 연계
스프링 IoC 컨테이너는 애플리케이션을 구성하는 Bean을 생성하고, 필요에 따라 의존성을 주입하며, 이 모든 과정을 효율적으로 관리한다. 이 세 가지가 함께 작동함으로써 다음과 같은 결과를 얻을 수 있다.
- 객체 생성 및 의존성 관리를 개발자가 아닌 컨테이너가 담당한다.
- 코드가 단순해지고, 비즈니스 로직에 더 집중할 수 있다.
- 변경 가능성이 높은 의존성을 효과적으로 관리할 수 있다.
@Service
public class MyService {
private final MyComponent myComponent;
@Autowired
public MyService(MyComponent myComponent) {
this.myComponent = myComponent;
}
public void execute() {
myComponent.doSomething();
}
}
위의 예시를 통해 Bean, IoC, DI의 연계과정을 풀어쓰면 아래와 같다.
위 코드에서 @Serivce 어노테이션이 붙어있고 이 때, 스프링이 @ComponentScan을 사용해 클래스 경로를 스캔한다. 여기서 MyService 생성자는 MyComponent라는 객체에 대해 의존성 주입(DI)이 Autowired를 통해 이루어 진다. 그리고 여기서 일어난 DI로 생성된 객체의 life cycle은 스프링이 관리하므로 IoC가 일어나고 있는 것이다.
관련 포스팅
2025.01.12 - [Languages/Java(Spring boot)] - [Spring] @Component와 @Configuration 어노테이션의 차이
참고 자료