본문 바로가기
이직&취업/Java 기초 상식

Java Spring Bean의 생명주기(Lifecycle)

by journeylabs 2025. 3. 24.
728x90
반응형

목차

    Java Spring Bean의 생명주기(Lifecycle)

    Spring 프레임워크는 강력한 IoC(Inversion of Control) 컨테이너를 제공하며, 이 컨테이너에서 관리되는 핵심 컴포넌트가 바로 Bean입니다. Bean은 Spring 컨테이너에 의해 생성, 조립, 사용, 소멸되는 객체를 의미하며, Spring Bean의 생명주기를 이해하는 것은 Spring 기반 애플리케이션 개발의 필수적인 부분입니다. 본 블로그에서는 Spring Bean의 생명주기를 심층적으로 파헤쳐, 각 단계별 상세 설명, 주요 개념, 예제, 주의사항 등을 통해 독자 여러분의 이해를 돕고자 합니다.

    1. 용어 상세 설명

    • Bean: Spring IoC 컨테이너에 의해 관리되는 객체. Spring 설정 파일(XML, Annotation, Java Config)에 정의되어 컨테이너에 의해 생성, 주입, 관리됩니다.
    • IoC (Inversion of Control): 제어의 역전. 객체의 생성 및 의존성 관리를 개발자가 직접 수행하는 것이 아니라, 컨테이너에 위임하는 디자인 패턴입니다.
    • DI (Dependency Injection): 의존성 주입. 객체 간의 의존 관계를 컨테이너가 설정 파일을 기반으로 자동으로 연결해주는 방식입니다.
    • BeanFactory: Spring IoC 컨테이너의 최상위 인터페이스. Bean을 생성하고 관리하는 기본적인 기능을 제공합니다.
    • ApplicationContext: BeanFactory 인터페이스를 확장한 인터페이스. BeanFactory의 모든 기능을 포함하며, AOP, 메시지 처리, 이벤트 발행 등 엔터프라이즈 애플리케이션 개발에 필요한 추가 기능을 제공합니다.
    • Bean Definition: Bean을 생성하기 위한 설정 정보. Bean의 클래스, 스코프, 초기화/소멸 메서드 등을 정의합니다.
    • BeanPostProcessor: Bean 생성 전/후에 특정 작업을 수행할 수 있도록 하는 인터페이스. Bean의 초기화 과정에 개입하여 Bean을 변경하거나 추가적인 기능을 제공할 수 있습니다.
    • FactoryBean: Bean 자체가 아닌, Bean을 생성하는 객체를 정의하는 인터페이스. 복잡한 Bean 생성 로직을 캡슐화할 때 유용합니다.

    2. 주요 개념 및 특징

    • Spring 컨테이너: Bean의 생명주기를 관리하는 핵심 주체. Bean을 생성, 의존성 주입, 초기화, 사용, 소멸하는 모든 과정을 담당합니다.
    • Bean 스코프: Bean의 인스턴스 범위를 정의합니다. Spring은 다음과 같은 스코프를 제공합니다.
      • singleton: 컨테이너당 하나의 인스턴스만 생성 (기본값)
      • prototype: 매 요청마다 새로운 인스턴스 생성
      • request: HTTP 요청당 하나의 인스턴스 생성 (Web 환경)
      • session: HTTP 세션당 하나의 인스턴스 생성 (Web 환경)
      • application: 웹 애플리케이션 컨텍스트 당 하나의 인스턴스 생성 (Web 환경)
      • websocket: WebSocket 세션당 하나의 인스턴스 생성 (Web 환경)
    • 콜백 메서드: Bean의 특정 시점에 실행되는 메서드. 초기화 콜백(afterPropertiesSet, @PostConstruct), 소멸 콜백(@PreDestroy, destroy)이 있습니다.
    • BeanPostProcessor: Bean 생성 전후에 공통적인 작업을 수행하는 데 사용됩니다. 예를 들어, AOP 프록시를 생성하거나, Bean의 유효성을 검사하는 데 사용될 수 있습니다.

    3. 생명주기 흐름 및 설명

    Spring Bean의 일반적인 생명주기는 다음과 같습니다.

    1. Bean Definition 로딩: Spring 컨테이너는 설정 파일(XML, Annotation, Java Config)을 읽어 Bean Definition을 로딩합니다. Bean Definition은 Bean을 생성하기 위한 메타데이터를 담고 있습니다.
    2. Bean 인스턴스화: 컨테이너는 Bean Definition을 기반으로 Bean 인스턴스를 생성합니다. new 연산자를 사용하거나, Factory Method, FactoryBean 등을 이용하여 객체를 생성합니다.
    3. 의존성 주입 (Dependency Injection): 컨테이너는 Bean이 필요로 하는 의존성을 주입합니다. Constructor Injection, Setter Injection, Field Injection 등의 방식을 사용할 수 있습니다.
    4. BeanPostProcessor (pre-initialization): 컨테이너는 BeanPostProcessor의 postProcessBeforeInitialization 메서드를 호출합니다. 이를 통해 Bean 초기화 전에 필요한 작업을 수행할 수 있습니다. 예를 들어, AOP 프록시를 생성하거나, Bean의 값을 변경할 수 있습니다.
    5. InitializingBean 인터페이스 구현 또는 @PostConstruct 어노테이션 사용:
      • InitializingBean: Bean 클래스가 InitializingBean 인터페이스를 구현한 경우, afterPropertiesSet() 메서드가 호출됩니다.
      • @PostConstruct: Bean 클래스에 @PostConstruct 어노테이션이 붙은 메서드가 있는 경우, 해당 메서드가 호출됩니다.
        이 단계에서 Bean은 필요한 초기화 작업을 수행합니다. 예를 들어, 데이터베이스 연결을 설정하거나, 캐시를 초기화할 수 있습니다.
    6. BeanPostProcessor (post-initialization): 컨테이너는 BeanPostProcessor의 postProcessAfterInitialization 메서드를 호출합니다. 이를 통해 Bean 초기화 후에 필요한 작업을 수행할 수 있습니다. 예를 들어, AOP 프록시를 적용하거나, Bean을 컨테이너에 등록할 수 있습니다.
    7. Bean 사용: Bean이 컨테이너에 의해 관리되면서 애플리케이션에서 사용됩니다.
    8. DisposableBean 인터페이스 구현 또는 @PreDestroy 어노테이션 사용:
      • DisposableBean: Bean 클래스가 DisposableBean 인터페이스를 구현한 경우, destroy() 메서드가 호출됩니다.
      • @PreDestroy: Bean 클래스에 @PreDestroy 어노테이션이 붙은 메서드가 있는 경우, 해당 메서드가 호출됩니다.
        컨테이너가 종료되거나 Bean이 더 이상 필요하지 않게 되면, 소멸 단계가 시작됩니다. 이 단계에서 Bean은 사용했던 자원을 해제하거나, 필요한 정리 작업을 수행합니다.
    9. Bean 소멸: 컨테이너는 Bean 인스턴스를 메모리에서 제거합니다.

    요약:

          Bean Definition 로딩 -> Bean 인스턴스화 -> 의존성 주입 
          -> BeanPostProcessor (pre-initialization) -> InitializingBean/ @PostConstruct 
          -> BeanPostProcessor (post-initialization) -> Bean 사용 -> DisposableBean/ @PreDestroy 
          -> Bean 소멸

    4. 예제 및 예제 상세 설명

    4.1. XML 설정 방식

     

    public class XmlBean {
        private String message;
    
        public XmlBean() {
            System.out.println("XmlBean Constructor");
        }
    
        public void setMessage(String message) {
            System.out.println("Setting message: " + message);
            this.message = message;
        }
    
        public void init() {
            System.out.println("XmlBean init method");
        }
    
        public void destroy() {
            System.out.println("XmlBean destroy method");
        }
    
        public void printMessage() {
            System.out.println("Message: " + message);
        }
    }

     

    XML 설정:

    <bean id="xmlBean" class="com.example.XmlBean" init-method="init" destroy-method="destroy">
        <property name="message" value="Hello from XML!"/>
    </bean>
    • class: Bean으로 등록할 클래스를 지정합니다.
    • init-method: Bean 초기화 시 실행할 메서드를 지정합니다. init() 메서드가 호출됩니다.
    • destroy-method: Bean 소멸 시 실행할 메서드를 지정합니다. destroy() 메서드가 호출됩니다.
    • <property>: Bean의 속성 값을 설정합니다. setMessage() 메서드가 호출되어 message 속성에 "Hello from XML!"이 할당됩니다.

    실행 코드:

    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    XmlBean xmlBean = context.getBean("xmlBean", XmlBean.class);
    xmlBean.printMessage();
    context.close();

     

    출력 결과:

    XmlBean Constructor
    Setting message: Hello from XML!
    XmlBean init method
    Message: Hello from XML!
    XmlBean destroy method
    1. Spring 컨테이너는 applicationContext.xml 파일을 읽어 XmlBean에 대한 Bean 정의를 로드합니다.
    2. XmlBean의 생성자가 호출되어 인스턴스가 생성됩니다.
    3. <property> 태그에 의해 setMessage() 메서드가 호출되어 message 속성 값이 설정됩니다.
    4. init-method에 지정된 init() 메서드가 호출됩니다.
    5. xmlBean.printMessage()를 호출하여 message 속성 값을 출력합니다.
    6. context.close()를 호출하면 컨테이너가 종료되면서 destroy-method에 지정된 destroy() 메서드가 호출됩니다.

    4.2. Annotation 기반 설정 방식 (@PostConstruct, @PreDestroy)

    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    
    public class AnnotationBean {
        private String message;
    
        public AnnotationBean() {
            System.out.println("AnnotationBean Constructor");
        }
    
        public void setMessage(String message) {
            System.out.println("Setting message: " + message);
            this.message = message;
        }
    
        @PostConstruct
        public void init() {
            System.out.println("AnnotationBean @PostConstruct init method");
        }
    
        @PreDestroy
        public void destroy() {
            System.out.println("AnnotationBean @PreDestroy destroy method");
        }
    
        public void printMessage() {
            System.out.println("Message: " + message);
        }
    }

     

    XML 설정 (Component Scanning):

    <context:component-scan base-package="com.example"/>

     

    Java Config 설정:

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class AppConfig {
      @Bean
      public AnnotationBean annotationBean() {
        AnnotationBean bean = new AnnotationBean();
        bean.setMessage("Hello from Annotation!");
        return bean;
      }
    }
    • @PostConstruct: Bean이 의존성 주입을 완료한 후 초기화 작업을 수행할 메서드에 붙입니다.
    • @PreDestroy: Bean이 소멸되기 전에 정리 작업을 수행할 메서드에 붙입니다.
    • <context:component-scan> (XML): 지정된 패키지에서 @Component 어노테이션이 붙은 클래스를 찾아 Bean으로 등록합니다. @PostConstruct @PreDestroy 어노테이션을 사용하려면 <context:annotation-config/> 또는 <context:component-scan/>을 설정 파일에 추가해야 합니다.
    • @Bean (Java Config): @Configuration 클래스 내에서 메서드에 @Bean 어노테이션을 붙여 Bean을 정의합니다.

    실행 코드:

    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    AnnotationBean annotationBean = context.getBean("annotationBean", AnnotationBean.class);
    annotationBean.printMessage();
    context.close();

     

    출력 결과:

    AnnotationBean Constructor
    Setting message: Hello from Annotation!
    AnnotationBean @PostConstruct init method
    Message: Hello from Annotation!
    AnnotationBean @PreDestroy destroy method
    1. Spring 컨테이너는 applicationContext.xml 파일을 읽고 <context:component-scan> 설정을 통해 com.example 패키지를 스캔하여 @Component 어노테이션이 붙은 클래스를 찾아 Bean으로 등록합니다. 또는 @Bean 어노테이션으로 등록합니다.
    2. AnnotationBean의 생성자가 호출되어 인스턴스가 생성됩니다.
    3. setMessage() 메서드가 호출되어 message 속성 값이 설정됩니다.
    4. @PostConstruct 어노테이션이 붙은 init() 메서드가 호출됩니다.
    5. annotationBean.printMessage()를 호출하여 message 속성 값을 출력합니다.
    6. context.close()를 호출하면 컨테이너가 종료되면서 @PreDestroy 어노테이션이 붙은 destroy() 메서드가 호출됩니다.

    4.3. InitializingBean, DisposableBean 인터페이스 구현 방식

    import org.springframework.beans.factory.DisposableBean;
    import org.springframework.beans.factory.InitializingBean;
    
    public class InterfaceBean implements InitializingBean, DisposableBean {
        private String message;
    
        public InterfaceBean() {
            System.out.println("InterfaceBean Constructor");
        }
    
        public void setMessage(String message) {
            System.out.println("Setting message: " + message);
            this.message = message;
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("InterfaceBean InitializingBean.afterPropertiesSet() method");
        }
    
        @Override
        public void destroy() throws Exception {
            System.out.println("InterfaceBean DisposableBean.destroy() method");
        }
    
        public void printMessage() {
            System.out.println("Message: " + message);
        }
    }

     

    XML 설정 (Bean 정의):

    <bean id="interfaceBean" class="com.example.InterfaceBean">
        <property name="message" value="Hello from Interface!"/>
    </bean>

     

    Java Config 설정:

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class AppConfig {
      @Bean
      public InterfaceBean interfaceBean() {
        InterfaceBean bean = new InterfaceBean();
        bean.setMessage("Hello from Interface!");
        return bean;
      }
    }
    • InitializingBean: Bean 생성 후 속성 설정이 완료되면 afterPropertiesSet() 메서드가 자동으로 호출됩니다.
    • DisposableBean: Bean이 소멸되기 전에 destroy() 메서드가 자동으로 호출됩니다.

    실행 코드:

    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    InterfaceBean interfaceBean = context.getBean("interfaceBean", InterfaceBean.class);
    interfaceBean.printMessage();
    context.close();

     

    출력 결과:

    InterfaceBean Constructor
    Setting message: Hello from Interface!
    InterfaceBean InitializingBean.afterPropertiesSet() method
    Message: Hello from Interface!
    InterfaceBean DisposableBean.destroy() method
    1. Spring 컨테이너는 applicationContext.xml 파일을 읽어 InterfaceBean에 대한 Bean 정의를 로드합니다.
    2. InterfaceBean의 생성자가 호출되어 인스턴스가 생성됩니다.
    3. <property> 태그에 의해 setMessage() 메서드가 호출되어 message 속성 값이 설정됩니다.
    4. InitializingBean 인터페이스를 구현했으므로 afterPropertiesSet() 메서드가 호출됩니다.
    5. interfaceBean.printMessage()를 호출하여 message 속성 값을 출력합니다.
    6. context.close()를 호출하면 컨테이너가 종료되면서 DisposableBean 인터페이스를 구현했으므로 destroy() 메서드가 호출됩니다.

    4.4. BeanPostProcessor 활용

    예제 코드 (MyBeanPostProcessor):

    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    import org.springframework.stereotype.Component;
    
    @Component
    public class MyBeanPostProcessor implements BeanPostProcessor {
    
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            System.out.println("BeanPostProcessor - Before Initialization: " + beanName);
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            System.out.println("BeanPostProcessor - After Initialization: " + beanName);
            return bean;
        }
    }

     

    적용 대상 Bean (예시):

    import org.springframework.stereotype.Component;
    
    @Component
    public class TargetBean {
      public TargetBean() {
        System.out.println("TargetBean Constructor");
      }
    }

     

    XML 설정 (Component Scanning):

    <context:component-scan base-package="com.example"/>

     

    또는 Java Config:

    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ComponentScan("com.example")
    public class AppConfig {
    }
    • BeanPostProcessor 인터페이스를 구현하여 Bean 생성 전후에 공통 작업을 수행합니다.
    • postProcessBeforeInitialization(): Bean 초기화 전(InitializingBean#afterPropertiesSet() 또는 @PostConstruct 호출 전)에 호출됩니다.
    • postProcessAfterInitialization(): Bean 초기화 후(InitializingBean#afterPropertiesSet() 또는 @PostConstruct 호출 후)에 호출됩니다.
    • 모든 Bean에 적용되므로, beanName을 사용하여 특정 Bean에만 적용하도록 조건을 설정할 수 있습니다.

    실행 코드:

    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    TargetBean targetBean = context.getBean("targetBean", TargetBean.class);
    context.close();

     

    출력 결과:

    TargetBean Constructor
    BeanPostProcessor - Before Initialization: targetBean
    BeanPostProcessor - After Initialization: targetBean
    1. Spring 컨테이너는 applicationContext.xml 파일을 읽고 <context:component-scan> 설정을 통해 com.example 패키지를 스캔하여 @Component 어노테이션이 붙은 클래스를 찾아 Bean으로 등록합니다.
    2. MyBeanPostProcessor는 컨테이너 내의 모든 Bean에 적용됩니다.
    3. TargetBean의 생성자가 호출되어 인스턴스가 생성됩니다.
    4. BeanPostProcessor postProcessBeforeInitialization() 메서드가 호출됩니다.
    5. BeanPostProcessor postProcessAfterInitialization() 메서드가 호출됩니다.
    6. context.close()를 호출하면 컨테이너가 종료됩니다. (TargetBean은 DisposableBean 구현 또는 @PreDestroy 어노테이션이 없으므로 별도의 소멸 단계는 실행되지 않습니다.)

    5. 주의사항

    • 초기화 및 소멸 콜백 메서드 중복 정의: @PostConstruct, InitializingBean, init-method를 Bean에 모두 정의하면, 모든 메서드가 순서대로 호출됩니다. 필요에 따라 적절한 방법을 선택하여 사용해야 합니다.
    • Bean 스코프: Bean 스코프를 잘못 설정하면 예상치 못한 문제가 발생할 수 있습니다. 예를 들어, prototype 스코프의 Bean에 의존성을 주입하면 매번 새로운 인스턴스가 생성되므로, 싱글톤 Bean에서 상태를 유지하는 데 문제가 발생할 수 있습니다.
    • 순환 참조: Bean 간에 순환 참조가 발생하면 Spring 컨테이너가 Bean을 생성하는 데 실패할 수 있습니다. Constructor Injection보다는 Setter Injection을 사용하여 순환 참조 문제를 해결할 수 있습니다.
    • BeanPostProcessor: BeanPostProcessor는 컨테이너의 모든 Bean에 영향을 미치므로, 신중하게 사용해야 합니다. 불필요한 작업을 수행하면 컨테이너의 성능을 저하시킬 수 있습니다.
    • @Autowired 시점: @Autowired를 사용하는 경우, 의존성 주입이 Bean 생성 시점에 이루어지므로, @PostConstruct 또는 afterPropertiesSet 메서드에서 주입된 의존성을 사용하는 것이 안전합니다. 생성자 주입을 사용하는 경우, Bean 생성 시점에 의존성이 주입되므로 바로 사용할 수 있습니다.

    Spring Bean의 생명주기를 깊이 있게 이해하는 것은 Spring 기반 애플리케이션을 효과적으로 개발하고 유지보수하는 데 필수적입니다. 본 블로그에서 다룬 내용을 통해 독자 여러분이 Spring Bean의 생명주기를 완벽하게 이해하고, 실제 개발 현장에서 이를 활용할 수 있기를 바랍니다. Spring Bean의 생명주기를 숙지하고 적절하게 활용하면, 더욱 견고하고 효율적인 애플리케이션을 구축할 수 있습니다.

    728x90
    반응형