Spring DI, IOC, Bean


DIとは何ですか.
依存注入とは.依存とは,あるクラスが別のクラスのメソッドを実行する際の依存である.例えば、自動車はホスト、エンジン、タイヤオブジェクトがあってこそ作動することができ、ホストとエンジン、タイヤの属性と方法が必要であるため、依存関係と言える.
あるオブジェクト(Aオブジェクト)を直接作成して使用する依存オブジェクト(B,Cオブジェクト)ではなく、注入(new B()、new C()で使用する方法です.
IOCとは?
依存関係のクラスはnewを直接使用してオブジェクトを注入するのではなく,制御反転(Inversion of Control)と呼ばれるジェネレータを使用して注入を受け入れる.これは,メソッドやオブジェクト呼び出し操作の一方(開発者)ではなく,外部によって決定されることを意味する.オブジェクトの依存性を逆転させることで、オブジェクト間の結合度を低減し、柔軟なコードを書くことができ、可読性とコードの繰り返しとメンテナンスを容易にします.
  • 既存(開発者がnewを直接使用して依存性を作成)
    :オブジェクトの作成->クラス内の依存オブジェクトの作成->依存オブジェクトメソッドの呼び出し
  • スプリング(外部からオブジェクトを作成することによって作成されたオブジェクトを注入します).
    :オブジェクトの作成->依存オブジェクトの注入(ユーザ制御ではなく委任スプリング)->依存オブジェクトメソッドの呼び出し
  • スプリングIOC容器
    bean設定ソースからbean定義を取得し、beanを構成して提供します.Benの依存関係を確立し,最も重要なインタフェースはBenFactory,ApplicationContextである.
  • BeanFactory:スプリングbeanオブジェクトを作成および管理するスプリングコンテナの最上位インタフェース.BeanFactoryコンテナは、クライアント要求時にオブジェクトを作成するのではなく、起動時にオブジェクトを作成します.
  • ApplicationContext:BenFactoryのインタフェースを継承し、起動時に登録されたBenオブジェクトをスキャンしてオブジェクト化します.スプリングコンテナと呼ばれています.スプリングコントローラは、@Configuration付きクラスを構成情報として使用します.ここで,@Beanと書かれたすべてのメソッドを呼び出し,返されたオブジェクトをスプリングコンテナに登録する.このように、スプリング容器に登録されているオブジェクトをスプリングbeanと呼ぶ.
  • スプリングが動作すると、スプリングはすべての依存オブジェクトを作成し、必要な位置に注入するので、beanは単一トーンモードの特徴を持っています.@各beanに添付されたメソッドにspring beanが既に存在する場合は、既存の空を返します.springbeanがない場合はspringbeanとして生成され、登録され、返されたコードが動的に作成されます.
    @BenはSpring Binとして登録されますが、単一の色調であることは保証されません.@単一の色調を保証するように構成されています.
        @Test
        @DisplayName("스프링 컨테이너와 싱글톤")
        void springContainer() {
    
            ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    
            MemberService memberService1 = ac.getBean("memberService", MemberService.class);
            MemberService memberService2 = ac.getBean("memberService", MemberService.class);
    
            // 참조값이 같은 것을 확인
            System.out.println("memberService1 = " + memberService1);
            System.out.println("memberService2 = " + memberService2);
    
    	// isSameAs : == , 인스턴스 비교
            // isEqualTo : equals, 값 비교
            assertThat(memberService1).isSameAs(memberService2);
        }
    Bean
    SpringIOCコンテナ管理の対象.プロジェクトがスプリング上で実行されると、ユーザーがbeanを使用して管理するオブジェクトの作成と破棄に関連するタスクが自動的に実行されます.オブジェクトの作成場所はbeanコンテナと呼ばれます.スプリングシートはオブジェクトを作成し、依存関係注入が完了した後に必要なデータを使用する準備ができているため、初期化操作は依存関係注入が完了した後に呼び出さなければなりません.
    Spring IOCコンテナに登録されているbeanたちの依存性管理がより容易になり、モノトーンの形態となっている.Springbeanは@bean付きメソッドの名前をspringbeanの名前として使用します.(@Bean(name="xxx")はbeanに直接名前を付けることができます.)
    getBean()メソッドを使用してspringbeanを検索できます.beanの名前は常に異なる名前を付けなければならない.同じ名前を付けると、他のbeanが無視されたり、既存のbeanが上書きされたり、設定に従ってエラーが発生したりする可能性があります.
    (bean定義の場合、コンテナにはオブジェクトが1つしか存在しません.スプリングシートは常に無状態に設計してください.)
    class ApplicationContextInfoTest {
    
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    
        @Test
        @DisplayName("모든 빈 출력하기")
        void findAllBean() {
            // getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회한다.
            String[] beanDefinitionNames = ac.getBeanDefinitionNames();
            for (String beanDefinitionName : beanDefinitionNames) {
                // getBean() : 빈 이름으로 빈 객체를 조회한다.
                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);
    
                // 스프링 내부에서 등록한 빈이 아니라 애플리케이션에서 개발하기 위해 등록한 빈 혹은 외부 라이브러리
                // 스프링이 내부에서 사용하는 빈은 getRole()로 구분할 수 있다.
                // ROLE_APPLICATION : 직접 등록한 애플리케이션 빈
                // ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈
                if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
                    Object bean = ac.getBean(beanDefinitionName);
                    System.out.println("name = " + beanDefinitionName + " object = " + bean);
                }
            }
        }
    
    }
    検索スプリングbean
    デフォルトクエリー
  • class ApplicationContextBasicFindTest {
    
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    
        @Test
        @DisplayName("빈 이름으로 조회")
        void findBeanByName() {
            MemberService memberService = ac.getBean("memberService", MemberService.class);
            // System.out.println("memberService = " + memberService);
            // System.out.println("memberService.getClass() = " + memberService.getClass());
            assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
        }
    
        @Test
        @DisplayName("이름 없이 타입으로만 조회")
        void findBeanByType() {
            MemberService memberService = ac.getBean(MemberService.class);
            assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
        }
    
        @Test
        @DisplayName("구체 타입으로 조회")
        void findBeanByConcreteType() {
            MemberService memberService = ac.getBean("memberService", MemberServiceImpl.class);
            assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
        }
    
        @Test
        @DisplayName("빈 이름으로 조회X")
        void findBeanByNameX() {
            // MemberService xxxx = ac.getBean("xxxx", MemberService.class);
            // ac.getBean 실행 시 NoSuchBeanDefinitionException 예외 실행
            assertThrows(NoSuchBeanDefinitionException.class, () -> ac.getBean("xxxx", MemberService.class));
        }
    }
  • 同じタイプが2つより多い場合にクエリ
  • class ApplicationContextSameBeanFindTest {
    
        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();
            }
        }
    }
  • 関係継承時クエリー
  • 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));
            }
        }
    
        @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();
            }
        }
    
    
    }
    スプリングから依存性を注入する方法
    1)ジェネレータによる注入依存性
    ユーザーコントローラでは、ユーザー情報レポートと受注レポートの依存性は、作成者によって注入されます.
    public UserController(UserInfoRepository userInfoService, OrderRepository orderService) {
    		this.userName = userInfoService;
    		this.orderNumber = orderService;
    	}
    2)フィールドに注入を受ける場合
    @Autowired:必要な依存オブジェクトのタイプに対応する空を検索して入力します(ジェネレータ/setter/フィールド).
    @Autowired
    private UserInfoRepository userName; 
    private OrderRepository orderNumber;
    // UserInfoRepository, OrderRepository가 이미 Bean으로 등록된 상태에서, userName, orderNumber에게 의존성을 주입해 달라는 의미이다.
    // 필드가 final 변수일 경우 주입받을 수 없다.
    3)setter方式で入力した場合
    @Autowired
    public void setUserName(UserInfoRepository userName){
    	this.userName = userName;
    }
    
    @Autowired
    public void setOrderNumber(OrderRepository orderNumber) {
    	this.orderNumber = orderNumber;
    }
    3つの方法では、1つ目の方法はspring referenceとして推奨されます.これは、必要なreferenceをコンストラクション関数のパラメータとして使用する必要があり、インスタンスを作成できないためです.
  • 依存関係注入を使用すると、クライアントコードを変更せずにクライアント呼び出しのターゲットタイプインスタンスを変更できます.静的クラス依存関係を変更しない場合は、動的オブジェクトインスタンス依存関係を簡単に変更できます.
  • *参考資料
    https://devlog-wjdrbs96.tistory.com/165
    https://velog.io/@gillog/Spring-DIDependency-Injection
    https://chanhuiseok.github.io/posts/spring-5/
    例のスプリング入門(白奇仙様)
    スプリングコア原理-基本編(金英漢)