Spring の原理を学ぶ (1): BeanFactory と ApplicationContext の原理と実装

目次

1. BeanFactory と ApplicationContext の関係

二、BeanFactoryの機能

三、ApplicationContextの機能

3.1 メッセージソース

3.2 リソースパターンリゾルバー

3.3 環境対応

3.4 アプリケーションイベントパブリッシャー

3.4.1 ApplicationEventPublisher 関数の経験

 3.4.2 イベントの用途とは

4 つ目は、BeanFactory の実装です。

4.1 DefaultListableBeanFactory

         4.2 BeanFactory のポストプロセッサ

        4.3 Bean ポストプロセッサ

        4.4 まとめ

5. ApplicationContext の実装

5.1 ClassPathXmlApplicationContext

5.1.1 使用

        5.1.2 原則

 5.2 FileSystemXmlApplicationContext

5.2.1 使用

5.2.2 原則

5.3 AnnotationConfigApplicationContext

5.3.1 使用

5.4 AnnotationConfigServletWebServerApplication


1. BeanFactory と ApplicationContext の関係

        まず、springboot のブート クラスを見てください。

@SpringBootApplication
public class A01 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
        SpringApplication.run(A01.class, args);
    }
}

        run メソッドの戻り値は springboot コンテナーです。見てみましょう。

ConfigurableApplicationContext context = SpringApplication.run(A01.class, args);

        では、ConfigurableApplicationContext とは何ですか? クラス図を見てみましょう。

        ConfigurableApplicationContext は ApplicationContext のサブクラスであり、ApplicationContext は間接的に BeanFactory を継承していることがわかります。

        BeanFactory は Spring のコア コンテナーであり、メインの ApplicationContext 実装は BeanFactory の機能を「結合」します。

二、BeanFactoryの機能

        まず、BeanFactory が持つインターフェースを見てください。

画像-20220323145908937

         一見、getBean メソッドしかないように見えますが、実際にはその実装クラスにも注目する必要があります。制御の反転、基本的な依存性注入、および Bean のライフサイクルまでのさまざまな機能が、その Bean によって提供されます。実装クラス。

        では、実装クラスの機能を見たい場合は、どこから始めればよいでしょうか。

        最初に、springboot のデフォルトの ConfigurableApplicationContext クラスで BeanFactory の実際のタイプを確認しましょう. コードは次のとおりです。

ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
//org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();

// 查看实际类型
// class org.springframework.beans.factory.support.DefaultListableBeanFactory
System.out.println(beanFactory.getClass());

        出力結果から、実際の型は DefaultListableBeanFactory であることがわかります (詳細は 4.1 を参照)。ここでは、BeanFactory の実装クラスである DefaultListableBeanFactory を分析の出発点として取り上げます。彼のクラス図:

画像-20220323150712761

        まず、DefaultListableBeanFactory の親クラスである DefaultSingletonBeanRegistry を見て、そのソース コードを見てみましょう。

画像-20220323150929451

         メンバー変数 singletonObjects があることがわかります. 実際, springboot のすべてのシングルトン オブジェクトはこの変数に格納されています. リフレクションを介して singletonObjects を取得し、それらを出力すると、すべてのシングルトン オブジェクトを確認できます.

三、ApplicationContextの機能

        ApplicationContext が BeanFactory のサブクラスであること、および BeanFactory の機能を学習したので、ApplicationContext が BeanFactory よりも多くの機能を持っていることに焦点を当てます。

画像-20220324115647260

         ご覧のとおり、BeanFactory からの継承に加えて、ApplicationContext は次の 4 つのクラスも継承します。

  • MessageSource: 国際化機能、多言語対応
  • ResourcePatternResolver: ワイルドカード マッチング リソース パス
  • EnvironmentCapable: 環境情報、システム環境変数、*.properties、*.application.yml およびその他の構成ファイルの値
  • ApplicationEventPublisher: イベント オブジェクトの公開

        これらの 4 つのカテゴリを個別に検討してみましょう。

3.1 メッセージソース

       MessageSource には国際化機能があり、多言語に対応しています。

       MessageSource に関連する国際化機能のファイルは、デフォルトで springboot の message で始まるファイルに配置されます. これらのファイルを最初にビルドしましょう:

        次に、これらのファイルで同じ名前のキーを定義します。たとえば、message_en.propertiesdefine in hi=hellomessages_ja.propertesdefine in hi=こんにちは、およびmessages_zhdefine inと、hi=你好これと異なる言語タイプに応じてコードでkey hi異なる値 を取得できるようにします。

        コードを記述したら、実行後に結果を確認できます。

画像-20220324181409040

        Locale.CHINA、Locale.ENGLISH などの値は、フロント エンドによって実際のプロジェクトのインターフェイスで使用される言語に解析されてから渡されます。

3.2 リソースパターンリゾルバー

        ResourcePatternResolver では、ワイルドカードを使用してリソース パスを一致させることができます。

        例 1:messagesクラスパスの先頭にある構成ファイルを取得します。

Resource[] resources = context.getResources("classpath:messages*.properties");
for (Resource resource : resources) {
    System.out.println(resource);
}

         例 2: jarspring 関連パッケージのspring.factories構成ファイルを取得する:

resources = context.getResources("classpath*:META-INF/spring.factories");
for (Resource resource : resources) {
    System.out.println(resource);
}

3.3 環境対応

        EnvironmentCapable は、環境変数、*.properties、*.application.yml およびその他の構成ファイル内の値など、システム環境情報またはシステム環境変数内の値を取得できます。

//获取系统环境变量中的java_home
System.out.println(context.getEnvironment().getProperty("java_home"));
//获取项目的application.yml中的server.port属性
System.out.println(context.getEnvironment().getProperty("server.port"));

3.4 アプリケーションイベントパブリッシャー

        ApplicationEventPublisher を使用して、イベントを発行できます。

3.4.1 ApplicationEventPublisher 関数の経験

        イベントを公開する機能を試すには、イベント送信クラス、イベント受信 (監視) クラス、イベント クラスの 3 つの部分を用意する必要があります。

        最初に、ApplicationEvent から継承するイベント クラスを見てください。

public class UserRegisteredEvent extends ApplicationEvent {
    public UserRegisteredEvent(Object source) {
        super(source);
    }
}

        次に、ユーザー登録イベントをリッスンするためのイベント受け入れ (監視) クラスを定義します。@Component アノテーションをクラスに追加する必要があり、クラスはspring管理に引き渡されます。春の任意のコンテナをリスナーとして使用できます。次に、イベントを処理するメソッドを定義します。パラメーターの型はイベント クラスのオブジェクトであり、@EventListenerアノテーションをメソッド ヘッダーに追加する必要があります。

@Component
@Slf4j
public class UserRegisteredListener {
    @EventListener
    public void userRegist(UserRegisteredEvent event) {
        System.out.println("UserRegisteredEvent...");
        log.debug("{}", event);
    }
}

        イベントを送信するための別のクラスを定義します。つまり、ApplicationEventPublisher のインスタンス オブジェクトを使用して、publishEvent メソッドを呼び出して送信します。渡されるパラメーターは、先ほど定義したイベント クラスです。

@Component
@Slf4j
public class UserService {
    @Autowired
    private ApplicationEventPublisher context;
    public void register(String username, String password) {
        log.debug("新用户注册,账号:" + username + ",密码:" + password);
        context.publishEvent(new UserRegisteredEvent(this));
    }
}

        次に、メインのスタートアップ クラスで呼び出します。

UserService userService = context.getBean(UserService.class);
userService.register("张三", "123456");

 3.4.2 イベントの用途とは

        イベントの主な機能はデカップリングです。

        たとえば、イベントを使用してユーザー登録機能を実行する場合、この機能には、イベントを送信するためのユーザー登録クラス UserService と、イベントを受信するためのユーザー登録監視クラス UserRegisteredListener があります。ユーザー登録後、ユーザーへのテキストメッセージの送信、ユーザーへの電子メールの送信、WeChat パブリックアカウントのユーザーへのリマインダーの送信など、さまざまなフォローアップ操作を行っているため。これには、システムに優れたスケーラビリティが必要です. UserService クラスと UserRegisteredListener クラスを結合することはできません. 上記のように、イベントを使用して、UserService クラスと UserRegisteredListener クラスの分離を実現できます: ユーザー登録クラス UserService が送信した後event の場合、異なるリスニング クラスを使用して受信することができ、異なるリスニング クラスは異なることを行うことができます。たとえば、UserRegisteredListener1 を使用してテキスト メッセージをユーザーに送信し、UserRegisteredListener2 を使用してユーザーに電子メールを送信できます。

        イベントを使用したデカップリングは、デカップリングの新しい方法ですが、AOP との違いは何ですか? これは考える価値があります。

4 つ目は、BeanFactory の実装です。

4.1 DefaultListableBeanFactory

        BeanFactory の実装クラスは多数あります。重要な実装クラスを把握して確認する必要があります。このクラスは DefaultListableBeanFactory です。 

         2つ目(BeanFactoryの機能)で述べたように、Springの底辺でのエンティティクラスの作成はDefaultListableBeanFactoryに依存しているため、BeanFactoryの実装クラスの中で最も重要な存在です。このクラスを使用して、Spring のプロセスをシミュレートし、DefaultListableBeanFactory を使用して他のエンティティ クラス オブジェクトを作成する必要があります。

public class TestBeanFactory {
    public static void main(String[] args) {

        //先创建bean工厂,刚创建的时候是没有任何bean的,我们需要往里面添加bean的定义
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        // bean 的定义(即bean的一些描述信息,包含class:bean是哪个类,scope:单例还是多例,初始化、销毁方法等)
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();
        //把beanDefinition这个bean定义注册进bean工厂,第一个参数是给它起的名字
        beanFactory.registerBeanDefinition("config", beanDefinition);

        
        // 打印BeanFactory中Bean
        for (String name : beanFactory.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }

    @Configuration
    static class Config {
        @Bean
        public Bean1 bean1() {
            return new Bean1();
        }

        @Bean
        public Bean2 bean2() {
            return new Bean2();
        }
    }

    // bean1依赖于bean2
    @Slf4j
    static class Bean1 {
        @Autowired
        private Bean2 bean2;

        public Bean2 getBean2() {
            return bean2;
        }

        public Bean1() {
            log.debug("构造 Bean1()");
        }
    }

    @Slf4j
    static class Bean2 {
        public Bean2() {
            log.debug("构造 Bean2()");
        }
    }

    interface Inter {

    }
}

        この時点で、Bean ファクトリにある Bean の数を出力します。結果は 1 つだけです。これは、登録したばかりの構成です。

        次に問題が発生します。Spring の実際の知識からわかっています: @Configuration と @Bean が追加されると、これらの Bean がコンテナーに登録されます; つまり、この時点で Bean ファクトリのすべての Bean を出力すると、 config だけでなく、bean1 と bean2 が表示されるはずです。この時点で、確立できる説明は 1 つだけです。@Configuration も @Bean も解決されていません。では、これらの注釈を解析する機能を提供しているのは誰でしょうか?

         4.2 BeanFactory のポストプロセッサ

        BeanFactory 自体は多くの機能を実装しておらず、その機能の多くは BeanFactory ポストプロセッサによって拡張されています。

AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

        上記のツール クラスを使用して、いくつかの一般的なポスト プロセッサを Bean ファクトリに追加します。この時点で、Bean ファクトリ内のすべての Bean を出力します。

        名前から大まかに推測できるより多くのポストプロセッサがあることがわかります.それらは@Configuration、@Autowiredを扱います...今、それらはBeanファクトリに追加されたばかりで、動作させる必要があります. .

public class TestBeanFactory {

    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        // bean 的定义(class, scope, 初始化, 销毁)
        AbstractBeanDefinition beanDefinition =
                BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();
        beanFactory.registerBeanDefinition("config", beanDefinition);

        // 给 BeanFactory 添加一些常用的后处理器
        AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

    beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(beanFactoryPostProcessor -> {
            beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
        });

        // 打印BeanFactory中Bean
        for (String name : beanFactory.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }

    @Configuration
    static class Config {
        @Bean
        public Bean1 bean1() {
            return new Bean1();
        }

        @Bean
        public Bean2 bean2() {
            return new Bean2();
        }
    }

    static class Bean1 {
        private static final Logger log = LoggerFactory.getLogger(Bean1.class);

        public Bean1() {
            log.debug("构造 Bean1()");
        }

        @Autowired
        private Bean2 bean2;

        public Bean2 getBean2() {
            return bean2;
        }
    }

    static class Bean2 {
        private static final Logger log = LoggerFactory.getLogger(Bean2.class);

        public Bean2() {
            log.debug("构造 Bean2()");
        }
    }
}

        この時点ですべての Bean を再度印刷すると、bean1 と bean2 が表示されていることがわかります。

        4.3 Bean ポストプロセッサ

        in 4.2, we added some post-processors. たとえば、 internalConfigurationAnnotationProcessor プロセス @Configuration はBeanFactory のポスト プロセッサに属し、 internalAutowiredAnnotationProcessor と internalCommonAnnotationProcessor はBean ポスト プロセッサに属し、Bean ライフ サイクルの各段階に提供されます。 @Autowired を解析するための internalAutowiredAnnotationProcessor や @Resource を解析するための internalCommonAnnotationProcessor などの拡張機能。

        @Autowired および @Resource アノテーションも機能させる必要がある場合は、次のことを行う必要があります。

// Bean 后处理器, 针对 bean 的生命周期的各个阶段提供扩展, 例如 @Autowired @Resource ...
        beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream()
                .sorted(beanFactory.getDependencyComparator())
                .forEach(beanPostProcessor -> {
            System.out.println(">>>>" + beanPostProcessor);
            beanFactory.addBeanPostProcessor(beanPostProcessor);
        });

        このようにして、 @Autowired および @Resource アノテーションが機能します。ここで getBeansOfType() メソッドによって渡されるパラメーターは、Bean のポストプロセッサーである BeanPostProcessor.class であることに注意してください。

        4.4 まとめ

        BeanFactory は比較的基本的なクラスで、多くの機能はありません。次のことは行いません。

  • BeanFactory ポストプロセッサを積極的に呼び出さない
  • Bean ポストプロセッサーを積極的に追加しない
  • シングルトンを積極的に初期化しません
  • #{}、${} などは解析されません。

5. ApplicationContext の実装

        最初に、ApplicationContext の実装クラスを見てください。

画像-20220325174005606

        今日は、さらに 4 つの重要な実装クラスを紹介します。

  • ClassPathXmlApplicationContext
  • FileSystemXmlApplicationContext
  • AnnotationConfigApplicationContext
  • AnnotationConfigServletWebServerApplication

5.1 ClassPathXmlApplicationContext

        より古典的なコンテナーは、クラスパス (クラスパス) の下の xml 形式の構成ファイルに基づいて ApplicationContext を作成します。

5.1.1 使用

        テストクラスを作成する 

private static void testClassPathXmlApplicationContext() {
    ClassPathXmlApplicationContext context = 
                                    new ClassPathXmlApplicationContext("a02.xml");

    //看一下ApplicationContext中有多少bean
    for (String name : context.getBeanDefinitionNames()) {
        System.out.println(name);
    }

    //看看bean2中有没有成功注入bean1
    System.out.println(context.getBean(Bean2.class).getBean1());
}

static class Bean1 {
}

static class Bean2 {

    private Bean1 bean1;

    public void setBean1(Bean1 bean1) {
        this.bean1 = bean1;
    }

    public Bean1 getBean1() {
        return bean1;
    }
}

         xml 構成ファイルを作成し、ファイルに Bean を定義する

<?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">

    <!-- 控制反转, 让 bean1 被 Spring 容器管理 -->
    <bean id="bean1" class="com.itheima.a02.A02.Bean1"/>

    <!-- 控制反转, 让 bean2 被 Spring 容器管理 -->
    <bean id="bean2" class="com.itheima.a02.A02.Bean2">
        <!-- 依赖注入, 建立与 bean1 的依赖关系 -->
        <property name="bean1" ref="bean1"/>
    </bean>
</beans>

        操作結果:

  

5.1.2 原則

        xml ファイルをロードするプロセスをシミュレートすることで、原理を理解できます。まず DefaultListableBeanFactory を初期化し、次に XmlBeanDefinitionReader を介して xml ファイルから Bean 構成情報を読み取り、これらの Bean を Bean ファクトリにロードします。 

public static void main(String[] args) {
        //先实现DefaultListableBeanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        System.out.println("读取之前...");
        for (String name : beanFactory.getBeanDefinitionNames()) {
            System.out.println(name);
        }

        System.out.println("读取之后...");
        //然后到xml文件中读取bean的定义信息
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions(new ClassPathResource("a02.xml"));
        for (String name : beanFactory.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }

 5.2 FileSystemXmlApplicationContext

        ディスクパス配下のxml形式の設定ファイルを元にApplicationContextを作成します。

5.2.1 使用

        テスト クラスを作成します。

private static void testFileSystemXmlApplicationContext() {
        FileSystemXmlApplicationContext context =
                new FileSystemXmlApplicationContext(
                        "src\\main\\resources\\a02.xml");
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }

        //看看bean2中有没有成功注入bean1
        System.out.println(context.getBean(Bean2.class).getBean1());
    }

static class Bean1 {
}

static class Bean2 {

    private Bean1 bean1;

    public void setBean1(Bean1 bean1) {
        this.bean1 = bean1;
    }

    public Bean1 getBean1() {
        return bean1;
    }
}

        xml ファイルは 5.1.1 と同じです。実行結果も5.1.1と同じ 

5.2.2 原則

        xml ファイルをロードするプロセスをシミュレートすることで、原理を理解できます。まず DefaultListableBeanFactory を初期化し、次に XmlBeanDefinitionReader を介して xml ファイルから Bean 構成情報を読み取り、これらの Bean を Bean ファクトリにロードします。  

public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        System.out.println("读取之前...");
        for (String name : beanFactory.getBeanDefinitionNames()) {
            System.out.println(name);
        }

        System.out.println("读取之后...");
        //然后到xml文件中读取bean的定义信息
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions(new FileSystemResource("src\\main\\resources\\a02.xml"));
        for (String name : beanFactory.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }

5.3 AnnotationConfigApplicationContext

5.3.1 使用

        より古典的なコンテナーは、Java 構成クラスに基づいて ApplicationContext を作成します。 

private static void testAnnotationConfigApplicationContext() {
    AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(Config.class);

    for (String name : context.getBeanDefinitionNames()) {
        System.out.println(name);
    }

    System.out.println(context.getBean(Bean2.class).getBean1());
}

@Configuration
static class Config {
    @Bean
    public Bean1 bean1() {
        return new Bean1();
    }

    @Bean
    public Bean2 bean2(Bean1 bean1) {
        Bean2 bean2 = new Bean2();
        bean2.setBean1(bean1);
        return bean2;
    }
}

static class Bean1 {
}

static class Bean2 {

    private Bean1 bean1;

    public void setBean1(Bean1 bean1) {
        this.bean1 = bean1;
    }

    public Bean1 getBean1() {
        return bean1;
    }
}

        操作結果:

         5.1.1 および 5.2.1 とは結果が異なることがわかります。これは、構成クラス Config もデフォルトで Bean ファクトリに Bean を注入するためです。さらに、AnnotationConfigApplicationContext も 5 つのポストプロセッサを自動的に追加します。

5.4 AnnotationConfigServletWebServerApplication

        より古典的なコンテナーは、Web 環境の Java 構成クラスに基づいて ApplicationContext を作成します。

        それを使用するテストクラスを作成します。

    private static void testAnnotationConfigServletWebServerApplicationContext() {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }

    @Configuration
    static class WebConfig {
        //tomcat容器
        @Bean
        public ServletWebServerFactory servletWebServerFactory(){
            return new TomcatServletWebServerFactory();
        }
        //前控制器
        @Bean
        public DispatcherServlet dispatcherServlet() {
            return new DispatcherServlet();
        }
        //让前控制器运行在Tomcat容器中
        @Bean
        public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
            return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        }
        //控制器
        @Bean("/hello")
        public Controller controller1() {
            return (request, response) -> {
                response.getWriter().print("hello");
                return null;
            };
        }
    }

        背景にたくさんの豆がプリントされているのがわかります。

        ここから、次のことを学ぶことができます。

  • springboot には Tomcat が組み込まれているため、Tomcat コンテナーを手動で追加する必要はなく、Bean を実行することもできます
  • すべての springboot リクエストは、dispatchServlet (フロント コントローラー) を通過してから、独自のコントローラーに移動します。
  • DispatcherServlet は、DispatcherServletRegistrationBean を介して Tomcat に登録できます。

おすすめ

転載: blog.csdn.net/m0_49499183/article/details/129900726