1.背景
の人気により、アノテーション構成の開発は誰からも支持されるようになり、開発に基づいた煩わしい構成Spring Boot
には別れを告げました。まず、構成アノテーションの内部定義を簡単に理解しましょう。機能的に言えば、これらのアノテーションは確かにさまざまな機能を担当しますが、本質的にはすべて構成アノテーションとして内部で処理されます。Spring
XML
Spring
@Component、@Configuration、@Bean、@Import
Spring
成熟したフレームワークでは、シンプルで多様な構成が重要であり、Spring
それも重要です。Spring
構成開発プロセスの観点から見ると、全体的な構成方法は、比較的「独創的な」段階から、現在では非常に「スマート」な段階に移行しています。この期間に行われた努力は非常に大きく、XML から自動アセンブリ、Spring から Spring Boot、@Component から @Configuration および @Conditional に至るまで、Spring は今日まで発展し、ますます使いやすくなっています。,Spring
また、多くの詳細が隠されているため、今日は @Component と @Configuration を一緒に見てみましょう。
通常の Spring 開発作業では、基本的に設定アノテーション、特に@Componen
t と@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
メソッドは動的にプロキシされるため、このメソッドを呼び出すと同じインスタンスが返されます。