본문 바로가기

Spring

스프링 빈과 스프링 컨테이너

저번 포스팅에서는 DI(Dependency Injection)에 대해서 알아 보았다.

이번에는 스프링에서 DI를 통해 의존관계를 주입해주는 이 스프링 컨테이너에 대해서 알아보자.

 

 

 

💻  스프링 컨테이너 생성

 

ApplicationContext applicationContext =
	new AnnotationConfigApplicationContext(AppConfig.class);

 

먼저, 스프링 컨테이너가 생성되는 코드를 보자.

여기서 AoolicationContext라는 인터페이스를  스프링 컨테이너라고 한다. 위 코드는 AppConfig라는 클래스에 속해있는 스프링 빈들을 스프링 컨테이너에서는 스프링 빈들의 생성,삭제,관리등을 담당한다. 

 

스프링 빈에 대해서 간단하게 짚고 넘어가자면, 하나의 자바 객체이다.

우리는 원래 자바에서 new 를 사용하여 자바 객체를 새로 하나 만들었다. 그러나 이렇게 되면, 서로에 대한 의존성이 높아지게 된다. 따라서, 스프링에서는 스프링 빈(Bean)이라는 것을 스프링 컨테이너에서 관리하고 우리는 원할때 메소드를 통해서 호출하는 방식으로 이것들을 사용한다.

 

원래는, 우리들이 객체를 생성하고 관리하였다. 허나, 의존성에 관한 문제로 인해 우리는 컨테이너라는 것을 만들고 이 컨테이너가 객체의 생성 및 관리를 하도록 역할을 위임하였다. 이것을 IOC(Inverse of Control), 제어의 역전이라고 하고, 이는 후에 다시 다루도록 하겠다.

 

다시 스프링 컨테이너로 돌아와서!

스프링 컨테이너는 XML을 기반으로 만들 수도 있고, 애노테이션 기반의 자바 설정 클래스로 만들 수 있다.

이렇게 두가지방식으로 만들 수 있는 이유는 추상화가 되어있기 때문인데 글 후반부에 다루도록 하겠다.

 

스프링 컨테이너를 생성할 때는 구성 정보를 지정해주어야 한다.  위 코드는 AppConfig라는 자바 설정 클래스를 기반으로 스프링 컨테이너를 만든 것이고, AnnotationConfigApplicationContext는 ApplicationContext인터페이스의 구현체인 클래스이다.

 

@Configuration
public class AppConfig {

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

    @Bean
    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }

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

    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }
}

스프링 컨테이너가 생성이 되면 스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보(AppConfig.class)를 사용해서 스프링 빈을 등록한다.

 

 

스프링 컨테이너의 생성 과정은 다음 순서와 같다

 

1. 스프링 컨테이너 생성

스프링 컨테이너를 생성하며, 구성 정보(위에서는 AppConfig.class)를 지정해준다.

 

2.스프링 빈 등록

스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용해서 스프링 빈을 등록한다.

 

3.스프링 빈 의존관계설정

스프링 컨테이너는 설정 정보를 참고해서 의존관계 주입을 한다.

 

스프링 빈을 생성하는 단계와 의존관계 주입 단계가 나누어져 있다. 그러나 실제로는 스프링 빈을 등록하면 생성자를 호출하며 의존관계 주입도 한꺼번에 처리가 된다.

 

 

 

 

💻  스프링 빈 조회

위에서는 스프링 빈을 생성하고 스프링 컨테이너에 등록하는 방법을 알아보았다.

이제는 이 스프링 빈들이 스프링 컨테이너에 잘 있는지 확인해보겠다.

AnnotationConfigApplicationContext ac = new
  AnnotationConfigApplicationContext(AppConfig.class);


@Test
@DisplayName("애플리케이션 빈 출력하기") void findApplicationBean() {
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
			if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
           			Object bean = ac.getBean(beanDefinitionName);
				System.out.println("name=" + beanDefinitionName + " object=" + bean);
			}
		}
	}

 

위 코드에서 ac라는 스프링 컨테이너를 생성하였고 그 컨테이너는 AppConfig라는 클래스의 구성정보를 담고 있다.

ac.getBeanDefinitionNames()라는 함수를 이용하여 스프링 빈들의 이름을 배열에 저장하였다.

 

if문의 getRole()메소드를 사용하여 일반적으로 사용자가 정의한 빈인지 판별해주었다.

Role ROLE_APPLICATION: 직접 등록한 애플리케이션 빈
Role ROLE_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈

이처럼 빈도 종류별로 구분해줄 수 있다.

만약, if문이 없었다면, 애플리케이션 빈뿐이 아닌, 모든 빈들이 출력이 되었을 것이다.

 

그리고 ac.getBean() 메소드를 이용하여 스프링 빈의 이름으로 그 객체를 조회한다. 그래서  bean의 타입은 Object가 된다.

getBean은 스프링 빈을 조회하는 가장 기본적인 메소드이니 기억해두자.

 

 

조회시 주의할 점)

  1. 타입으로 조회시 같은 타입의 스프링 빈이 둘 이상이면 오류가 발생한다. 이때는 빈 이름으로 조회를 하면 된다. getBeansOfType()을 사용하면 해당 타입의 모든 빈을 조회 할 수 있다.
  2. 부모 타입으로 조회하면, 자식 타입도 함께 조회한다.

 

 

 

💻 BeanFactory와 ApplicationContext

 

우리는 앞서 봤던 것처럼, ApplicationContext의 인터페이스를 따르는 AnnotationConfigApplicationContext라는 클래스를 이용하여 스프링 컨테이너를 만들었다. 

 

그리고 이러한 ApplicationContext는 BeanFactory라는 인터페이스를 상속받는다. 이 BeanFactory는 스프링 컨테이너의 최상위 인터페이스이고 스프링 빈을 관리하고 조회하는 역할을 담당한다. 우리가 쓰는 대부분의 모든 기능들은 이 BeanFactory라는 인터페이스가 제공하는 기능이고, ApplicationContext는 여러가지 부가기능들을 덧붙여 제공해준다. 이 부가기능들은 그냥 편리하게 사용하게 해주는 기능들이며 알 필요가 딱히 없어보인다..

 

그리고, BeanFactory를 직접 사용할 일은 거의 없으니 부가기능이 포함된 ApplicationContext를 계속해서 사용하면 된다.

 

아까 말했듯이 스프링 컨테이너는 자바 코드,XML등 여러가지 형식의 설정 정보를 받아드릴 수 있게 돼있다고 했다.

이것은 여러가지 스프링 컨테이너의 형식들이 여러가지 형식의 설정 정보들을 받아드릴 수 있게 만들어져 있기 때문이다.

 

  • AnnotationConfigApplicationContext : 자바코드
  • GenericXmlApplicationContext : xml

이렇게 여러가지 클래스들이 ApplicationContext를 상속하여 설계되어 있다.

 

이렇게 다양한 설정 형식들을 지원하는 중심에는 BeanDefinition이라는 추상화가 있다.

설정정보를 읽으면 컨테이너는 BeanDefinition이라는 것을 만든다. 이것을 빈 설정 메타정보라 한다. 우리가 코드에 짤때마다 @bean을 달아놨는데 이거당 각각 하나씩 메타정보가 생성이된다.

이 안에는 빈을 구성하는 여러가지 정보가 담겨있어서 이것을 이용하여 여러 정보들을 얻을 수 있다.

물론, 우리가 BeanDefinition을 직접 생성하여 스프링 컨테이너에 등록할 수도 있지만, 딱히 쓸모가 없어 보인다..