Springの@Configurationと@Componentの違いと原理の詳細な説明

1.背景

の人気により、アノテーション構成の開発は誰からも支持されるようになり、開発に基づいた煩わしい構成Spring Bootには別れを告げましたまず、構成アノテーションの内部定義を簡単に理解しましょう。機能的に言​​えば、これらのアノテーションは確かにさまざまな機能を担当しますが、本質的にはすべて構成アノテーションとして内部で処理されます。SpringXMLSpring@Component、@Configuration、@Bean、@ImportSpring

成熟したフレームワークでは、シンプルで多様な構成が重要であり、Springそれも重要です。Spring構成開発プロセスの観点から見ると、全体的な構成方法は、比較的「独創的な」段階から、現在では非常に「スマート」な段階に移行しています。この期間に行われた努力は非常に大きく、XML から自動アセンブリ、Spring から Spring Boot、@Component から @Configuration および @Conditional に至るまで、Spring は今日まで発展し、ますます使いやすくなっています。,Springまた、多くの詳細が隠されているため、今日は @Component と @Configuration を一緒に見てみましょう。

通常の Spring 開発作業では、基本的に設定アノテーション、特に@Component と@Configurationを使用します。もちろん、Spring では他のアノテーションを使用してクラスを設定クラスとしてマークすることもできます。これは広義の設定クラスの概念です。 @Component と @Configuration は開発作業に密接に関係しているため、ここでは @Component と @Configuration についてのみ説明します。次に、次の質問、  @Component と @Configuration の違いについて説明します。

2. @Component と @Configuration の使用

2.1 アノテーションの定義

2 つの違いについて説明する前に、2 つのアノテーションの定義を見てみましょう。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
​
  /**
   * The value may indicate a suggestion for a logical component name,
   * to be turned into a Spring bean in case of an autodetected component.
   * @return the suggested component name, if any (or empty String otherwise)
   */
  String value() default "";
​
}
复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
​
  @AliasFor(annotation = Component.class)
  String value() default "";
​
  boolean proxyBeanMethods() default true;
​
}
复制代码

定義から、@Configuration アノテーションは本質的に @Component であるため、@ComponentScan は @Configuration アノテーションが付けられたクラスをスキャンできます。

2.2 アノテーションの使用法

次に、日常の開発における 2 つのアノテーションの使用法を見てみましょう。これら 2 つのアノテーションを使用して、クラスを構成クラスとしてマークします。

@Configuration
public class AppConfig {
}
​
@Component
public class AppConfig {
}
复制代码

上記のプログラムの場合、Spring は設定クラスとして扱って処理することになりますが、明確にしておく必要がある概念があり、それは Spring では実際には設定クラスが分類されており、大きく 2 つのカテゴリに分けることができます。は LITE モード、もう 1 つのタイプは FULL モードと呼ばれます。上記の注釈に対応して、@Component は LITE タイプ、@Configuration は FULL タイプです。これら 2 つの構成タイプを理解するにはどうすればよいですか? まずはこのプログラムを見てみましょう。

@Component実装構成クラスを使用する場合:

@Component
public class AppConfig {
  @Bean
  public Foo foo() {
    System.out.println("foo() invoked...");
    Foo foo = new Foo();
    System.out.println("foo() 方法的 foo hashcode: " + foo.hashCode());
    return foo;
  }
​
  @Bean
  public Eoo eoo() {
    System.out.println("eoo() invoked...");
    Foo foo = foo();
    System.out.println("eoo() 方法的 foo hashcode: "+ foo.hashCode());
    return new Eoo();
  }
​
  public static void main(String[] args) {
    AnnotationConfigApplicationContext context =
        new AnnotationConfigApplicationContext(AppConfig.class);
  }
}
复制代码

実行結果は以下の通りです。

foo() invoked...
foo() 方法的 foo hashcode: 815992954
eoo() invoked...
foo() invoked...
foo() 方法的 foo hashcode: 868737467
eoo() 方法的 foo hashcode: 868737467
复制代码

foo()結果から、メソッドが 2 回実行され、1 回は Bean メソッドによって、もう 1 回はeoo()呼び出しによって実行されるため、2 回生成されるオブジェクトがfoo異なることがわかります。これは皆さんの予想どおりですが、@Configurationアノテーション構成クラスを使用すると、実行結果は次のようになります。

foo() invoked...
foo() 方法的 foo hashcode: 849373393
eoo() invoked...
eoo() 方法的 foo hashcode: 849373393
复制代码

foo()ここでは、メソッドが 1 回だけ実行され、eoo()メソッド呼び出しfoo()によって生成された foo オブジェクトが同じであることがわかります。これは@Component@Configurationの違いの現象ですが、なぜこのような現象が起こるのでしょうか? eoo() メソッドの中で foo() メソッドが呼び出されるという問題を考えてみましょう。 明らかに foo() メソッドは新しいオブジェクトを形成します、呼び出す foo() メソッドが元の foo() メソッドではないと仮定すると、新しいオブジェクトが形成されない可能性はありますか? foo() メソッドを呼び出すときにコンテナに移動して Bean foo を取得すると、この効果を達成できますか? では、どうすればそのような効果を達成できるのでしょうか? 方法はあります、プロキシつまり、AppConfig を含め、呼び出した eoo() メソッドと foo() メソッドはすべて Spring によってプロキシされるため、ここで @Component と @Configuration の最も基本的な違いを理解します。実際、この説明はそれほど厳密ではありませんが、より正確には、クラスの BeanDefinition 属性に Full 構成属性がある場合、このクラスは Spring によってプロキシされます。

3.Spring が完全に構成されたプロキシを実装する方法

これを理解するには、Spring がこれらのコンフィグレーション クラスを FULL モードまたは LITE モードに変換するタイミングについても明確にする必要があります。次に、Spring で個人的に非常に重要だと思うクラス、ConfigurationClassPostProcessor を紹介します。

3.1ConfigurationClassPostProcessorとは

まず、このクラスの定義を簡単に見てみましょう。

public class ConfigurationClassPostProcessor implements 
                                              BeanDefinitionRegistryPostProcessor,
                                               PriorityOrdered, 
                                               ResourceLoaderAware, 
                                               BeanClassLoaderAware, 
                                               EnvironmentAware {}
复制代码

このクラス定義から、このクラスの型は BeanDefinitionRegistryPostProcessor であることがわかり、Spring の組み込み Aware インターフェイスの多くが実装されています。Beanfactory のポストプロセッサを理解している場合は、ConfigurationClassPostProcessor の実行タイミングを知る必要があります。プロセス全体をわかりやすく説明するには、ConfigurationClassPostProcessor クラスがいつインスタンス化されたのかを知る必要があります。

3.2ConfigurationClassPostProcessor はいつインスタンス化されますか?

この質問に答えるには、まず前提を明確にする必要があります。それは、ConfigurationClassPostProcessor クラスに対応する BeanDefinition が Spring コンテナに登録されたときです。Spring のインスタンス化は非常に特殊であり、主に BeanDefinition に基づいて処理されるため、この質問は今すぐこのクラスはいつ Beand 定義として登録されましたか? これに対する答えは、ソース コード、特に Spring コンテナーを初期化するときに見つかります。

new AnnotationConfigApplicationContext(ConfigClass.class)
  -> new AnnotatedBeanDefinitionReader(this);
    -> AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
      -> new RootBeanDefinition(ConfigurationClassPostProcessor.class);
        -> registerPostProcessor(BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName)
复制代码

ここからわかるように、ConfigurationClassPostProcessor が BeanDefinition として登録されています。上で述べたように、Spring は BeanDefinition の解析、処理、インスタンス化、充填、初期化、および多数のコールバックによって Bean を形成します。これで、ConfigurationClassPostProcessor BeanDefinition が形成されました。 。

3.3 @Component と @Configuration の実装の違い

上記の ConfigurationClassPostProcessor は BeanDefinition 登録センターに登録されており、これは Spring が特定の時点でそれを Bean に処理することを意味します (特定の時点とは、BeanFactory 内のすべてのポストプロセッサーの処理です)。

AbstractApplicationContext
  -> refresh()
    -> invokeBeanFactoryPostProcessors(beanFactory);
      -> PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
复制代码

この BeanFactory のポストプロセッサの処理方法は比較的複雑です。簡単に言うと、主に BeanFactoryPostProcessor と BeanDefinitionRegistryPostProcessor を実装するすべてのクラスを処理します。もちろん、ConfigurationClassPostProcessor もそのうちの 1 つなので、実装方法を見てみましょう。

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    int registryId = System.identityHashCode(registry);
    if (this.registriesPostProcessed.contains(registryId)) {
      throw new IllegalStateException(
          "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
    }
    if (this.factoriesPostProcessed.contains(registryId)) {
      throw new IllegalStateException(
          "postProcessBeanFactory already called on this post-processor against " + registry);
    }
    this.registriesPostProcessed.add(registryId);
​
    processConfigBeanDefinitions(registry);
  }
  
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    int factoryId = System.identityHashCode(beanFactory);
    if (this.factoriesPostProcessed.contains(factoryId)) {
      throw new IllegalStateException(
          "postProcessBeanFactory already called on this post-processor against " + beanFactory);
    }
    this.factoriesPostProcessed.add(factoryId);
    if (!this.registriesPostProcessed.contains(factoryId)) {
      // BeanDefinitionRegistryPostProcessor hook apparently not supported...
      // Simply call processConfigurationClasses lazily at this point then.
      processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
    }
​
    enhanceConfigurationClasses(beanFactory);
    beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
  }
复制代码

これら 2 つのメソッドは、ConfigurationClassPostProcessor の中で最も重要なはずです。ここで簡単に要約しましょう。最初のメソッドは主に、内部クラス、@Component、@ComponentScan、@Bean、@Configuration、@Import およびその他のアノテーションの処理を完了します。そして、生成します。対応する BeanDefinition 別の方法は、CGLIB を使用して @Configuration を強化することです。まず Spring が設定された LITE モードと FULL モードをどこで区別するかを見てみましょう。最初のメソッドには checkConfigurationClassCandidate メソッドがあります。

public static boolean checkConfigurationClassCandidate(
      BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
      
        // ...
        
      Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
        if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
          beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
        }
        else if (config != null || isConfigurationCandidate(metadata)) {
          beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
        }
        else {
          return false;
        }
        // ...      
      }
复制代码

プログラムの判断によると、クラスに @Configuration アノテーションが付けられ、プロキシ モードが true の場合、このクラスに対応する BeanDefinition には Spring によって追加された FULL 構成モード属性が設定されますが、この「属性」を理解していない学生もいるかもしれません。簡単に言うと、実はこの「属性」は Spring では AttributeAccessor という特定のインターフェースを持っていて、BeanDefinition はこのインターフェースを継承しているのですが、この AttributeAccessor をどう理解すればよいでしょうか。実際には非常に単純ですが、BeanDefinition は主に何をするのでしょうか? これは主に、このクラスが抽象かどうか、そのスコープは何か、遅延ロードされるかどうかなど、クラス オブジェクトを記述するために使用されます。クラス オブジェクトが BeanDefinition で記述できない「属性」を持つ場合、これはどのように記述する必要があります。それに対処するには?そこで、このインターフェイス AttributeAccessor が再び役に立ちます。定義したデータを格納できます。マップとして理解できます。BeanDefinition の属性の意味は理解できましたか?

ここでも@Configuration(proxyBeanMethods = false)同じ効果が見られます@Component。どちらも LITE モードです

ここでの最初のステップは、クラスが FULL モードであるか LITE モードであるかを判断することです。次に、次のステップは、構成クラスのアノテーションの解析を開始することです。ConfigurationClassParser クラスには processConfigurationClass メソッドがあり、その内部には doProcessConfigurationClass メソッドがあります。上記の @Component とその他のアノテーションを解析する処理です 解析が完了すると、ConfigurationClassPostProcessor クラスの processConfigBeanDefinitions メソッド内にあるloadBeanDefinitionsメソッドがあり、解析に成功したアノテーションデータをすべてBeanDefinitionとして登録するメソッドです。これは ConfigurationClassPostProcessor クラスです。最初のメソッドによって実行されるタスクと、このメソッドについてはここで簡単に説明します。実際、このメソッドは非常に複雑であり、ゆっくりと学習する必要があります。

enhanceConfigurationClasses次に、 ConfigurationClassPostProcessor クラスのメソッドについて説明します. このメソッドは主に @Configuration アノテーションが付けられたクラスの拡張を完了し、CGLIB プロキシを実行します. コードは次のとおりです:

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
        Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<String, AbstractBeanDefinition>();
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
            if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {//判断是否被@Configuration标注
                if (!(beanDef instanceof AbstractBeanDefinition)) {
                    throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
                            beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
                }
                else if (logger.isWarnEnabled() && beanFactory.containsSingleton(beanName)) {
                    logger.warn("Cannot enhance @Configuration bean definition '" + beanName +
                            "' since its singleton instance has been created too early. The typical cause " +
                            "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
                            "return type: Consider declaring such methods as 'static'.");
                }
                configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
            }
        }
        if (configBeanDefs.isEmpty()) {
            // nothing to enhance -> return immediately
            return;
        }
        ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
        for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
            AbstractBeanDefinition beanDef = entry.getValue();
            // If a @Configuration class gets proxied, always proxy the target class
            beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
            try {
                // Set enhanced subclass of the user-specified bean class
                Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
                Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);//生成代理的class
                if (configClass != enhancedClass) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(String.format("Replacing bean definition '%s' existing class '%s' with " +
                                "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
                    }
                    //替换class,将原来的替换为CGLIB代理的class
                    beanDef.setBeanClass(enhancedClass);
                }
            }
            catch (Throwable ex) {
                throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
            }
        }
    }
复制代码

CGLIB の知識が必要になるので簡単にまとめておきます このメソッドは、すべての BeanDefinition の中から属性が FULL モードの BeanDefinition を見つけて、それに対してプロキシ拡張を実行し、その BeanDefinition の beanClass を設定します。次に、拡張中に明確にする必要がある詳細がいくつかあります。つまり、eoo()、foo()、その他のメソッドなどの通常のクラスのメソッドは MethodInterceptor によってインターセプトされ、このメソッドの呼び出しは次のようになります。この時点で、ConfigurationClassPostProcessor がいつインスタンス化されるか、いつアノテーション設定が解析されるか、いつ設定の拡張が実行されるかが少し明確になるはずです。ここに表示されている内容が理解できない場合は、私と話し合ってください。

4. まとめ

@Component は Spring の LITE モードの構成アノテーションを表します。このモードのアノテーションは Spring によってプロキシされません。これは標準クラスです。このクラスに @Bean でマークされたメソッドがある場合、メソッドは相互に呼び出すことができます。実際には、これは通常の Java クラスのメソッドの呼び出しです。

@Configuration は Spring の FULL モードを表す設定アノテーションです このモードのクラスは Spring によってプロキシされます このクラス内の @Bean メソッドの相互呼び出しはプロキシ メソッドの呼び出しと同等です その後プロキシ メソッド内で判定されますgetBean メソッドを呼び出すか、invokeSuper メソッドを呼び出すか、これがこれら 2 つのアノテーションの最も基本的な違いです。

一言で言えば、 @Configuration アノテーションが付けられたすべての @Bean メソッドは動的にプロキシされるため、このメソッドを呼び出すと同じインスタンスが返されます。 

おすすめ

転載: blog.csdn.net/2301_76607156/article/details/130526647