blog

Springソースコードシリーズ - FactoryBeanソースコードで議論する

実際の開発では、システム基盤開発に傾倒しているのであれば、特に使い慣れないものではないはずです。そして、この2つはよく比較されますが、その理由は、この2つの名前が非常に人々を混乱させやすいようですが、...

Mar 6, 2020 · 8 min. read
シェア

はじめに

実際の開発では、システム基盤の開発に傾倒している場合、FactoryBeanの使用は特に馴染みがないはずですが、FactoryBeanとBeanFactoryこの2つはよく比較され、その理由は、この2つの名前は非常に人々を混乱させやすいようですが、これらの2つの原理と役割は全く異なります。この記事では、FactoryBeanに焦点を当ててソースコード解析を開始します。

FactoryBean
public interface FactoryBean<T> {
	// 作成されたBeanオブジェクトに戻る
	T getObject() throws Exception;
 // beanオブジェクトのクラス型
 Class<?> getObjectType();
 // beanオブジェクトがシングルトンであろうとなかろうと、デフォルトはシングルトンである。
 default boolean isSingleton() {
		return true;
	}
}

これをテストするクラスを作成し、コンテナから MyDemoFactoryBean と MyDemoService を取得できるかどうかを確認してみましょう:

@Configuration
@ComponentScan("com.leon.factorybean")
public class Config {
}
public class MyApplication {
 public static void main(String[] args) {
 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
 String[] myDemoTactoryBeanNames = applicationContext.getBeanNamesForType(MyDemoFactoryBean.class);
 String[] myDemoServicBeanNames = applicationContext.getBeanNamesForType(MyDemoService.class);
 System.out.println(JsonUtil.convert(myDemoTactoryBeanNames));
 System.out.println(JsonUtil.convert(myDemoServicBeanNames));
 }
}

FactoryBeanソースコードのトレース

1.エントランス

このようにSpringでは、ソースコードをトレースする最も直接的な方法は、applicatonContext.getBeanの方法です。

MyDemoFactoryBean myDemoFactoryBean = applicationContext.getBean(MyDemoFactoryBean.class);

2. AbstractApplicationContext#getBeanにデリゲートします。

@Override
public <T> T getBean(Class<T> requiredType) throws BeansException {
 assertBeanFactoryActive();
 return getBeanFactory().getBean(requiredType);
}

DefaultListableBeanFactoryへの再委任#getBean(java.lang.Class)

getBeanFactory()は実際にはDefaultListableBeanFactoryインスタンスを返します。ここで、DefaultListableBeanFactoryがSpringのBeanFactoryのデフォルト実装であることがわかります。したがって、デフォルトでは、当該IOCコンテナはそれです。この記事では、コンテナの分析はあまり行わず、まずFactoryBeanの助けを借りて、その外観を探ります。

DefaultListableBeanFactoryへの再委任#getBean(java.lang.Class, java.lang.Object

上記のメソッドはまだ実際には何もしておらず、getBeanに委譲されたオーバーロードされたメソッドのままです。ソースコードは以下の通りです:

public <T> T getBean(Class<T> requiredType, @Nullable Object... args) throws BeansException {
 Assert.notNull(requiredType, "Required type must not be null");
 Object resolved = resolveBean(ResolvableType.forRawClass(requiredType), args, false);
 if (resolved == null) {
 throw new NoSuchBeanDefinitionException(requiredType);
 }
 return (T) resolved;
 }

DefaultListableBeanFactoryへの再委任#resolveBean

最後にようやく山の本当の顔を参照してください、内部のresolveBean()メソッドに委譲することにより、メソッドを変更すると、ビーンメソッドの最終的な解決です。ソースコードは次のとおりです:

private <T> T resolveBean(ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) {
	// Beanを構文解析し,戻り結果が空でなければ,構文解析されたBeanを既に得たことを意味し,それを直接返す。このメソッドは重要な構文解析メソッドである!
 NamedBeanHolder<T> namedBean = resolveNamedBean(requiredType, args, nonUniqueAsNull);
 if (namedBean != null) {
 	return namedBean.getBeanInstance();
 }
 // returnがnullの場合,beanFactoryを通してbeanを生成する。
 BeanFactory parent = getParentBeanFactory();
 if (parent instanceof DefaultListableBeanFactory) {
 	return ((DefaultListableBeanFactory) parent).resolveBean(requiredType, args, nonUniqueAsNull);
 }
 else if (parent != null) {
 	ObjectProvider<T> parentProvider = parent.getBeanProvider(requiredType);
 	if (args != null) {
 		return parentProvider.getObject(args);
 	}
 	else {
 		return (nonUniqueAsNull ? parentProvider.getIfUnique() : parentProvider.getIfAvailable());
 	}
 }
 return null;
}

注目すべきは、getBeanNamesForType メソッドと getBean メソッドです。

DefaultListableBeanFactory#getBeanNamesForType

public String[] getBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) { Class resolved = type.resolve(); // ここで、クラスの解決を得る // MyDemoFactoryBean他の属性は定義されていないので、ここではifブランチに入る。 if (resolved != null && !type.hasGenerics()) { // これはgetBeanNamesForTypeのオーバーロードされたメソッドであり、doGetBeanNamesForTypeメソッドを呼び出すことになる。 return getBeanNamesForType(resolved, includeNonSingletons, allowEagerInit); } else { return doGetBeanNamesForType(type, includeNonSingletons, allowEagerInit); } }
private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) {
		List<String> result = new ArrayList<>();
		// すべてのbeanDefinitionNamesをチェックする.beanDefinitionnew" AnnotationConfigApplicationContext(Config.class)解析されるときは解析される。つまり、ここではループを直接たどることができる。
		for (String beanName : this.beanDefinitionNames) {
			// エイリアスがあるかどうかを判断する。一般に、エイリアスを使用するケースはほとんどない。この場合、エイリアスは存在しないので、if分岐に進む。
			if (!isAlias(beanName)) {
				try {
					RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
					// Only check bean definition if it is complete.
					if (!mbd.isAbstract() && (allowEagerInit ||
							(mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading()) &&	!requiresEagerInitForType(mbd.getFactoryBeanName()))) {
 // !!! 重要である!ここでは,beanNameとbeanDefinitionに基づいて,現在の型がFactoryBean型であるかどうかを判断する。このパラメタは,通常のBeanとFactoryBeanとを区別する核となる。
 // isFactoryBean()メソッドの分析については、次のサブセクション7を参照のこと。
						boolean isFactoryBean = isFactoryBean(beanName, mbd);
						BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
 // !!! 重要型が一致するかどうか、デフォルトはfalseである.
						boolean matchFound = false;
 // FactoryBeanの初期化を許可するかどうか、デフォルトは許可されている
						boolean allowFactoryBeanInit = allowEagerInit || containsSingleton(beanName);
						boolean isNonLazyDecorated = dbd != null && !mbd.isLazyInit();
						// 次に、matchFoundパラメーターを設定することである。
 // ファクトリBean型でない場合は,直接isTypeMatchメソッドを呼び出してmatchFoundパラメタを設定する。
						if (!isFactoryBean) {
							if (includeNonSingletons || isSingleton(beanName, mbd, dbd)) {
								matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit);
							}
						}
 // FactoryBeanであれば、次のelseブランチを入力する。
						else {
						// まず第一に、matchFoundを設定しようとする通常のbeanNameによると、trueの場合、それはFactoryBeanで生成されたbeanNameを探している。
							if (includeNonSingletons || isNonLazyDecorated ||	(allowFactoryBeanInit && isSingleton(beanName, mbd, dbd))) {
								matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit);
							}
 // !重要である! matchFoundがまだfalseの場合、FactoryBean自体を探している可能性が高いことを意味し、次にbeanNameに接頭辞を追加する:&FactoryBean自体を見つけようとする。
							if (!matchFound) {
								// In case of FactoryBean, try to match FactoryBean instance itself next.
								beanName = FACTORY_BEAN_PREFIX + beanName;
								matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit);
							}
						}
 // matchFoundが真の場合,beanNameが結果に追加される。FactoryBean型を探す場合、beanNameはすでに追加されていることに注意。&接頭辞
						if (matchFound) {
							result.add(beanName);
						}
					}
				}
				catch (CannotLoadBeanClassException | BeanDefinitionStoreException ex) {
					if (allowEagerInit) {
						throw ex;
					}
					// Probably a placeholder: let's ignore it for type matching purposes.
					LogMessage message = (ex instanceof CannotLoadBeanClassException) ?
							LogMessage.format("Ignoring bean class loading failure for bean '%s'", beanName) :
							LogMessage.format("Ignoring unresolvable metadata in bean definition '%s'", beanName);
					logger.trace(message, ex);
					onSuppressedException(ex);
				}
			}
		}
 // ...他のコードを省略する...
 // 見つかったBeanNameの結果を返す
		return StringUtils.toStringArray(result);
	}

AbstractBeanFactory#isFactoryBean

それを知った上で、FactoryBean型かどうかを判断する方法は、isFactoryBean()メソッドによって解決されます:

protected boolean isFactoryBean(String beanName, RootBeanDefinition mbd) {
		Boolean result = mbd.isFactoryBean;
		// beanDefinitionのisFactoryBean属性がNULLの場合、bdのbeanTypeを直接取得し、FactoryBeanかどうかを判断する。
 // 次に、判定結果をbdのisFactoryBeanプロパティに代入し、結果を直接返す。
		if (result == null) {
			Class<?> beanType = predictBeanType(beanName, mbd, FactoryBean.class);
			result = (beanType != null && FactoryBean.class.isAssignableFrom(beanType));
			mbd.isFactoryBean = result;
		}
		return result;
}

AbstractBeanFactory#getBean(java.lang.String, java.lang.Class, java.lang.Object...)

ApplicationContextのgetBeaメソッドには、多くのオーバーロードがありますが、実際には、究極は、特定のBeanを見つけるために、beanNameに基づいて、beanNameを解決することです。その理由は、ApplicationContextでは、beanDefinition、beanType、IOCコンテナなどは、すべてbeanNameに基づいてマッピング関係を行うからです。したがって、ソースコードを読むと、多くの場合、最初にbeanNameを解決してから、さらに操作を行うことがわかります。beanNameの定義方法が多様であるため、解析処理も複雑になり、ソースコードを解析するときは静かにする必要があります。getBeanメソッド自体は、Beanのライフサイクル、循環依存関係、および他の複雑なシナリオを含むため、分析のためにgetBeanメソッドを別途取り出します。この記事だけgetBeanメソッドを知っている必要があります実際のBeanを取得することができます。

まとめ

上記の分析により、FactoryBeanがどのように動作するかの一般的なアイデアが得られます。ソースコードから、いくつかの結論を引き出すこともできます:

Read next

最近、dva+umiでは、reduxとhookを使ってテーブル・リクエストをカプセル化した。

数ヶ月間reactと接点がなかったのですが、最近ふとreactのプロジェクトメソッドが非常にポピュラーな書き方であることを知り、......フロントエンドをやるのが難しすぎて、次から次へと技術が出てきます。最近の研究は、リクエスト関数のテーブルのreduxカプセル化についてです。

Mar 5, 2020 · 4 min read