Spring 컨테이너(ApplicationContext)를 사용해야 하는 이유
Spring 프레임워크를 사용하면서 ApplicationContext와 같은 컨테이너를 적용하면 처음에는 코드가 복잡해진다고 느낄 수 있다. 하지만 장기적으로 보면 Spring 컨테이너의 도입은 코드의 유지보수와 확장성에 큰 이점을 제공한다. 이번 글에서는 Spring 컨테이너를 사용했을 때 얻을 수 있는 장점과 함께 간단한 예제를 통해 그 필요성을 살펴보겠다.
1. Spring 컨테이너(ApplicationContext)란 무엇인가?
Spring 컨테이너는 애플리케이션의 객체를 생성하고 관리하는 핵심 역할을 한다. Spring에서는 IoC(Inversion of Control) 원칙을 기반으로 객체의 생성 및 의존성 주입을 담당한다. ApplicationContext는 Spring에서 제공하는 주요 컨테이너 중 하나로, 다양한 부가기능을 지원한다.
- Bean의 생성과 관리: 개발자가 직접 객체를 생성하는 대신, Spring이 객체(Bean)를 생성하고 생명주기를 관리한다.
- Dependency Injection: 객체 간의 의존성을 외부에서 주입받을 수 있게 한다.
- 부가 기능: AOP(관점 지향 프로그래밍), 이벤트 리스너, 메시지 소스 등 다양한 기능을 제공한다.
2. Spring 컨테이너를 사용하면 어떤 장점이 있을까?
1) 객체 간의 결합도를 낮춘다
Spring은 객체 간의 의존성을 주입(DI: Dependency Injection)하여 코드의 결합도를 낮춘다. 결합도가 낮아지면 객체를 더 쉽게 교체하거나 테스트할 수 있다.
예시: 의존성 주입 없이 객체 생성
아래는 일반적으로 new 키워드를 사용해 객체를 직접 생성하는 코드이다.
public class Car {
private Engine engine;
public Car() {
this.engine = new Engine();
}
public void start() {
engine.run();
}
}
public class Engine {
public void run() {
System.out.println("Engine is running");
}
}
위와 같은 방식은 Car가 Engine 객체에 직접 의존하기 때문에 강한 결합을 가지게 된다. Engine 클래스를 변경하거나 다른 엔진으로 교체하려면 Car 클래스도 수정해야 한다.
예시: Spring 컨테이너를 이용한 의존성 주입
Spring 컨테이너를 사용하면 의존성을 외부에서 주입받아 결합도를 낮출 수 있다.
@Component
public class Engine {
public void run() {
System.out.println("Engine is running");
}
}
@Component
public class Car {
private final Engine engine;
@Autowired
public Car(Engine engine) { // 의존성 주입
this.engine = engine;
}
public void start() {
engine.run();
}
}
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {}
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Car car = context.getBean(Car.class);
car.start();
}
}
위 코드에서는 Car 클래스가 Engine 객체를 직접 생성하지 않는다. 대신, **Spring 컨테이너(ApplicationContext)**가 Engine 객체를 생성하고 Car에 주입해준다. 이렇게 하면 Engine 클래스를 다른 구현체로 쉽게 변경할 수 있고, Car 클래스는 수정할 필요가 없다.
2) 객체의 생명주기를 관리한다
Spring 컨테이너는 객체의 생성부터 소멸까지 생명주기를 관리한다. 개발자는 생명주기 관리를 신경 쓰지 않아도 된다.
- 싱글톤 스코프: 기본적으로 Bean은 싱글톤으로 관리된다.
- 스코프 설정: 필요에 따라 프로토타입, 요청 스코프 등을 사용할 수 있다.
@Component
@Scope("prototype")
public class PrototypeBean {
public PrototypeBean() {
System.out.println("Prototype Bean Created");
}
}
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
PrototypeBean bean1 = context.getBean(PrototypeBean.class);
PrototypeBean bean2 = context.getBean(PrototypeBean.class);
System.out.println(bean1 == bean2); // false
}
}
위 예제에서 PrototypeBean은 매번 새로운 객체를 생성한다. Spring 컨테이너 덕분에 객체의 스코프를 자유롭게 관리할 수 있다.
3) 테스트와 유지보수가 쉬워진다
Spring 컨테이너를 사용하면 의존성을 외부에서 주입받을 수 있기 때문에 단위 테스트를 쉽게 작성할 수 있다. Mock 객체를 주입해 테스트 환경을 쉽게 구성할 수 있다.
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppConfig.class)
public class CarTest {
@MockBean
private Engine engine;
@Autowired
private Car car;
@Test
public void testCarStart() {
Mockito.doNothing().when(engine).run();
car.start();
Mockito.verify(engine, times(1)).run();
}
}
위 예제에서는 MockBean을 통해 Engine 객체의 가짜 구현체를 주입받았다. Car 클래스의 로직만 테스트할 수 있어 단위 테스트가 간단해진다.
4) 추가 기능을 쉽게 사용할 수 있다
Spring 컨테이너는 객체 생성과 관리뿐만 아니라 다양한 추가 기능을 제공한다.
- AOP(Aspect-Oriented Programming): 로깅, 트랜잭션 관리와 같은 공통 기능을 모듈화할 수 있다.
- 이벤트 리스너: 애플리케이션 이벤트를 처리할 수 있다.
- 국제화: 다국어 지원을 위한 메시지 소스를 제공한다.
총 정리
Spring 컨테이너(ApplicationContext)를 도입하면 객체의 생성과 의존성 주입을 Spring이 대신 관리해준다. 코드가 처음에는 복잡해 보일 수 있지만, 결합도가 낮아지고 유지보수성이 높아진다. 또한 객체의 생명주기 관리, AOP, 이벤트 리스너와 같은 다양한 기능을 쉽게 사용할 수 있다.
이제 더 이상 new 키워드로 직접 객체를 생성할 필요가 없다. Spring 컨테이너를 사용해 애플리케이션을 더 유연하고 효율적으로 관리해보자.
'JAVA > Spring' 카테고리의 다른 글
스프링 빈 조회: 부모 타입과 Object 타입의 차이점 (0) | 2024.12.16 |
---|---|
JAVA SPRING 테스트 코드 작성: JUnit 5에서는 public을 생략해야된다고? 심지어 권장?! (0) | 2024.12.16 |
AppConfig: 어셈블러이자 오브젝트 팩토리로 불리는 이유 (1) | 2024.12.16 |
ASSERTTHAT 메서드 사용법 (0) | 2024.12.14 |
테스트 코드의 중요성과 작성 방법에 대한 심층적인 이해 (1) | 2024.12.14 |
댓글