Spring 스프링 빈(Bean)이란?

suover

spring

스프링 빈이란?

스프링 빈(Spring Bean)은 스프링 IoC(Inversion of Control) 컨테이너가 관리하는 자바 객체를 의미합니다. 간단히 말하면, 스프링 애플리케이션 컨텍스트(ApplicationContext) 내에서 생명주기(생성부터 소멸까지)를 관리 받는 객체를 가리킵니다.

IoC 컨테이너와 DI 개념

  • IoC(Inversion of Control): 제어의 역전이란, 객체의 생성 및 의존성 주입을 개발자가 직접 제어하지 않고 스프링 컨테이너가 대신 처리해주는 것을 말합니다.
  • DI(Dependency Injection): 의존성 주입이란, 객체가 필요로 하는 의존 객체를 외부에서 주입해주는 방식입니다. 일반적인 자바 애플리케이션에서는 의존 객체를 직접 생성하거나 setter 등을 통해 주입해주지만, 스프링에서는 컨테이너가 빈들 간의 의존관계를 분석하고 주입해줍니다.

스프링 빈의 의의

  1. 생명주기 관리: 객체 생성-초기화-사용-소멸 과정을 스프링 컨테이너가 대신 관리합니다.
  2. 의존성 간소화: 복잡한 객체 간 의존 관계를 코드에서 직접 다루지 않고, 스프링 설정을 통해 외부에서 지정할 수 있어 유지 보수가 쉬워집니다.
  3. 테스트 용이: 코드가 강하게 결합되지 않으므로, 단위 테스트나 통합 테스트에서 목(Mocks)이나 스텁(Stub)을 쉽게 주입할 수 있습니다.

스프링 빈 등록 방법

스프링 빈을 등록하는 방법은 여러 가지가 있지만, 주로 다음과 같이 크게 세 가지로 분류할 수 있습니다.

  1. XML 설정 파일을 사용하는 방법
  2. Java Config(자바 설정) 클래스를 사용하는 방법
  3. 컴포넌트 스캔(Component Scan)을 사용하는 방법 (@Component, @Service, @Repository, @Controller 등)

아래에서 각 방식을 간단한 예시와 함께 살펴보겠습니다.

XML 설정 파일 사용

XML
<!-- applicationContext.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="exampleBean" class="com.example.ExampleBean">
        <!-- 생성자 주입, setter 주입 등 의존관계 설정 -->
    </bean>

</beans>
XML

  • applicationContext.xml 또는 원하는 XML 설정 파일 안에 <bean> 태그로 빈을 등록할 수 있습니다.
  • id는 빈을 식별하기 위한 이름이고, class는 해당 빈을 생성할 때 사용할 클래스의 이름을 나타냅니다.

자바 설정(Java Config) 사용

Java
@Configuration
public class AppConfig {

    @Bean
    public ExampleBean exampleBean() {
        return new ExampleBean();
    }

}
Java

  • @Configuration이 붙은 클래스를 하나의 설정 클래스로 이용합니다.
  • @Bean 어노테이션이 있는 메서드를 스프링이 실행하여 그 리턴 값을 컨테이너에 빈으로 등록해줍니다.
  • XML 방식에 비해 타입 안전성이 높고, IDE 자동 완성 기능의 도움을 받기 쉽다는 장점이 있습니다.

컴포넌트 스캔(Component Scan) 사용

Java
@Component
public class ExampleBean {
    // ...
}
Java

Java
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
    // ...
}
Java

  • @Component, @Service, @Controller, @Repository 등의 어노테이션이 붙은 클래스를 스캔해서 빈으로 자동 등록합니다.
  • @Component는 일반적으로 사용되며, 역할에 따라 세분화된 어노테이션(@Service, @Controller, @Repository)을 사용할 수 있습니다.
  • @Configuration이 붙은 설정 클래스에서 @ComponentScan 어노테이션을 사용해 특정 패키지를 스캔 범위로 지정합니다.

스프링 빈 생명주기(Lifecycle)

스프링 빈은 생성부터 소멸까지의 모든 과정을 스프링 컨테이너가 제어합니다. 이를 빈 생명주기(Bean Lifecycle)라고 하는데, 주된 과정은 아래와 같습니다.

  1. 빈 객체 생성
  2. 의존성 주입(Dependency Injection)
  3. 초기화(InitializingBean, @PostConstruct 등)
  4. 빈 사용 단계
  5. 소멸 전 콜백(DisposableBean, @PreDestroy 등)
  6. 빈 소멸

스프링 프레임워크는 위 과정을 자동으로 처리하며, 사용자가 초기화나 소멸 과정을 커스터마이징하고 싶으면 다양한 방법을 제공합니다.

초기화/소멸 메서드 지정하는 방법

1. @PostConstruct / @PreDestroy

      • JSR-250 표준 어노테이션을 사용하여 초기화와 소멸 전 콜백을 지정합니다.
      • 클래스 내 메서드 위에 선언합니다.
      Java
      @Component
      public class ExampleBean {
          
          @PostConstruct
          public void init() {
              // 초기화 로직
          }
          
          @PreDestroy
          public void destroy() {
              // 자원 정리 로직
          }
      }
      Java

      2. InitializingBean / DisposableBean 인터페이스

      • 스프링 프레임워크에서 제공하는 인터페이스를 구현하는 방법입니다.
      • 다만, 스프링에 종속적인 방법이므로 가능한 한 @PostConstruct, @PreDestroy 사용을 권장합니다.

      3. XML / Java Config에서 메서드 직접 지정

      • XML에서 <bean> 태그에 init-method, destroy-method 속성을 사용하거나, 자바 설정 클래스에서 @Bean(initMethod = “init”, destroyMethod = “destroy”)처럼 지정할 수도 있습니다.

      스프링 빈 스코프(Bean Scope)

      스프링은 빈이 어떻게 생성되고 공유될지 결정하는 스코프(scope) 개념을 제공합니다. 대표적인 스코프는 다음과 같습니다.

      1. Singleton
        • 스프링 컨테이너 당 하나의 인스턴스만 생성해서 공유합니다.
        • 기본 스코프이며, 대부분의 빈이 싱글톤으로 사용됩니다.
      2. Prototype
        • 요청할 때마다 새로운 인스턴스를 생성합니다.
        • 매번 다른 객체가 필요할 때 사용하지만, 빈을 재사용하지 않기 때문에 리소스 낭비와 생명주기 관리를 주의해야 합니다.
      3. Request
        • 웹 애플리케이션에서 HTTP 요청이 들어올 때마다 새로운 빈을 생성하고, 요청이 끝나면 소멸됩니다.
        • 스프링 MVC나 웹 환경에서만 사용 가능합니다.
      4. Session
        • HTTP 세션과 동일한 생명주기를 가집니다. 세션이 유지되는 동안 한 개의 빈을 공유하고, 세션이 끝나면 소멸됩니다.
      5. Application
        • 서블릿 컨텍스트(ServletContext)와 같은 범위의 생명주기를 갖습니다.
        • 거의 한 애플리케이션 전역적으로 공유가 필요한 경우 사용됩니다.

      스코프 설정은 @Scope 어노테이션으로 지정하며, XML 설정에서도 <bean scope=”singleton”>, <bean scope=”prototype”>와 같이 선언할 수 있습니다.

      의존성 주입 방식

      스프링 빈 간의 의존 관계를 설정하는 대표적인 방법은 아래와 같습니다.

      생성자 주입(Constructor Injection)

      • 의존성을 생성자를 통해 주입합니다.
      • 가장 권장되는 방식이며, final 필드를 활용하면 불변성을 보장하고, 테스트 용이성이 높습니다.
      Java
      @Service
      public class OrderService {
      
          private final ProductRepository productRepository;
      
          @Autowired
          public OrderService(ProductRepository productRepository) {
              this.productRepository = productRepository;
          }
      }
      Java

      Setter 주입(Setter Injection)

      • 세터 메서드를 통해 의존성을 주입합니다.
      • 순환 참조가 복잡한 경우나, 일부 필드만 필요에 따라 주입할 때 사용하기도 합니다.
      Java
      @Service
      public class OrderService {
          
          private ProductRepository productRepository;
      
          @Autowired
          public void setProductRepository(ProductRepository productRepository) {
              this.productRepository = productRepository;
          }
      }
      Java

      필드 주입(Field Injection)

      • @Autowired를 필드에 직접 선언하여 의존성을 주입합니다.
      • 테스트나 무분별한 사용 시 구조가 난해해질 수 있어 최근에는 자제하는 추세입니다. 주로 간단한 테스트용 코드나 프로토타이핑 시에만 일시적으로 사용을 권장합니다.

      예시 코드로 알아보기

      다음 코드는 Java Config와 컴포넌트 스캔 방식을 섞어 사용하는 예시입니다.

      빈 클래스 예시

      Java
      package com.example.service;
      
      import org.springframework.stereotype.Service;
      
      @Service
      public class PaymentService {
      
          public void pay() {
              System.out.println("[PaymentService] 결제가 정상 처리되었습니다.");
          }
      }
      Java

      • @Service가 붙어 있기 때문에 스프링이 컴포넌트 스캔을 통해 해당 클래스를 빈으로 등록하게 됩니다.

      의존성을 주입받는 빈

      Java
      package com.example.service;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      
      @Service
      public class OrderService {
      
          private final PaymentService paymentService;
      
          // 생성자 주입 방식 사용
          @Autowired
          public OrderService(PaymentService paymentService) {
              this.paymentService = paymentService;
          }
      
          public void order() {
              System.out.println("[OrderService] 주문 요청을 받았습니다.");
              paymentService.pay();
              System.out.println("[OrderService] 주문 처리가 완료되었습니다.");
          }
      }
      Java

      • OrderService에서 PaymentService를 주입받습니다.
      • @Autowired 생성자 주입을 통해 DI를 수행합니다.

      설정 클래스(AppConfig)

      Java
      package com.example.config;
      
      import org.springframework.context.annotation.ComponentScan;
      import org.springframework.context.annotation.Configuration;
      
      @Configuration
      @ComponentScan(basePackages = "com.example")
      public class AppConfig {
          // 컴포넌트 스캔을 통해 @Service, @Component 등을 자동 등록
      }
      Java

      • @Configuration이 붙은 설정 클래스에서 @ComponentScan을 통해 com.example 패키지 이하를 스캔합니다.
      • 설정 파일에 별도의 @Bean 메서드를 작성하지 않아도, @Service 등이 붙은 모든 클래스를 자동으로 빈 등록하게 됩니다.

      메인 실행 예시

      Java
      package com.example;
      
      import com.example.config.AppConfig;
      import com.example.service.OrderService;
      import org.springframework.context.annotation.AnnotationConfigApplicationContext;
      
      public class SpringBeanExample {
          public static void main(String[] args) {
              // 스프링 컨테이너 생성
              AnnotationConfigApplicationContext context =
                      new AnnotationConfigApplicationContext(AppConfig.class);
      
              // 필요한 빈을 가져와서 메서드 호출
              OrderService orderService = context.getBean(OrderService.class);
              orderService.order();
      
              // 컨테이너 종료
              context.close();
          }
      }
      Java

      • AnnotationConfigApplicationContext를 이용해 스프링 컨테이너를 생성합니다.
      • @ComponentScan 설정에 따라 OrderService, PaymentService 빈이 자동 등록됩니다.
      • 스프링 빈인 OrderService를 가져와서 실제 애플리케이션 로직(order())을 실행합니다.

      실행 결과 (콘솔 출력 예시)

      Markdown
      [OrderService] 주문 요청을 받았습니다.
      [PaymentService] 결제가 정상 처리되었습니다.
      [OrderService] 주문 처리가 완료되었습니다.
      Markdown

      정리

      스프링 빈은 스프링 프레임워크의 근간이라 할 수 있는 개념입니다.

      • IoC(제어의 역전)DI(의존성 주입) 개념을 기반으로 하며,
      • 다양한 방법(XML, Java Config, 컴포넌트 스캔)으로 빈을 정의하고,
      • 스프링 컨테이너가 빈의 생명주기를 관리하며,
      • 스코프를 통해 빈이 생성되고 사용되는 범위를 제어할 수 있습니다.

      스프링 애플리케이션을 개발할 때, 빈을 어떻게 관리하고 의존성을 어떻게 주입할지 설계하는 것은 매우 중요한 일입니다. 설계 단계에서부터 어떤 로직이 Singleton으로 관리될지, Prototype으로 새로 생성돼야 하는지 등을 잘 따져봐야 합니다. 또한, 생성자 주입, 필드 주입, Setter 주입의 특성을 잘 이해하고 상황에 맞게 사용하는 것도 중요합니다.

      정리하자면, 올바르게 설계된 스프링 빈 구조는 애플리케이션의 확장성과 유지보수성을 높이고, 단위 테스트나 통합 테스트를 수행할 때 훨씬 편리한 환경을 제공해줍니다.

      스프링 빈에 대한 정확하고 확실한 이해가 곧 스프링 프레임워크를 능숙하게 다루는 지름길이며, 더 나아가서는 마이크로서비스 아키텍처(MSA)나 클라우드 환경에서도 안정적으로 확장 가능한 애플리케이션을 만드는 발판이 됩니다.

      Leave a Comment