たんトンコンテナ


Webアプリケーションとモノトーン


Springの誕生の目的は、企業のオンラインサービス技術をサポートすることです.ほとんどのSpringアプリケーションはWebアプリケーションで、通常は複数のお客様が同時に要求します.これはAppConfig.classコードの面でこのような同時要求を考慮する必要があるタイミングである.

ばねのない純DI容器AppConfigは、要求されるたびに新しいオブジェクトを生成する.顧客トラフィックが毎秒100の場合、毎秒100個のオブジェクトが生成され、破棄され、メモリが大幅に浪費されます.
解決策は、1つのオブジェクトのみを作成および共有するシングルトーンモードです.
package hello.core.singletion;

import hello.core.AppConfig;
import hello.core.member.MemberService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

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

public class SingletonTest {

    @Test
    @DisplayName("스프링 없는 순수한 DI 컨테이너")
    void pureContainer() {
        AppConfig appConfig = new AppConfig();
        // 1. 조회: 호출할 때 마다 객체를 생성
        MemberService memberService1 = appConfig.memberService();

        // 2. 조회: 호출할 때 마다 객체를 생성
        MemberService memberService2 = appConfig.memberService();

        // 참조값이 다른 것을 확인
        System.out.println("memberService1 = " + memberService1);
        System.out.println("memberService2 = " + memberService2);

        // memberService1 != memberService2
        assertThat(memberService1).isNotSameAs(memberService2);
    }
}

上記のテストコードの結果は、クエリの空の参照アドレスが異なることです.

モノトーンモード


これは、クラスのインスタンスが1つしか生成されないことを保証する設計モードです.
複数のオブジェクトインスタンスの作成は防止する必要がありますが、外部でnewキーを任意に使用することを防止するには、プライベートジェネレータを使用する必要があります.
package hello.core.singletion;

public class SingletonService {

    // 1. static 영역에 객체를 딱 1개만 생성해둔다.
    private static final SingletonService instance = new SingletonService();
    
    // 2. public으로 열어서 객체 인스턴스가 필요하면 이 static 메서드를 통해서만 조회하도록 허용한다.
    public static SingletonService getInstance() {
        return instance;
    }
    
    // 3. 생성자를 private로 선언해서 외부에서 new 키워드를 사용한 객체 생성을 못하게 막는다.
    private SingletonService() {}
    
    public void logic() {
        System.out.println("싱글톤 객체 로직 호출");
    }
}
  • 静的領域にオブジェクトインスタンスを事前に作成してアップロードします.
  • オブジェクトインスタンスが必要な場合は、getInstance()メソッドのみでクエリーできます.このメソッドを呼び出すと、常に同じインスタンスが返されます.
  • は、1つのオブジェクトインスタンスのみが存在する必要があるため、ジェネレータが新しいキーワードで外部にオブジェクトインスタンスを作成することをプライベートで阻止する.
  • モノトーンモードを使用すると、お客様が要求するたびにオブジェクトを作成するのではなく、作成したオブジェクトを共有できます.しかし、以下のような問題がある.

    モノトーンモードの問題

  • モノトーンモードを実現するコード自体はたくさんあります.
  • 依存関係のため、クライアントは特定のクラスに依存します->DIP違反.
  • クライアントは、OCPの原則に違反する特定のクラスに依存する可能性が高い.
  • をテストするのは難しいです.
  • の内部プロパティを変更または初期化するのは難しいです.
  • プライベートジェネレータでサブクラスを作成するのは難しいです.
  • 結論は柔軟性が悪い.
  • anti-patternとも呼ばれる.
  • しかしながら、スプリングコンテナは、モノトーンモードの問題を解決するとともに、オブジェクトインスタンスをモノクロトーンとして管理する.

    たんトンコンテナ

  • スプリングコンテナは、モノトーンモードではなく、モノトーンでオブジェクトインスタンスを管理します.
  • スプリングコンテナは単トンコンテナの役割を果たす.(モノトーンレジストリ)
  • スプリングコンテナのこれらの機能により、単一の色調モードのすべての欠点を解決しながら、オブジェクトを単一の色調に保つことができる.
  • モノトーンパターンは、乱雑なコードを必要としない.
  • DIP、OCP、テスト、およびプライベート作成者は、単一のトーンを使用できます.
  • @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);
        
        // memberService1 == memberService2
        assertThat(memberService1).isSameAs(memberService2);
    }
    単色調コンテナを適用すると、多くのユーザーがリクエストのたびにインスタンスを共有し、効率的に再使用できます.

    モノトーンの注意点


    //この内容は実際の仕事で特に重要です.
    単一トーンモードでもスプリングなどの単一トーンコンテナでも、1つのオブジェクトインスタンスを作成して1つのオブジェクトインスタンスを共有する単一トーン方式は、複数のクライアントが同じオブジェクトインスタンスを共有するため、単一トーンオブジェクトを保持状態に設計することはできません(stateful).ステータスなし(stateless)に設計する必要があります.
  • 特定のクライアントに依存フィールドはありません.
  • 特定のクライアントは、値を変更できるフィールドを持ってはいけません.
  • は、できるだけ読み取り専用にしてください.
  • フィールドではなく、Javaで共有されていない領域変数、パラメータ、ThreadLocalなどを使用する必要があります.
  • スプリングシートのフィールドに共有値を設定すると、重大な障害が発生する可能性があります.
    package hello.core.singleton;
    
    public class StatefulService {
        private int price; // 상태를 유지하는 필드
        
        public void order(String name, int price) {
            System.out.println("name = " + name + " price = " + price);
            this.price = price; // 여기가 문제가 됨.
        }
        
        public int getPrice() {
            return price;
        }
    }
    package hello.core.singleton;
    
    import org.assertj.core.api.Assertions;
    import org.junit.jupiter.api.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.Bean;
    
    public class StatefulServiceTest {
    
        @Test
        void statefulServiceSingleton() {
            ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
            StatefulService statefulService1 = ac.getBean("statefulService", StatefulService.class);
            StatefulService statefulService2 = ac.getBean("statefulService", StatefulService.class);
            
            // ThreadA: A 사용자 10000원 주문
            statefulService1.order("userA", 10000);
            // ThreadB: B 사용자 20000원 주문
            statefulService2.order("userB", 20000);
            
            // ThreadA: A 사용자 주문 금액 조회
            int price = statefulService1.getPrice();
            // ThreadA: A 사용자는 10000원을 기대했지만, 기대와 다르게 20000원 출력
            System.out.println("price = " + price);
            
            Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
        }
        
        static class TestConfig {
            @Bean
            public StatefulService statefulService() {
                return new StatefulService();
            }
        }
    }

    //画像ソース:たんトンコンテナStatefulServicepriceフィールドは共有フィールドであり、特定のクライアントが値を変更します.その結果、ユーザーAの注文金額は10000ウォンではなく、20000ウォンだった.このように、フィールドの共有に注意してください.スプリングシートは常に無状態に設計する必要があります.

    @構成とモノトーン

    @Configuration
    public class AppConfig {
        
        @Bean
        public MemberService memberService() {
            return new MemberServiceImpl(memberRepository());
        }
        
        @Bean
        public OrderService orderService() {
            return new OrderServiceImpl(memberRepository(), discountPolicy());
        }
        
        @Bean
        public MemberRepository memberRepository() {
            return new MemoryMemberRepository();
        }
        
        @Bean
        public DiscountPolicy discountPolicy() {
            return new RateDiscountPolicy();
        }
    }
    memberService beanを作成するコードから、memberRepository()が呼び出されます.このメソッドを呼び出すとnew MemoryMemberRepository()が呼び出されます.
    orderService binを作成するコードも同様に2479142が好きです.このメソッドを呼び出すとmemberRepository()が呼び出されます.
    その結果,それぞれ2つの異なるnew MemoryMemberRepository()が生成され,単色調は破られたようだ.
    package hello.core.singletion;
    
    import hello.core.AppConfig;
    import hello.core.member.MemberRepository;
    import hello.core.member.MemberServiceImpl;
    import hello.core.order.OrderServiceImpl;
    import org.assertj.core.api.Assertions;
    import org.junit.jupiter.api.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class ConfigurationSingletonTest {
    
        @Test
        void configurationTest() {
            ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    
            MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
            OrderServiceImpl orderService = ac.getBean("orderService", OrderServiceImpl.class);
            MemberRepository memberRepository = ac.getBean("memberRepository", MemberRepository.class);
    
            MemberRepository memberRepository1 = memberService.getMemberRepository();
            MemberRepository memberRepository2 = orderService.getMemberRepository();
    
            // 모두 같은 인스턴스를 참고하고 있다.
            System.out.println("memberService -> memberRepository = " + memberRepository1);
            System.out.println("orderService -> memberRepository = " + memberRepository2);
            System.out.println("memberRepository = " + memberRepository);
    
            // 모두 같은 인스턴스를 참고하고 있다.
            Assertions.assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);
            Assertions.assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);
        }
    }
    上記のテストコードを表示すると、すべてのメンバーのRepositoryインスタンスが同じインスタンスを共有します.
    ばね容器はそれぞれMemoryMemberRepositoryを呼び出してばね片を生成するので、@Beanは合計3回呼び出したように見える.
  • スプリング容器は、スプリングボックスに登録するmemberRepository()が付加された@Bean
  • である.
  • メンバー・サービス()論理コールmemberRepository()
  • orderService()論理コールmemberRepository()
  • しかし、結局1回しか呼び出さなかった.

    @構成とバイトコード操作の魔法


    以上の疑問点に対する正解はmemberRepository()言語テストにあります.
    スプリング操作クラスのバイトコードのライブラリを使用します.
    @Test
    void configurationDeep() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        
        // AppConfig도 스프링 빈으로 등록된다.
        AppConfig bean = ac.getBean(AppConfig.class);
    
        System.out.println("bean = " + bean.getClass());
        // 출력: bean = class hello.core.AppConfig&&EnhancerBySpringCGLIB&&bd479d70
    }
    純粋なクラスの場合は、次のように出力します.@Configurationしかし,クラス名にxxxCGIBを追加すると,かなり複雑な結果が出力されることが分かる.これは私が作成したクラスではなく、springがCGIBというバイトコード操作ライブラリを使用してAppConfigクラスを継承する他のクラスを作成し、他のクラスをspring beanとして登録します.
    //この部分は白奇仙のもっとjava、コードを操作するいろいろな方法。に詳しく記載されているかもしれません.
    class hello.core.AppConfigが貼付された各方法については、既にスプリング空孔が存在する場合、存在する空孔が戻され、スプリング空孔が存在しない場合、スプリング空孔として生成され、登録され、返されるコードが動的に生成される.そのため、単調を保証することができます.

    @Configurationを適用せずに@Beanのみを適用するとどうなりますか?


    この場合、出力結果は@Beanとなる.
    AppConfigはCGIB技術がなく、純AppConfigでSpringBinに登録されていることが確認できます.
    call AppConfig.memberService
    call AppConfig.memberRepository
    call AppConfig.orderService
    call AppConfig.memberRepository
    call AppConfig.memberRepository
    以上の出力結果から、MemberRepositoryが合計3回呼び出されたことが判明した.これで、モノトーンが崩れてしまいます.
    まとめてみるとbean = class hello.core.AppConfigだけでスプリングシートとして登録できますが、単調である保証はありません.
    したがって、@Beanスプリング設定情報は常に使用されるべきである.

    References

  • スプリングコア原理-基本編