JAVA/Spring

Spring 컨테이너(ApplicationContext)를 사용해야 하는 이유

min민 2024. 12. 16.

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");
    }
}

 

위와 같은 방식은 CarEngine 객체에 직접 의존하기 때문에 강한 결합을 가지게 된다. 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 컨테이너를 사용해 애플리케이션을 더 유연하고 효율적으로 관리해보자.

댓글