Backend/Spring

스프링 빈(Bean)

keepbang 2021. 8. 20. 16:27

빈이란?

  • 자바 객체를 말하며 스프링에서는 IOC컨테이너를 제공하는데 이 컨테이너가 관리하는 객체를 빈(bean)이라고 합니다.
  • 빈은 IOC컨테이너에 의해서 인스턴스화 되고 조립 및 관리되는 객체입니다.

 

IOC Container, DI Container

  • 어떤 객체가 사용하는 의존 객체를 직접 생성해서 사용하는 것이 아니라 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을 말하며 이러한 개념을 토대로 만들어진 것을 IOC Container 또는 DI Container라고 합니다.
  • 스프링에서는 BeanFactory, ApplicationContext가 IOC로 만들어진 인터페이스이며 ApplicaitonContext를 Spring Container라고 합니다.

 

BeanFactory

  • 스프링 컨테이너의 최상위 인터페이스입니다.
  • 스프링 빈을 관리하고 조회하는 역할을 담당합니다.
  • getBean()을 제공합니다.

 

ApplicationContext

  • BeanFactory 기능을 모두 상속받아서 제공합니다.
  • Application을 개발할 때 빈 관리나 조회하는 기능은 물론이고 수 많은 부가기능들이 필요합니다. 그래서 ApplicationConext에는 BeanFactory외에도 여러 인터페이스를 상속받고 있습니다.
  • MessageSource
    • 메시지 소스를 활용한 국제화 기능
  • EnvironmentCapable
    • 환경 변수
    • 로컬, 개발, 운영등으로 구분해서 처리
  • ApplicationEventPublisher
    • 이벤트를 발행하고 구독하는 모델을 편리하게 지원
  • ResourceLoader
    • 파일, 클래스패스, 외부 리소스등을 편리하게 조회

 

ApplicationContext는 다양한 구현체들을 가지고있습니다.

 

스프링 빈 이벤트 라이프 사이클

  1. 스프링 컨테이너생성
  2. 스프링 빈 생성
  3. 의존관계 주입
  4. 초기화 콜백(빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출)
  5. 사용
  6. 소멸전 콜백(빈이 소멸되기 직전에 호출)
  7. 스프링 컨테이너 종료

 

스프링은 의존관계 주입이 완료될 경우 스프링 빈에게 콜백 메서드를 통해서 초기화 시점을 알려주고 스프링 컨테이너가 종료되기 직전에 소멸전 콜백 메서드를 통해 소멸 시점을 알려줍니다.

BeanFactory 상단에 주석을 보면 빈 라이프 사이클 콜백에 대한 내용이 나와있습니다.

Bean factory implementations should support the standard bean lifecycle interfaces as far as possible.
The full set of initialization methods and their standard order is:

  1. BeanNameAware's setBeanName
  2. BeanClassLoaderAware's setBeanClassLoader
  3. BeanFactoryAware's setBeanFactory
  4. EnvironmentAware's setEnvironment
  5. EmbeddedValueResolverAware's setEmbeddedValueResolver
  6. ResourceLoaderAware's setResourceLoader (only applicable when running in an application context)
  7. ApplicationEventPublisherAware's setApplicationEventPublisher (only applicable when running in an application context)
  8. MessageSourceAware's setMessageSource (only applicable when running in an application context)
  9. ApplicationContextAware's setApplicationContext (only applicable when running in an application context)
  10. ServletContextAware's setServletContext (only applicable when running in a web application context)
  11. postProcessBeforeInitialization methods of BeanPostProcessors
  12. InitializingBean's afterPropertiesSet
  13. a custom init-method definition
  14. postProcessAfterInitialization methods of BeanPostProcessors

On shutdown of a bean factory, the following lifecycle methods apply:

  1. postProcessBeforeDestruction methods of DestructionAwareBeanPostProcessors
  2. DisposableBean's destroy
  3. a custom destroy-method definition

 

초기화 메서드 방법 및 순서

  1. @PostConstruct Annotation 사용
  2. InitializingBean 인터페이스를 구현하고 afterPropertiesSet()메서드를 재정의하여 사용
  3. @Bean Annotaion에 initMethod 속성 사용, initMethod 속성에 초기화 작업에 사용할 메서드 이름을 입력

 

소멸 메서드 방법 및 순서

  1. @PreDestroy Annotation 사용
  2. DisposableBean 인터페이스를 구현하고 destroy메서드를 재정의하여 사용
  3. @Bean Annotation에 destroyMethod 속성 사용, initMethod와 마찬가지로 소멸할 때 사용할 메서드 이름을 입력

 

//AppConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean(initMethod = "initHomeService", destroyMethod = "destroyHomeService")
    public HomeService homeServiceBean(){
        return new HomeService();
    }
}


//HomeService.java
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class HomeService implements InitializingBean, DisposableBean {

    @PostConstruct
    public void initPostConstruct(){
        System.out.println("PostConstruct Annotation");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet");
    }

    public void initHomeService() {
        System.out.println("custom initMethod");
    }

    @PreDestroy
    public void preDestroy(){
        System.out.println("PreDestroy Annotation");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("destroy");
    }

    public void destroyHomeService() {
        System.out.println("custom destroyMethod");
    }

}

위 소스로 실행 순서를 확인하면 아래와 같이 출력됩니다.

......스프링 실행.....
PostConstruct Annotation
afterPropertiesSet
custom initMethod

......스프링 종료.....
PreDestroy Annotation
destroy
custom destroyMethod

 

빈 설정 방법

 

Spring Bean을 등록하는 방법

1. XML 설정 사용 XML을 사용하면 컴파일 없이 빈 설정 정보를 변경할 수 있다. 빈 설정에 에러가 났을 경우 컴파일러가 이를 잡아주지 못하고 문제가 생겼을 경우에 원인을 찾는게 어렵다. 최근에

keepbang.tistory.com

 

 


 

스프링에서 빈이 만들어지는 과정

 

스프링에서 컨테이너가 만들어지고 빈을 등록하는 과정을 어떻게 처리하는지 디버깅을 한번 해보겠습니다.

 

처음 스프링 프레임워크을 실행하면 SpringApplication.run 메서드를 실행하게 됩니다.해당 메서드를 디버깅하면서 추적하면 아래와같이 createApplicationContext를 통해 ApplicationContext를 생성합니다.

 

createApplicationContext에서는 webApplicationType에 따라 ApplicationContext를 상속받는 객체를 리턴해주는걸 볼 수 있습니다.

 

 

여기서는 Servlet기반에 web application이기 때문에 AnnotaionConfigServletWebServerApplicationContext를 리턴합니다.

 

여기까지가 ApplicationConext인 Spring Container의 생성과정입니다.

 

다시 run메서드로 돌아와 아래 라인을 보면 refreshContext가 있습니다.

 

해당 메서드를 디버깅하다보면 AbstractApplicationContext에 refresh메서드를 호출합니다.

 

친절하게 주석이 다 달려있다...

refresh메서드에서 spring이 bean을 찾고 생성하고 관리하는 과정이 일어납니다.

 

여기서 주의깊게 살펴볼 메서드는

  1. invokeBeanFactoryPostProcessors
  2. finishBeanFactoryInitializaiton

이렇게 두개입니다.

 

invokeBeanFactoryPostProcessors메서드를 디버깅하다보면 ComponentScan을 파싱하고 basePakage아래에 있는 resource들을 읽어서 BeanDefinition형태로 저장합니다.

 

 


BeanDefinition

  • bean name, scope, class, dependencies등이 담겨져있습니다.
  • BeanDefinition을 빈 설정 메타정보라 합니다.
  • 자바 코드의 @Bean이나 XML설정의 을 각각 하나씩 메타 정보로 생성합니다.
  • 스프링 컨테이너는 이 메타정보를 기반으로 빈을 생성합니다.
public class BeanDefinitionTest {

    AnnotationConfigApplicationContext atx = new AnnotationConfigApplicationContext(SingletonService.class, PrototypeService.class);

    @Test
    @DisplayName("빈 메타정보 확인")
    void findApplicationBean(){
        String[] beanDefinitionNames = atx.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = atx.getBeanDefinition(beanDefinitionName);

            if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){
                System.out.println("beanDefinitionName = " + beanDefinitionName +
                        " beanDefinition = " + beanDefinition);
            }
        }
    }
}

임의로 만든 컴포넌트를 ApplicationContext에 등록하고 BeanDefinition을 조회했을때 아래와같은 정보를 확인할 수 있습니다.

beanDefinitionName = singletonService beanDefinition = Generic bean: class [spring.example.service.SingletonService]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null
beanDefinitionName = prototypeService beanDefinition = Generic bean: class [spring.example.service.PrototypeService]; scope=prototype; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null

BeanDefinition이 생성되면 finishBeanFactoryInitialization메서드를 통해 싱글톤 빈을 생성(인스턴스화)합니다.

 

 

[Next]

  • 의존관계 주입의 방법 및 장단점
  • 순환참조

 

[Reference]

  • 인프런 - 김영한님의 스프링 핵심 원리 기본편