반응형

# 스프링 컨테이너와 스프링 빈

## 스프링 컨테이너 생성

//스프링 컨테이너 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
  • ApplicationContext 를 스프링 컨테이너라고 한다.
  • ApplicationContext 는 인터페이스이다. (다형성 적용)
  • 스프링 컨테이너는 XML을 기반으로 만들 수 있고 / 애노테이션 기반의 자바 설정 클래스로 만들 수 있다.
  • AppConfig 를 사용했던 방식이 애노테이션 기반의 자바 설정 클래스로 스프링 컨테이너를 만든 것.
  • 자바 설정 클래스를 기반으로 스프링 컨테이너( ApplicationContext )를 생성
new AnnotationConfigApplicationContext(AppConfig.class); 

이 클래스는 ApplicationContext 인터페이스의 구현체이다.
  • 스프링 컨테이너 생성 과정은 아래와 같다.
1. 스프링 컨테이너 생성.
new AnnotationConfigApplicationContext(AppConfig.class)

스프링 컨테이너를 생성할 때는 구성 정보를 지정해주어야 한다.
여기서는 AppConfig.class 를 구성 정보로 지정했다.


2. 스프링 빈 등록.
스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용해서 빈을 등록한다. 
(@Bean 어노테이션 붙은 메서드)
(빈 이름은 보통 메서드 이름 사용, 빈 이름을 아래와 같이 임의로 부여 할 수도 있다.)
(빈 이름 임의 부여 예 : @Bean(name="orderService12"); )
(빈 이름은 항상 다른 이름을 부여해야 한다. 만약 같은 이름으로 부여하면 해당 빈을 무시하거나 기존 빈을 덮어버릴수있음.)


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


4. 스프링 빈 의존관계 설정 완료
- 스프링 빈은 설정 정보를 참고해서 의존관계 주입(DI)
- 단순히 자바 코드를 호출 한것 같지만 차이 존재.
  • 스프링은 빈을 생성하고, 의존관계를 주입하는 단계가 나누어져 있다.
  • 그런데 자바 코드로 스프링 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한번에 처리된다. 

## 컨테이너에 등록된 전체 빈 조회

  • 모든 빈 출력
실행하면 스프링에 등록된 모든 빈 정보를 출력할 수 있다.

ac.getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회한다.

ac.getBean() : 빈 이름으로 빈 객체(인스턴스)를 조회한다.
  • 애플리케이션 빈 출력하기
스프링이 내부에서 사용하는 빈은 getRole() 로 구분할 수 있다.

ROLE_APPLICATION : 일반적으로 사용자가 정의한 빈

ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈
package hello.core.beanfind;

import hello.core.AppConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ApplicationContextInfoTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName("등록된 전체 빈 출력")
    void findAllBean() {
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();

        // iter + Tab 키 입력 시 반복문 자동 완성.
        for (String beanDefinitionName : beanDefinitionNames) {
            Object bean = ac.getBean(beanDefinitionName);
            System.out.println("name = " + beanDefinitionName + " Object = " + bean);
        }
    }

    @Test
    @DisplayName("애플리케이션 빈 출력")
    void findApplicationBean() {
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();

        // iter + Tab 키 입력 시 반복문 자동 완성.
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);

            // Role ROLE_APPLICATION : 직접 등록한 애플리케이션 빈
            if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
                Object bean = ac.getBean(beanDefinitionName);
                System.out.println("name = " + beanDefinitionName + " Object = " + bean);
            }

            // Role ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈
            if(beanDefinition.getRole() == BeanDefinition.ROLE_INFRASTRUCTURE) {
                Object bean = ac.getBean(beanDefinitionName);
                System.out.println("name = " + beanDefinitionName + " Object = " + bean);
            }
        }
    }
}

## 스프링 빈 조회_가장 기본적인 방법

package hello.core.beanfind;

import hello.core.AppConfig;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import static org.junit.jupiter.api.Assertions.*;

class ApplicationContextBasicFindTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName("빈 이름으로 조회")
    void findBeanByName() {
        MemberService memberService = ac.getBean("memberService", MemberService.class);
        Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }

    @Test
    @DisplayName("빈 타입으로만 조회")
    void findBeanByType() {
        MemberService memberService = ac.getBean(MemberService.class);
        Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }

    @Test
    @DisplayName("구체 타입으로 조회")
    void findBeanByName2() {
        MemberService memberService = ac.getBean("memberService", MemberServiceImpl.class);
        Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }

    @Test
    @DisplayName("빈 이름으로 조회되는게 없는 경우.")
    void findBeanByNameX() {
        //MemberService xxxx = ac.getBean("xxxx", MemberService.class);
        assertThrows(NoSuchBeanDefinitionException.class,
                () -> ac.getBean("xxxx", MemberService.class));
    }
}

## 스프링 빈 조회_동일한 타입이 2개 이상인 경우

package hello.core.beanfind;

import hello.core.AppConfig;
import hello.core.discount.DiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class ApplicationContextSameBeanFindeTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(sameBeanConfig.class);

    @Test
    @DisplayName("타입으로 조회 시 같은 타입이 둘 이상 있으면 중복 오류 발생.")
    void findBeanByTypeDuplicate() {
        assertThrows(NoUniqueBeanDefinitionException.class,
                () -> ac.getBean(MemberRepository.class));
    }

    @Test
    @DisplayName("타입으로 조회 시 같은 타입이 둘 이상 있으면 빈 이름을 지정하면 된다.")
    void findBeanByName() {
        MemberRepository memberRepository = ac.getBean("memberRepository1", MemberRepository.class);
        assertThat(memberRepository).isInstanceOf(MemberRepository.class);
    }

    @Test
    @DisplayName("특정 타입을 모두 조회하기")
    void findAllBeanByType() {
        Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
        for (String key : beansOfType.keySet()) {
            System.out.println("key = " + key + " value = " + beansOfType.get(key));
        }

        System.out.println("beansOfType = " + beansOfType);
        assertThat(beansOfType.size()).isEqualTo(2);
    }

    @Configuration
    static class sameBeanConfig {

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

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

## 스프링 빈 조회_상속관계 (중요!!!)

  • 부모 타입으로 조회 시, 자식 타입도 함께 조회한다.
  • 자바 객체의 최고 부모인 Object 타입으로 조회 시, 모든 스프링 빈을 조회한다.
package hello.core.beanfind;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class ApplicationContextExtendsFindTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

    @Test
    @DisplayName("부모 타입으로 조회 시 자식이 둘 이상 있으면, 중복 오류 발생.")
    void findBeanByParentTypeDuplicate() {
        assertThrows(NoUniqueBeanDefinitionException.class,
                () -> ac.getBean(DiscountPolicy.class));
    }

    @Test
    @DisplayName("부모 타입으로 조회 시 자식이 둘 이상 있으면, 빈 이름 지정하면 됨.")
    void findBeanByParentTypeBeanName() {
        DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
        assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
    }

    @Test
    @DisplayName("특정 하위 타입으로 조회.")
    void findBeanBySubType() {
        RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
        assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
    }

    @Test
    @DisplayName("부모 타입으로 모두 조회.")
    void findAllBeanByParentType() {
        Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
        assertThat(beansOfType.size()).isEqualTo(2);
        for (String key : beansOfType.keySet()) {
            System.out.println("key = " + key + " value = " + beansOfType.get(key));
        }
        System.out.println("beansOfType = " + beansOfType);
    }

    @Test
    @DisplayName("부모 타입으로 모두 조회 - Object")
    void findAllBeanByObjectType() {
        Map<String, Object> beansOfType = ac.getBeansOfType(Object.class);
        for (String key : beansOfType.keySet()) {
            System.out.println("key = " + key + " value = " + beansOfType.get(key));
        }
    }

    @Configuration
    static class TestConfig {
        @Bean
        public DiscountPolicy rateDiscountPolicy() {
            return new RateDiscountPolicy();
        }

        @Bean
        public DiscountPolicy fixDiscountPolicy() {
            return new FixDiscountPolicy();
        }
    }
}

## BeanFactory 와 ApplicationContext

BeanFactory (빈 관리, 조회)

스프링 컨테이너의 최상위 인터페이스.

역할 : 스프링 빈 관리, 조회

getBean() 을 제공. 
지금까지 작업하면서 사용했던 대부분의 기능은 BeanFactory가 제공하는 기능.

ApplicationContext (BeanFactory 기능 모두 상속 + 부가기능 제공)

BeanFactory 기능을 모두 상속받아서 제공.

애플리케이션을 개발에 필요한 부가기능 제공.

1. 메시지소스를 활용한 국제화 기능
- 예를 들어서 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력

2. 환경변수
- 로컬, 개발, 운영등을 구분해서 처리

3. 애플리케이션 이벤트
- 이벤트를 발행하고 구독하는 모델을 편리하게 지원

4. 편리한 리소스 조회
- 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회
  • BeanFactory를 직접 사용할 일은 거의 없고, 부가기능이 포함된 ApplicationContext를 사용한다.
  • BeanFactory나 ApplicationContext를 스프링 컨테이너라고 한다.

## 다양한 설정 형식 지원_자바 코드, XML

  • 스프링 컨테이너는 다양한 형식의 설정 정보를 받아드릴 수 있게 유연하게 설계되어 있다. (자바 코드, XML, Groovy 등)
  • 과거에는 설정 정보로 XML 형식 많이 사용, 최근에는 자바 코드 형식 사용.
  • 애노테이션 기반 자바 코드 설정 사용
new AnnotationConfigApplicationContext(AppConfig.class)

AnnotationConfigApplicationContext 클래스를 사용하면서 자바 코드로된 설정 정보를 넘기면 된다.
  • XML 설정 사용
최근 스프링 부트를 많이 사용하면서 XML기반의 설정은 잘 사용하지 않는다.

많은 레거시 프로젝트 들이 XML로 되어 있고, XML 사용 시 컴파일 없이 빈 설정 정보를 변경할 수 있는 장점 존재.

GenericXmlApplicationContext 를 사용하면서 xml 설정 파일을 넘기면 된다.
  • xml 기반의 스프링 빈 설정 정보 아래와 같이 생성.
  • src/main/resources/appConfig.xml (자바 코드가 아닌건 모두 resources 하위에 두기)
<?xml version="1.0" encoding="UTF-8"?>
<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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="memberService" class="hello.core.member.MemberServiceImpl">
        <constructor-arg name="memberRepository" ref="memberRepository" />
    </bean>

    <bean id="memberRepository" class="hello.core.member.MemoryMemberRepository" />

    <bean id="orderService" class="hello.core.order.OrderServiceImpl">
        <constructor-arg name="memberRepository" ref="memberRepository" />
        <constructor-arg name="discountPolicy" ref="discountPolicy" />
    </bean>

    <bean id="discountPolicy" class="hello.core.discount.RateDiscountPolicy" />

</beans>
package hello.core.xml;

import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

import static org.assertj.core.api.Assertions.assertThat;

public class XmlAppContext {

    @Test
    void xmlAppContext() {
        ApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
        MemberService memberService = ac.getBean("memberService", MemberService.class);
        assertThat(memberService).isInstanceOf(MemberService.class);
    }
}
  • xml 기반의 appConfig.xml 스프링 설정 정보 / 자바 코드로 된 AppConfig.java 설정 정보를 비교해 보면 거의 비슷하다는 것을 알 수 있다. 

## 스프링 빈 설정 메타 정보_BeanDefinition

  • 스프링이 다양한 설정 형식을 지원하는 중심에는 BeanDefinition 라는 추상화가 존재. (역할과 구현을 개념적으로 나눈 것)
XML을 읽어서 BeanDefinition을 만들면 된다.

자바 코드를 읽어서 BeanDefinition을 만들면 된다.

스프링 컨테이너는 자바 코드인지, XML인지 몰라도 되고, BeanDefinition만 알면 된다.
  • BeanDefinition 을 빈 설정 메타정보라 한다.
@Bean , <bean> 각각 하나씩 메타 정보가 생성된다.

 

  • 스프링 컨테이너는 이 메타정보를 기반으로 스프링 빈을 생성한다.
  • AnnotationConfigApplicationContext 는 AnnotatedBeanDefinitionReader 를 사용 AppConfig.class 를 읽고 BeanDefinition 을 생성한다.
  • GenericXmlApplicationContext 는 XmlBeanDefinitionReader 를 사용 appConfig.xml 설정 정보를 읽고 BeanDefinition 을 생성.
  • 새로운 형식의 설정 정보가 추가되면, XxxBeanDefinitionReader를 만들어서 BeanDefinition 을 생성하면 된다.

### BeanDefinition 살펴보기

package hello.core.beandefinition;

import hello.core.AppConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class BeanDefinitionTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    //GenericXmlApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");

    @Test
    @DisplayName("빈 설정 메타정보 확인.")
    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 +
                        " beanDefinition = " + beanDefinition);
            }
        }
    }
}
  • BeanDefinition을 직접 생성해서 스프링 컨테이너에 등록할 수 도 있다. 하지만 실무에서 BeanDefinition을 직접 정의하거나 사용할 일은 거의 없다.
반응형

+ Recent posts