Spring 프레임워크는 자바 애플리케이션 개발에서 가장 널리 사용되는 프레임워크 중 하나입니다. Spring의 핵심 기능 중 하나는 IoC(Inversion of Control, 제어의 역전)과 DI(Dependency Injection, 의존성 주입)입니다.
Spring에서 Bean(빈)은 IoC 컨테이너가 관리하는 객체입니다. IoC는 이러한 Bean을 생성하고 관리하는 역할을 합니다. 즉, IoC 컨테이너는 객체의 생성과 라이프사이클을 개발자가 직접 처리하지 않도록 하고, 필요할 때 Bean을 주입하여 애플리케이션을 구성할 수 있도록 합니다. Spring Boot를 사용하면 이러한 IoC 설정이 자동화되어 개발자가 최소한의 설정만으로도 빠르게 애플리케이션을 개발할 수 있습니다. IoC, Bean, DI에 대해서 알아보겠습니다.
IoC(Inversion of Control, 제어의 역전)란?
IoC 개념 및 필요성
IoC는 객체의 생성 및 관리를 개발자가 직접 하는 것이 아니라 Spring 컨테이너가 담당하도록 하여 객체 간 결합도를 줄이고, 유지보수성을 높이는 패턴입니다. Spring에서는 IoC 컨테이너를 통해 객체를 빈(Bean)으로 관리하고 필요할 때마다 주입하여 사용합니다. 이를 통해 객체간 결합도가 감소고 테스트가 수월해집니다.
Spring이 IoC라고 부르는 이유
Spring에서는 객체의 라이프사이클과 의존성 관리를 개발자가 직접 하지 않고 컨테이너가 관리하는 방식으로 동작합니다. 이는 기존의 방식에서 객체가 직접 자신의 의존성을 찾는 것에서 벗어나 외부 컨테이너가 객체를 생성하고 주입하는 방식으로 전환되었기 때문입니다. 즉, 객체의 제어 흐름이 개발자에서 Spring으로 역전(Inversion) 되었기 때문에 IoC라고 부릅니다.
Bean이란 무엇인가?
Bean의 개념과 역할
Spring에서 Bean이란, Spring IoC 컨테이너가 관리하는 객체를 의미합니다. 개발자가 직접 객체를 생성하는 것이 아니라, Spring이 객체를 생성하고, 필요할 때 주입(Injection)하는 방식을 사용합니다.
Bean의 생명주기
Bean의 생명주기는 크게 다음과 같습니다.
- 객체 생성 (
@Bean
,@Component
등의 설정에 의해 Spring 컨테이너가 객체를 생성) - 의존성 주입 (생성자 또는 세터를 통해 의존성 주입)
- 초기화 메서드 실행 (
@PostConstruct
활용 가능) - 사용(Ready State)
- 소멸(Destroy) (
@PreDestroy
활용 가능)
생명주기가 서로 다른 Bean 예시
스코프 설정을 통해 Bean마다 생명주기를 다르게 적용할 수 있습니다. Singleton을 기본값으로 가지며, Prototype, Request 등이 있다.
- Singleton(기본값) : 애플리케이션 실행 동안 단 하나의 인스턴스만 생성
- Prototype : 요청할 때마다 새로운 인스턴스를 생성
@Bean
@Scope("prototype")
public Car car() {
return new Car();
}
Spring IoC 컨테이너
Spring에서는 IoC 컨테이너로 ApplicationContext와 BeanFactory를 제공합니다.
ApplicationContext
와BeanFactory
BeanFactory
: 가장 기본적인 IoC 컨테이너입니다.ApplicationContext
:BeanFactory
를 확장한 개념으로, AOP, 이벤트 리스너, 메시지 소스 처리 등 다양한 기능을 제공합니다.
- Spring Boot에서의 자동 설정
- Spring Boot에서는
@SpringBootApplication
을 사용하면 내부적으로 IoC 컨테이너가 자동으로 설정됩니다. - 개발자가
AnnotationConfigApplicationContext
등을 직접 생성할 필요 없이, 빈을 선언하면 자동으로 관리됩니다.
- Spring Boot에서는
Bean 생성 및 관리 (Java-based 설정)
@Configuration
, @Bean
, @Component
Java 기반 설정에서 Bean을 등록하려면 @Configuration
과 @Bean
을 사용합니다. @Configuration
내부에 @Bean
을 정의하면 IoC에서 자동으로 생성 및 관리를 합니다. @Component
를 사용하면 @Configuration
에 생성하지 않더라도 Spring이 자동으로 빈을 감지하여 등록합니다.
@Configuration
public class AppConfig {
@Bean
public Car car() {
return new Car(engine());
}
@Bean
public Engine engine() {
return new Engine();
}
}
@Component
public class CarSystem {}
의존성 주입(Dependency Injection, DI)
DI 원칙을 사용하면 코드가 더 깔끔해지고, 분리가 더 효과적입니다. 결과적으로 클래스를 테스트하기가 더 쉬워지며, 특히 종속성이 인터페이스나 추상 기본 클래스에 있는 경우 단위 테스트에서 스텁이나 모의 구현을 사용할 수 있습니다.
DI는 객체 간 의존성을 Spring이 자동으로 주입해줍니다. 크게 3가지가 있으나, 필드주입은 권장하는 방법이 아니기 때문에 제외하고 생성자 주입과 세터주입을 보겠습니다.
생성자 주입
@Service
public class Car {
private final Engine engine;
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
}
가장 권장되는 방법입니다. 생성자를 통하여 필요한, 의존성을 주입받는 방법입니다.
세터 주입
@Service
public class Car {
private Engine engine;
@Autowired
public void setEngine(Engine engine) {
this.engine = engine;
}
}
Setter를 통해 의존성을 주입받는 방법입니다. 이 방법은 생성자 주입이 이루어진 후에 이루어집니다.
순환 종속성
예를 들어, 클래스 A는 생성자 주입을 통해 클래스 B의 인스턴스를 필요로 하고, 클래스 B는 생성자 주입을 통해 클래스 A의 인스턴스를 필요로 합니다. 클래스 A와 B의 빈을 서로 주입하도록 구성하면 Spring IoC 컨테이너는 런타임에 이 순환 참조를 감지하고BeanCurrentlyInCreationException
예외를 발생시킵니다. 이를 해결하는 방법 중 하나는 어떤 Bean은 세터 주입으로 구성할 수 있게 수정하는 것입니다. 다른 방법은 @Lazy어노테이션을 사용한 지연 초기화를 사용하는 방법이 있습니다.
DI 활용 예제 - 서비스 계층과 DAO 적용
Spring 애플리케이션에서는 DAO(Data Access Object) 계층과 서비스(Service) 계층을 분리하여 관리합니다. DAO는 데이터베이스와의 직접적인 상호작용을 담당하며, 서비스 계층은 비즈니스 로직을 처리합니다.
@Repository
public class UserRepository {
public String getUser() {
return "Spring User";
}
}
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public String getUserName() {
return userRepository.getUser();
}
}
이와 같이 나누면, 비즈니스 로직과 데이터 접근 로직을 분리하여 유지보수를 쉽게 할 수 있습니다.
IoC와 DI의 장점과 단점
- IoC/DI의 장점
- 유연한 코드 구조 - IoC/DI를 사용하여 결합도가 감소한다.
- Mock 객체 활용 가능 - 외부에서 주입이 가능해지면서 Mock 객체를 사용할 수 있게 되고 테스트 용이성이 증가한다.
- 코드 가독성 증가 - 객체 생성을 중앙 집중화하여 어떤 객체들을 사용하고 있는지 파악하기 쉽다.
- IoC/DI의 단점
- 학습 비용이 높음 - 초기 설정이 복잡하여 학습에 대한 부담이 있다.
- Bean관리의 복잡성 - Bean이 많아지고 관리를 잘못하면 복잡성이 증가한다.
Spring의 IoC와 DI는 객체 관리의 책임을 개발자가 아닌 Spring 프레임워크가 맡도록 함으로써 결합도를 낮추고 유지보수성을 향상시키는 핵심 개념입니다. Spring Boot를 활용하면 IoC 컨테이너 설정이 자동화되어 더욱 빠르게 개발할 수 있습니다. IoC/DI를 효과적으로 활용하면, 더 모듈화된 애플리케이션을 개발할 수 있습니다. 현재는 주니어 개발자로 어떤 객체를 빈으로 설정할지 스코프 설정이나, 생명주기에 대한 고민면서 다양하게 적용해 보고 있다. 요청마다 생성되는 빈을 사용해본다던가, 자주 사용하는 ObjectMapper를 빈으로 등록해서 사용하단던지, 프로젝트의 기본적인 설정을 상수로 사용해 등록한다던지 여러 고민을 하고 있다. 어려운 부분도 많다. 빈을 관리하는 일이 프로젝트가 작으면 어렵지 않지만 커질 수록 더 많은 빈이 생기고 무슨 역할을 하는 객체였는지 알기 어려워지는 등 복잡성이 증가한다. 이런 부분들을 고려하여 공부하고 리팩토링해보고 다양하게 적용하지만 이 모든 것이 결국 비용(유지보수도 결국 비용이다.)으로 연결되므로 경험이 부족하다는게 실감되는 때가 있다.
'개발자 공부 > JAVA' 카테고리의 다른 글
트랜잭션과 Java Spring에서의 선언적 트랜잭션 (0) | 2025.03.11 |
---|---|
스프링 시큐리티(Spring Security)를 사용한 이유와 특징 정리 (0) | 2025.03.09 |
스프링 프레임워크(Spring)을 사용하는 이유 (0) | 2025.03.07 |
자바(Java)를 선택한 이유 – 내가 공부하면서 느낀 점 (0) | 2025.03.05 |
Java, Spring Boot에서 WebSocket 테스트하기 (0) | 2025.02.27 |