본문 바로가기
Study/Java Spring Boot

[Spring] 핵심 원리 4 스프링 컨테이너와 빈 (Spring 전환)

본 글은 김영한님의 강의 내용을 바탕으로 정리한 글입니다.

 

스프링 핵심 원리 - 기본편 - 인프런 | 학습 페이지

지식을 나누면 반드시 나에게 돌아옵니다. 인프런을 통해 나의 지식에 가치를 부여하세요....

www.inflearn.com

 

이전 글까지 우리는 순수JAVA로 도메인을 개발하고 SOLID를 준수하는 객체 지향적인 DI컨테이너를 만들어보았다.

이제부터는 Spring을 이용해서 AppConfig와 DI 방식을 더욱 간략히 하는 기술들을 적용시켜보자.

잠시 Spring 컨테이너와 빈의 개념을 집자.

 

Spring 컨테이너

  • 인스턴스의 생명주기를 관리하며, 생성된 인스턴스들에게 추가적인 기능을 제공하도록하는 것
  • 객체의 생성과 소멸을 컨트롤
  • BeanFactory , ApplicationContext 두 종류가 있다.
    • BeanFactory 
      • 빈을 등록하고 생성하고 조회하고 돌려주고, 그 외에 부가적인 빈을 관리하는 기능을 담당한다.
      • 빈 팩토리가 빈의 정의는 즉시 로딩하는 반면, 요청이 있기까지 인스턴스화를 하지 않는다
      • getBean()이 호출되야, 의존성 주입을 이용해 빈을 인스턴스화하고 빈의 생명주기가 시작됨.
    • ApplicationContext 
      • BeanFactory 를 상속, 확장한 향상 된 컨테이너
      • 컨텍스트 초기화 시점에 모든 싱글톤 빈을 미리 생성하므로, 인스턴스를 즉시 사용하게 보장
      • 기본적인 기능은 빈 팩토리와 동일하고 스프링이 제공하는 각종 부가 서비스를 추가로 제공
        • 국제화가 지원되는 텍스트 메시지를 관리해 준다.
        • 이미지같은 파일 자원을 로드 할 수 있는 포괄적인 방법을 제공해준다.
        • 리너스로 등록된 빈에게 이벤트 발생을 알려준다.

Spring Bean

  • Spring IoC 컨테이너가 관리하는 자바 객체를 빈(Bean)이라고 부름.

 

 

1. Spring Bean

package hello.core;

import hello.core.discount.*;
import hello.core.member.*;
import hello.core.order.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
    @Bean
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository());
    }
    @Bean
    public MemberRepository memberRepository(){
        return new MemoryMemberRepository(); //메모리 맴버리포지토리로 생성
    }
    @Bean
    public OrderService orderService(){
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }
    @Bean
    public DiscountPolicy discountPolicy(){
        return new FixDiscountPolicy(); // 고정 할인 정책으로 생성
    }

}

Spring Bean 어노테이션을 명시해주면, ApplicationContext 라는 컨테이너를 사용한 것이다.

애플리케이션의 실행과 동시에 @Bean이 명시된 객체를 생성하여 관리해준다.

주의 , 빈객체의 이름은 반드시 유일해야 하며, 사고방지를 위해 이름을 명시하기를 바람

@Bean(name="memberService2")

 

이제 호출할 클라이언트 앱쪽에서 Bean객체를 넘겨받아보자.

package hello.core;

import hello.core.member.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MemberApp {
    public static void main(String[] args) {
//        MemberService memberService = new MemberServiceImpl();

//        AppConfig appConfig = new AppConfig();
//        MemberService memberService = appConfig.memberService();

        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = applicationContext.getBean("memberService", hello.core.member.MemberService.class);

        Member member1 = new Member(1L, "member1", Grade.VIP);
        memberService.join(member1);

        Member findMember = memberService.findMember(member1.getId());
        System.out.println("findMember = " + findMember.getName());
        System.out.println("member1 = " + member1.getName());
    }
}

 

 

2. Bean 조회

2-1 등록된 모든 Bean 조회

아래와 같이 test 밑에 hello.core.beanfind / ApplicationContextInfoTest 를 만들어서 수행시켜보자

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;
import static org.assertj.core.api.Assertions.assertThat;
class ApplicationContextInfoTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    @Test
    @DisplayName("모든 빈 출력하기")
    void findAllBean() {
        String[] beanDefinitionNames = ac.getBeanDefinitionNames(); //빈 객체이름들을 가져옴
        for (String beanDefinitionName : beanDefinitionNames) { //iter + tab
            Object bean = ac.getBean(beanDefinitionName);
            System.out.println("name=" + beanDefinitionName + " \t\tobject=" + bean); //soutv
        }
    }
    @Test
    @DisplayName("애플리케이션 빈 출력하기")
    void findApplicationBean() {
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
            //Role ROLE_APPLICATION: 직접 등록한 애플리케이션 빈
            //Role ROLE_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈
            if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
                Object bean = ac.getBean(beanDefinitionName);
                System.out.println("name=" + beanDefinitionName + " \t\tobject=" +
                        bean);
            }
        }
    }
}

전체 Bean을 출력해보면, 내부에서 사용하는 빈까지 출력되는데,

beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION

이렇게 if문에 분기를 사용해 직접등록한 종류의 빈만 출력해 볼 수 있다.

 

2-2  등록된 하나의 Bean 조회

package hello.core.beanfind;
import hello.core.AppConfig;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.junit.jupiter.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.assertj.core.api.Assertions.*;
class ApplicationContextBasicFindTest {
    AnnotationConfigApplicationContext ac = new
            AnnotationConfigApplicationContext(AppConfig.class);
    @Test
    @DisplayName("빈 이름, 클래스타입 조회")
    void findBeanByName() {
        MemberService memberService = ac.getBean("memberService",MemberService.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }    
    @Test
    @DisplayName("빈 이름, 구체화 타입 조회")
    void findBeanByName2() {
        MemberServiceImpl memberService = ac.getBean("memberService",MemberServiceImpl.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }
    @Test
    @DisplayName("클래스타입만으로 조회") //비추천
    void findBeanByType() {
        MemberService memberService = ac.getBean(MemberService.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }

    @Test
    @DisplayName("빈 이름으로 조회X")
    void findBeanByNameX() {
        //ac.getBean("xxxxx", MemberService.class);
        Assertions.assertThrows(NoSuchBeanDefinitionException.class, () -> ac.getBean("xxxxx", MemberService.class));
    }
}

이렇게 getBean() 안에 이름이나 클래스명 인자를 넣어서 조회할 수 있다.

1) Bean이름과 클래스명을 넣어서 조회  : 제일 구체적이고 명확한 조회법 (Bean이름은 유니크하기 때문)

2) Bean이름과 구현체명 을 넣어서 조회 : 조회는 가능하지만, SOLID와 객체 지향을 배웠기에 추상에 의존하자!

3) 클래스타입만 넣어서 조회 : 여러 Bean이 존재할 수 있으므로 주의가 필요하다.

+ 빈이름이 없는 경우, NoSuchBeanDefinitionException 발생

NoSuchBeanDefinitionException

 

2-3 중복이름이 존재시 에러

Bean이름은 중복되어선 안된다. 중복이 어떤 익셉션이 발생하는지 한번 시도해보자.

package hello.core.beanfind;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
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;
class ApplicationContextExtendsFindTest {
    AnnotationConfigApplicationContext ac = new
            AnnotationConfigApplicationContext(TestConfig.class);
    @Test
    @DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 중복 오류가 발생한다")
    void findBeanByParentTypeDuplicate() {
        //DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
        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));
        }
    }
    @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와

해당 클래스 안에서 SameBeanConfig 컨테이너를 만들고 테스트를 해보았다. 

SameBeanConfig 에서 memberRepository1, memberRepository2 두 개의 Bean을 추가로 등록해주고나서,

어떠한 익셉션이 나는지 실행시켜보았다. 

 

1) Bean이 2개 이상인 경우, 클래스타입만 넣어서 조회시 에러 

NoUniqueBeanDefinitionException가 발생한 걸 확인 할 수 있다.

이러한 상황에서는 클래스타입으로 getBean()을 하지말고, Bean이름까지 명시해서 수행시키자.

 

2) Bean이 2개 이상인 경우, Bean 이름까지 지정해서 조회하자!

MemberRepository memberRepository = ac.getBean("memberRepository1", MemberRepository.class);

 

3) Bean이 2개 이상인 경우, 클래스타입만 넣어서 조회시 Map으로 받자!

2개 이상인 경우, Bean이름과 Bean클래스타입을 Map<String, 클래스타입>으로 반환해주는 getBeansOfType() 이 있다.

@Test
@DisplayName("Bean이 2개 이상인 경우, 클래스타입만 넣어서 조회시 Map으로 받자!")
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);
}

 

 

 

3. Spring Bean 상속 관계

Spring Bean은 최상위 부모 클래스타입으로 조회를 하면, 

해당 클래스뿐만 아니라, 그 하위 모든 자식 클래스들도 감자 줄기처럼 다 함께 조회된다.

 

사실 실무에서 Bean을 조회할 일을 많지 않다.

그래도 기본기능이기도 하면서 증명해야하는 순간이 있을수 있기때문에 빈 조회 방식을 알아보았다.

package hello.core.beanfind;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
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;

class ApplicationContextExtendsFindTest {
    AnnotationConfigApplicationContext ac = new
            AnnotationConfigApplicationContext(TestConfig.class);
    @Test
    @DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 중복 오류가 발생한다")
    void findBeanByParentTypeDuplicate() {
        //DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
        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));
        }
    }
    @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();
        }
    }
}

DiscountPolicy를 상속하는 정률할인 객체와, 고정할인 정책 두 Bean을 등록한 TestConfig를 작성하고

5가지 테스트를 메소드를 만들었다. 

 

1) getBean()에 부모타입으로 조회시 - 자식이 둘 이상이 경우 NoUniqueBeanDefinitionException 익셉션 발생

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

2) getBean()에 빈이름을 명시하여 조회

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

3) 특정 자식타입으로 조회 (지양함)

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

4) 부모타입으로 모두조회 - DiscountPolicy

@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));
    }
}

5) 부모타입으로 모두조회 - Object

@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));
    }
}

자바는 모든 클래스가 Object 클래스를 상속받으므로 Spring 내부 클래스까지 출력된다.

 

 

4. BeanFactory, ApplicationContext

 방금전까지 Bean을 조회하던 모든 메소드가 BeanFactory 인터페이스의 메소드를 상속받은것들이다.

우리가 사용한 ApplicationContext 는 BeanFactory를 상속해서 부가적인 기능을 추가한 클래스이다.

 

  • 메시지소스를 활용한 국제화 기능
    • 예를 들어서 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력
  • 환경변수
    • 로컬, 개발, 운영등을 구분해서 처리
  • 애플리케이션 이벤트
    • 이벤트를 발행하고 구독하는 모델을 편리하게 지원
  • 편리한 리소스 조회
    • 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회

 

정리

  • ApplicationContext는 BeanFactory의 기능을 상속받는다.
  • ApplicationContext는 빈 관리기능 + 편리한 부가 기능을 제공한다.
  • BeanFactory를 직접 사용할 일은 거의 없다. 부가기능이 포함된 ApplicationContext를 사용한다.
  • BeanFactory나 ApplicationContext를 스프링 컨테이너라 한다.
  • 보통 ApplicationContext만 사용한다. 

 

 

5. Java 설정 , XML 설정

최근에는 Xml로 Bean을 설정하지 않지만, 과거 프로젝트들은 DI컨테이너가 xml로 존재하는 경우가 있다.

package hello.core.xml;
import hello.core.member.MemberService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import static org.assertj.core.api.Assertions.*;
public class XmlAppContext {
 @Test
 void xmlAppContext() {
 ApplicationContext ac = new
GenericXmlApplicationContext("appConfig.xml");

 MemberService memberService = ac.getBean("memberService",
MemberService.class);
 assertThat(memberService).isInstanceOf(MemberService.class);
 }
}

src/main/resources/appConfig.xml

<?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>
  • Bean태그에 이름을 고유의 id값으로 지정하고, class명을 넣어서 설정해준다.
  • 빈을 등록하는 DI를 xml로 사용하게 되면, Complie없이 로드되기 때문에 변경과 적용이 더 쉽다. 
  • xml 기반 GenericXmlApplicationContext 보다, AnnotationConfigApplicationContext가 훨씬 더 많은 기능을 제공하고 최신화되고 있기 때문에, 사용되지 않는다.  
  • xml 사용이나 추가적인 정보가 필요하면 스프링 공식 레퍼런스 문서를 참조하자.

 

6. 스프링 빈 설정 메타 정보 - BeanDefinition

  • 스프링은 어떻게 이런 다양한 설정 형식을 지원 할 수 있게 해주는 이유는 BeanDefinition 이라는 추상체 때문, 
  • JAVA 든, XML이든 읽어서 Bean에 메타정보를 담는 BeanDefinition 객체를 만들기만 하면 똑같이 수행되는 구조.
  • Spring자체가 OOP의 SOLID 원칙을 잘 지켜서 역할과 구현을 잘 나누어 설계되어 있기 때문에 다양한 설정방식이 존재 할 수 있다.
  • @Bean or <Bean> 태그 하나마다 하나씩 BeanDefinition 메타 정보가 생성된다.
  • 스프링 컨테이너는 이 BeanDefinition 객체의 정보를 기반으로 스프링 빈을 생성한다.

 

BeanDefinition 살펴보기

BeanDefinition 정보

  • BeanClassName: 생성할 빈의 클래스 명 (자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)
  • factoryBeanName: 팩토리 역할의 빈을 사용할 경우 이름, 예) appConfig
  • factoryMethodName: 빈을 생성할 팩토리 메서드 지정, 예) memberService
  • Scope: 싱글톤(기본값)
  • lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때 까지 최대한 생성을 지연처리 하는지 여부
  • InitMethodName: 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명
  • DestroyMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명
  • Constructor arguments, Properties: 의존관계 주입에서 사용한다. (자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)
package hello.core.beandefinition;
import hello.core.AppConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
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을 직접정의하거나 사용하지는 않겠지만, 

스프링빈을 등록하는 방식이 어떻게 많은 형식으로 확장가능한지까지 알아보았다.

이를 통해 OOP의 SOLID가 잘 지켜진 객체지향적인 코드가 가지는 무한한 확장성을 볼 수 있었다.

 

 

 

이번글까지의 소스코드 ↓