스프링 컨테이너인 ApplicationContext에 대해 알아보자!

코린이·2024년 9월 19일
post-thumbnail

스프링은 스프링 컨테이너를 이용하여 자바 객체의 생명주기를 관리합니다.

스프링은 스프링 컨테이너로서 BeanFactory 라는 인터페이스르 제공하며 해당 인터페이스는 Bean을 관리하고 검색하는 기능을 제공합니다.

ApplicationContextBeanFactory를 상속받은 인터페이스로 Bean의 관리 및 검색 기능 뿐만 아니라 다국화, 이벤트 기능 등 추가적인 기능들을 제공합니다.

그렇기 때문에 대부분 ApplicationContext 인터페이스의 구현체를 사용하여 개발을 진행합니다.

저는 이번에 ApplicationContext에 초첨을 맞춰 글을 써보고자 합니다.

ApplicationContext

Spring에서는 ApplicationContext의 다양한 구현체를 제공합니다.

ApplicationContext의 사용 목적 중 하는 Bean의 관리 및 검색 기능을 제공하는 것입니다.

구현체에 따라 XML, Java 클래스 파일 등 다양한 형식의 설정 파일을 통해 스프링에서 관리할 자바 객체(Bean)을 추가할 수 있습니다.

스프링에는 위와 같이 몇가지의 ApplicationContext 구현체가 존재한다.

AnnotationConfigApplicationContext: Java 클래스 설정 파일을 기반으로 빈을 관리한다.

GenericXmlApplicationContext: xml 설정 파일을 기반으로 빈을 관리한다.

xxxApplicationContext: Java 혹은 xml 설정 파일 이외에 다른 형식의 설정파일로 빈을 관리한다.

이것이 가능한 이유는 Bean 등록을 BeanDefinition으로 추상화하여 생성하기 때문입니다.

다양한 형식의 설정 파일에 상관없이 BeanDefinition 객체가 생성됩니다.

스프링 컨테이너의 동작

1. 스프링 컨테이너 생성과정

스프링 컨테이너는 다음과 같은 순서로 생성된다.

  1. new를 하여 스프링 컨테이너를 만든다.
  2. 스프링 컨테이너 안에는 스프링 빈 저장소 라는 것이 있는데 스프링 빈이 여기에 저장된다.
    이때 빈 이름이 Key, 빈 객체가 Value가 된다.
  3. 스프링 컨테이너를 생성할 때는 구성 정보(설정 정보)를 지정해주어야 하는데 여기서는 AppConfig.class이다.
  4. 이 AppConfig.class를 보고 스프링 빈 저장소에 등록한다.ㅌㅂ

만약 동일한 이름의 빈 객체가 존재할 경우 오류가 발생한다.

2. 빈 등록 및 의존성 주입

  1. 설정 정보에 있는 정보를 스프링 빈 저장소에 등록한다. 이때 @Bean이 붙은 객체를 등록한다.
  2. 이때 빈 이름은 메서드 이름을 사용하며 빈 이름을 직접 부여할 수 도 있다.
    • 빈 이름은 항상 다른 이름을 부여해야 한다.
  3. Bean 생성 후, 스프링 컨테이너는 의존성 주입(Dependency Injection)을 수행합니다.
    • 의존성 주입은 Bean이 필요로 하는 다른 Bean을 찾아서 해당 Bean을 주입하는 작업을 의미합니다.
    • @Bean으로 스프링 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한번에 처리된다.

3. 빈 초기화

Bean이 생성되고 의존성이 주입된 후, 스프링 컨테이너는 Bean의 초기화 작업을 수행합니다.
초기화 작업은 커스텀한 초기화 메서드를 호출하거나 특정 인터페이스를 구현한 메서드를 실행하는 등의 방식으로 진행됩니다.

4. 빈 사용

  • ac.getBean(빈 이름, 타입) 혹은 ac.getBean(타입)를 이용하여 빈 저장소에 등록된 빈 객체를 가져올 수 있다.
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

// ac.getBean(빈 이름, 타입)
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);

// ac.getBean(타입)
MemberServiceImpl memberService = ac.getBean(MemberServiceImpl.class);

5. 소멸

스프링 컨테이너가 종료될 때 또는 필요한 경우에 Bean의 소멸 작업을 수행합니다.
소멸 작업은 커스텀한 소멸 메서드를 호출하거나 특정 인터페이스를 구현한 메서드를 실행하는 등의 방식으로 진행됩니다.

BeanDefinition

BeanDefinition은 빈 설정 메타정보라고 합니다.

BeanDefinition 라는 인터페이스 덕분에 스프링 컨테이너는 설정 파일이 자바 코드인지, xml인지 몰라도 빈을 관리할 수 있습니다.

BeanDefinition은 역할(interface)이 되고, AppConfig.class, appConfig.xml, appConfig.xxx 등과 같은 것들이 구현이 되어 역할과 구현으로 서로 나눠졌다고 볼 수 있습니다.

BeanDefinition 에 대해 조금 더 자세히 알아보겠습니다.

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
    private final AnnotatedBeanDefinitionReader reader;
...
}

앞서 자바 설정 구현체 중에는 AnnotationConfigApplicationContext 가 있고 해당 코드를 보면 AnnotatedBeanDefinitionReader 라는 객체가 존재하며 이는 자바 설정 파일을 읽어 BeanDefinition를 생성하는 역할을 합니다.

이 뿐만 아니라 다른 ApplicationContext 의 구현체들도 각각 Reader객체가 존재하고 해당 객체가 설정파일을 읽어 BeanDefinition를 생성합니다.

BeanDefinition에는 다음과 같은 정보들이 포함되어 있습니다.

  • BeanClassName : 생성할 빈의 클래스 명(자바 설정처럼 팩토리 역할의 빈을 사용하면 없음)
  • factoryBeanName : 팩토리 역할의 빈을 사용할 경우 이름, 예) appConfig
  • factoryMethodName : 빈을 생성할 팩토리 메서드 지정, 예) memberService
  • Scope : 싱글톤(기본값)
  • lazyInit : 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때까지 최대한 생성을 지연처리 하는지 여부
  • InitMethodName : 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명
  • DestoryMethodName : 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명
  • Constructor arguments, Properties : 의존관계 주입에서 사용한다. (자바 설정처럼 팩터리 역할의 빈을 사용하면 없음)

실제로 BeanDefinition의 메타정보를 출력해보겠습니다.

아래코드는 Java로 작성된 설정파일입니다.

@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}

해당 Java 설정파일을 의존성으로 하여 AnnotationConfigApplicationContext를 통해 의존성 컨테이너를 생성하고 개발자가 생성한 빈만(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) 추려 출력하도록 코드를 작성하였습니다.

class BeanDefinitionTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

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

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

factoryBeanName는 AppConfig의 명이 되고, factoryMethodName는 빈 저장소에 저장된 빈의 이름(key)이 출력됩니다.

beanDefinitionName = memberService
beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false;
                               factoryBeanName=appConfig; factoryMethodName=memberService; initMethodName=null; destroyMethodName=(inferred); defined in hello.core.AppConfig

beanDefinitionName = memberRepository
beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; 
                             factoryBeanName=appConfig; factoryMethodName=memberRepository; initMethodName=null; destroyMethodName=(inferred); defined in hello.core.AppConfig

마무리

해당 포스팅에서 스프링의 ApplicationContext과 이것의 동작과정을 알아보았고, 빈의 메타 정보를 가지고 있는 객체인 BeanDefinition에 대해서 알아보았습니다.

여러가지 형식의 설정 파일을 이용하여 빈을 스프링 컨테이너에 주입할 수 있지만 등록해야 할 빈이 수십개, 수백개가 되면 일일이 등록하기 귀찮고, 설정정보 자체가 커져 누락하는 문제까지 발생할 수 있다.

그래서 스프링은 설정정보 없이 자동으로 스프링 빈을 등록하는 @ComponentScan이라는 기능과 의존관계도 자동으로 주입하는 @Autowired라는 기능을 제공합니다.

해당 어노테이션을 사용하면 특정 범위 내에 있는 자바 클래스들 중 @Component라는 어노테이션이 붙은 클래스에 대해 스프링 컨테이너에서 자동으로 빈을 생성하여 생명주기를 관리해주기 때문에 개발자가 비지니스 로직에 더욱 집중할 수 있습니다. 해당 기능에 대해서는 기회가 되면 포스팅 할 수 있도록 하겠습니다.

긴 글 읽어주셔서 감사합니다.

참고자료

https://drcode-devblog.tistory.com/334
https://velog.io/@max9106/Spring-ApplicationContext
https://sorryday.tistory.com/21
https://devwarriorjungi.tistory.com/entry/스프링-컨테이너Spring-Container-빈Bean

profile
호기심이 많고, 문제를 끝까지 해결하려는 집념이 강한 개발자입니다.

0개의 댓글