Spring Container & SpringBean

황상익·2024년 4월 4일

Spring 기본

목록 보기
5/10

Spring Container 생성

ApplicationContext applicationContext =
 new
AnnotationConfigApplicationContext(AppConfig.class);

ApplicationContext를 스프링 컨테이너라 한다. 그리고 이는 인터페이스이다.
AppConfig를 사용했던 방식은 에노테이션 기반의 자바 클래스로 스프링 컨테이너를 만든것

참고) BeanFactory, ApplicationContext로 구분해서 이야기.


스프링 컨테이너를 생성할때는 구성정보를 지정
여기서는 AppConfig에 구성정보 설정

빈 이름
빈 이름은 메서드 이름을 사용
빈 이름을 직접 부여 할 수도 있다.

참고) 빈 이름은 항상 다른 이름을 부여해야 한다. 같은 이름을 부여하면, 다른 빈이 무시되거나, 기존 빈을 덮어버리거나 설정에 따라 오류 발생 가능성


스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입
단순히 자바 코드를 호출하는 것 같지만 차이가 있다.

스프링 빈을 생성, 의존 관계를 주입하는 단계가 나눠져 있다.
자바코드로 스프링 빈을 등록하면, 생성자를 호출하면서 의존관계 주입도 한번에 처리.

컨테이넌에 등록된 모든 빈 조회

public class ApplicationContextInfoTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName("모든 빈 출력")
    void find_All_Bean(){
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            Object bean = ac.getBean(beanDefinitionName);
            System.out.println("name = " + beanDefinitionName + "Object = " + bean);
        }
    }

    @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 = " + beanDefinition + " object = " + bean);
            }
        }
    }

	//모든 빈 출력
    //ac.getBeanDefinition -> 모든 빈 이름 출력하기
    //ac.getBean -> 빈 이름으로 객체 (인스턴스) 조회
    
    //애플리케이션 빈 출력하기 
    //스프링 내부에서 사용하는 빈 제외하고, 등록한 빈만 출력 
    //ROLE_APPLICATION : 일반적으로 사용자가 정의한 빈
    //ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈

}

스프링 빈 조회
ac.getBean(빈 이름, 타입)
ac.getBean(타입)

조회 대상 빈 없다면

NoSuchBeanDefinitionException: No bean named 'xxxxx' available

Test 돌릴때 이런 바로 나왓다.

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'memberService' available

이유 AppConfig에 ComponentScan해주고 Bean을 annotation 해주는 것을 잊어먹음

public class ApplicationContextBasicFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName("find by Name")
    void findBeanByName() {
        MemberService memberService = ac.getBean("memberService",
                MemberService.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }

    @Test
    @DisplayName("find by type")
    void findType(){
        MemberService memberService = ac.getBean(MemberService.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }


    @Test
    @DisplayName("findSpecificType")
    void specific(){
        MemberService memberService = ac.getBean("memberService", MemberService.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }

    @Test
    //예외
    @DisplayName("findName X")
    void nameX(){
        assertThrows(NoSuchBeanDefinitionException.class, ()
        -> ac.getBean("xxxxx", MemberService.class));
    }
}

ac.getBean으로 해주지 않은 이유

 @DisplayName("findName X")
    void nameX(){
        assertThrows(NoSuchBeanDefinitionException.class, ()
        -> ac.getBean("xxxxx", MemberService.class));
    }

-> 구체 타입으로 하면 설정 복잡...

스프링 빈 조회 - 동일한 타입 둘 이상

자 여기서 문제점이 있다.

public class ApplicationContextSameBeanFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);

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

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

    @Test
    @DisplayName("특정 타입 모두 조회")
    void findAllBeanType() {
        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
    private class SameBeanConfig {

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

        }

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

바로 SameBeanConfig 부분을 private로 설정했다. -> private으로 설정 했을 경우 contsructor에 생성이 안되서 이름을 못찾는 것 같다.

10:52:20.173 [main] WARN org.springframework.context.annotation.AnnotationConfigApplicationContext -- Exception encountered during context initialization - cancelling refresh attempt: java.lang.IllegalArgumentException: No visible constructors in class hello.core.beanfind.ApplicationContextSameBeanFindTest$SameBeanConfig

java.lang.IllegalArgumentException: No visible constructors in class hello.core.beanfind.ApplicationContextSameBeanFindTest$SameBeanConfig
public class ApplicationContextSameBeanFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);


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

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

    @Test
    @DisplayName("특정 타입 모두 조회")
    void findAllBeanType() {
        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();
        }
    }
}

왜 inner 클래스에는 static을 붙여줘야 할까?
1) Inner class (Non-static nested class) 는 Outer class 의 인스턴스화 이후 Inner class 의 인스턴스화가 가능하며

2) 두 인스턴스의 관계정보는 Inner class의 인스턴스 안에 만들어져 메모리 공간을 더 차지하며, 생성시간도 더 걸린다.

3) 더 심각한 문제는 Inner class 가 Outer class 인스턴스에 대한 참조를 갖고 있기 때문에, Garbage Collection 은 Outer class 의 인스턴스를 수거 대상으로 보지 않아 GC 의 대상에서 빠지게 된다. (더 쉽게 풀어쓰자면 inner class, outer class 두 인스턴스가 연결되어 있어서 outer class 인스턴스의 메모리를 못 뺏는 것)

4) 때문에 Outer class 를 참조할 일이 없다면, Nested class 는 static 을 붙여 무조건 static nested class 를 만들자!

출처
-> https://velog.io/@mooh2jj/Java-static-%EB%B3%80%EC%88%98-static-%EB%A9%94%EC%84%9C%EB%93%9C-%EA%B7%B8%EB%A6%AC%EA%B3%A0-static-%ED%81%B4%EB%9E%98%EC%8A%A4

스프링 빈 조회 - 상속 관계
부모타입으로 조회하면 자식 타입도 함께 조회
자바 객체의 최고인 Object 타입으로 조회하면 모든 스프링 빈을 조회

public class ApplicationContextExtendsFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

    @Configuration
    static class TestConfig {

        @Bean
        public DiscountPolicy rateDiscountPolicy() {
            return new RateDiscountPrice();
        }

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

    @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(RateDiscountPrice.class);
    }

    @Test
    @DisplayName("특정하위타입 조회")
    void findBeanByType(){
        RateDiscountPrice rateDiscountPrice = ac.getBean(RateDiscountPrice.class);
        assertThat(rateDiscountPrice).isInstanceOf(RateDiscountPrice.class);
    }

    @Test
    @DisplayName("부모티입으로 모두 조회")
    void findBeanByParentAllType(){
        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.keySet());
        }
    }

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

BeanFactory & ApplicationContext

BeanFactory
스프링 컨테이너의 최상위 인터페이스
스프링 빈을 관리하고 조회하는 담당
getBaen()

ApplicationContext
BeanFactory 기능을 모두 상속 받아서 제공
빈을 관리하고 검색하는 기능을 BeanFactory 제공, 둘 차이???
애플리케이션을 개발할 때는 빈을 관리하고 조회하는 기능은 물론이고, 수 많은 부가기능 필요

메시지소스를 활용한 국제화 기능
환경변수
애프릴케이션 이벤트
편리한 리소스 조회

스프링 빈 설정 메타정보 - BeanDefinition
스프링은 다양한 설정 지원을 어찌 하는 걸까???
쉽게 말해 역할과 구현을 개념적으로 나눈것



AnnotationConfigApplicationContext는 AnnontatedBeanDefinitionReader를 사용해서 AppConfig.class를 읽고 BeanDefinition을 생성

BeanDefinition 정보
BeanClassName : 생성할 빈의 클래스 명
factoryBeanName : 팩토리 역할의 빈을 사용할 경우 ex) appConfig
factoryMethodName : 빈을 생성할 펙토리에서 메서드 지정
Scope 싱글톤
lazyInit : 스프링 컨테이너를 생성할때 빈을 생성하는 것 X, 실제 빈을 사용할때 까지 생성을 지연
InitMethodName : 빈을 생성, 의존관계를 적용한 뒤 호출되는 초기화 메서드 명
DestroyMethodName : 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명
Constructor arguments, Properties: 의존관계 주입에서 사용한다. (자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)

public class BeanDefinitionTest {
    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){
                System.out.println("beanDefinition = " + beanDefinition);
                System.out.println("beanDefinitionName = " + beanDefinitionName);
            }
        }
    }
}

실무에서 사용할 일 거의 없다.
그냥 다양한 설정을 할 수 있구나를 이해하고 넘어간다.

profile
개발자를 향해 가는 중입니다~! 항상 겸손

0개의 댓글