序文
この記事は、「Springの単純なバージョンを実装する方法」シリーズの2番目の記事です。最初の記事では、単純なXMLベースのSetterインジェクションを実装する方法を紹介します。この記事では、単純なコンストラクターインジェクション関数を実装する方法を見ていきます。手順はセッターインジェクションと同じです。まず、XML構成ファイル内の情報を解析して表現するデータ構造を設計し、次にこれらの解析済みデータ構造を使用して、ここでのコンストラクターインジェクションなどを実行します。言うまでもありませんが、直接トピックに行きましょう。
データ構造設計
コンストラクタインジェクションを使用するXMLの構成は次のとおりです。
<bean id="orderService" class="cn.mghio.service.version3.OrderService">
<constructor-arg ref="stockService"/>
<constructor-arg ref="tradeService"/>
<constructor-arg type="java.lang.String" value="mghio"/>
</bean>
上記のOrderServiceクラスは次のとおりです。
/**
* @author mghio
* @since 2021-01-16
*/
public class OrderService {
private StockDao stockDao;
private TradeDao tradeDao;
private String owner;
public OrderService(StockDao stockDao, TradeDao tradeDao, String owner) {
this.stockDao = stockDao;
this.tradeDao = tradeDao;
this.owner = owner;
}
}
XMLの構成構造からは、Setterインジェクションに似ています。これは、Key-Valueの形式です。各コンストラクター引数ノードは、実際に解析された値型の値、型の型、パラメーター名の名前を含め、ValueHolderとして抽象化できます。次のように表示されます。
/**
* @author mghio
* @since 2021-01-16
*/
public class ValueHolder {
private Object value;
private String type;
private String name;
// omit setter and getter
}
同じBeanに複数のValueHolderを含めることができます。実装をカプセル化し、いくつかの判断メソッド(コンストラクターインジェクションで構成されているかどうかなど)を提供するために、ConstructorArgumentとしてさらにカプセル化され、いくつかのCRUDインターフェイスを提供し、ValueHolderを以下に示すように、内部クラス:
/**
* @author mghio
* @since 2021-01-16
*/
public class ConstructorArgument {
private final List<ValueHolder> argumentsValues = new LinkedList<>();
public void addArgumentValue(Object value) {
this.argumentsValues.add(new ValueHolder(value));
}
public List<ValueHolder> getArgumentsValues() {
return this.argumentsValues;
}
public int getArgumentCount() {
return this.argumentsValues.size();
}
public boolean isEmpty() {
return this.argumentsValues.isEmpty();
}
public void clear() {
this.argumentsValues.clear();
}
// some other methods...
public static class ValueHolder {
private Object value;
private String type;
private String name;
}
}
次に、Get ConstructorArgumentメソッドを追加し、BeanDefinitionインターフェースでConstructorArgumentメソッドを構成するかどうかを決定します。構造を次の図に示します。
XML構成ファイルを解析します
前の記事に基づいて、XMLの解析は比較的簡単です。ここでは、constructor-argノードを解析し、アセンブリデータをBeanDefinitionのConstructorArgumentプロパティに追加し、XmlBeanDefinitionReaderクラスのloadBeanDefinition(Resource resource)メソッドを次のように変更します。 :
/**
* @author mghio
* @since 2021-01-16
*/
public class XmlBeanDefinitionReader {
private static final String CONSTRUCTOR_ARG_ELEMENT = "constructor-arg";
private static final String NAME_ATTRIBUTE = "name";
private static final String TYPE_ATTRIBUTE = "type";
// other fields and methods ...
public void loadBeanDefinition(Resource resource) {
try (InputStream is = resource.getInputStream()) {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(is);
Element root = document.getRootElement(); // <beans>
Iterator<Element> iterator = root.elementIterator();
while (iterator.hasNext()) {
Element element = iterator.next();
String beanId = element.attributeValue(BEAN_ID_ATTRIBUTE);
String beanClassName = element.attributeValue(BEAN_CLASS_ATTRIBUTE);
BeanDefinition bd = new GenericBeanDefinition(beanId, beanClassName);
if (null != element.attributeValue(BEAN_SCOPE_ATTRIBUTE)) {
bd.setScope(element.attributeValue(BEAN_SCOPE_ATTRIBUTE));
}
// parse <constructor-arg> node
parseConstructorArgElements(element, bd);
parsePropertyElementValues(element, bd);
this.registry.registerBeanDefinition(beanId, bd);
}
} catch (DocumentException | IOException e) {
throw new BeanDefinitionException("IOException parsing XML document:" + resource, e);
}
}
private void parseConstructorArgElements(Element rootEle, BeanDefinition bd) {
Iterator<Element> iterator = rootEle.elementIterator(CONSTRUCTOR_ARG_ELEMENT);
while (iterator.hasNext()) {
Element element = iterator.next();
parseConstructorArgElement(element, bd);
}
}
private void parseConstructorArgElement(Element element, BeanDefinition bd) {
String typeAttr = element.attributeValue(TYPE_ATTRIBUTE);
String nameAttr = element.attributeValue(NAME_ATTRIBUTE);
Object value = parsePropertyElementValue(element, null);
ConstructorArgument.ValueHolder valueHolder = new ConstructorArgument.ValueHolder(value);
if (StringUtils.hasLength(typeAttr)) {
valueHolder.setType(typeAttr);
}
if (StringUtils.hasLength(nameAttr)) {
valueHolder.setName(nameAttr);
}
bd.getConstructorArgument().addArgumentValue(valueHolder);
}
// other fields and methods ...
}
XMLを解析するプロセスは、全体として2つのステップに分かれています。最初のステップは、各ノードをトラバースするときにノードが存在するかどうかを判断し、ノードが存在する場合は解析されます。2番目のステップは、解析およびアセンブルされたValueHolderをに追加することです。 BeanDefinitionを使用して、BeanDefinitionに解析されるXMLコンストラクターインジェクションを構成します。Beanを作成するプロセスで、このデータ構造をコンストラクターインジェクションに使用する方法を見てみましょう。
コンストラクターの選び方
明らかに、コンストラクターインジェクションの使用は、現在インスタンス化されるBeanに構成コンストラクターインジェクションがあるかどうかを判断することにより、Beanをインスタンス化するフェーズに配置する必要があります。ある場合は、コンストラクターインスタンス化を使用します。XMLに構成コンストラクターの挿入があるかどうかを判別するには、BeanDefinitionによって提供されるhasConstructorArguments()メソッドを直接使用できます。実際、最終的には、ConstructorArgument.ValueHolderコレクションに値があるかどうかを判別することによって決定されます。複数のコンストラクターがある場合の選択方法についても質問があります。たとえば、OrderServiceクラスには次の3つのコンストラクターがあります。
/**
* @author mghio
* @since 2021-01-16
*/
public class OrderService {
private StockDao stockDao;
private TradeDao tradeDao;
private String owner;
public OrderService(StockDao stockDao, TradeDao tradeDao) {
this.stockDao = stockDao;
this.tradeDao = tradeDao;
this.owner = "nobody";
}
public OrderService(StockDao stockDao, String owner) {
this.stockDao = stockDao;
this.owner = owner;
}
public OrderService(StockDao stockDao, TradeDao tradeDao, String owner) {
this.stockDao = stockDao;
this.tradeDao = tradeDao;
this.owner = owner;
}
}
XMLコンストラクターによって注入される構成は次のとおりです。
<bean id="orderService" class="cn.mghio.service.version3.OrderService">
<constructor-arg ref="stockService"/>
<constructor-arg ref="tradeService"/>
<constructor-arg type="java.lang.String" value="mghio"/>
</bean>
インジェクションに最適なコンストラクターを選択するにはどうすればよいですか?ここで使用するマッチング方法は1です。最初にコンストラクターパラメーターの数を判断し、一致しない場合は直接スキップして次のサイクルに進みます。2。コンストラクターパラメーターの数が一致する場合は、パラメータータイプを判断します。現在のパラメータータイプと一致するか、現在のパラメータータイプのスーパータイプである場合は、このコンストラクターを使用してインスタンス化します。使用される判断方法は比較的単純で単純です。実際、Springの判断方法は状況をより包括的に考慮し、コードの実装もより複雑です。興味がある場合は、org.springframework.beans.factory.support.ConstructorResolver.autowireConstructorを確認してください。 (。。。)メソッド。ここで、XML構成のコンストラクター注入パラメーターを解析するときは、型をターゲット型に変換し、クラスにConstructorResolverという名前を付ける必要があることに注意してください。実装コードは多数あるため、ここでは投稿しません。 GitHubで完全なコードを確認してください。次に、Beanをインスタンス化するときに、コンストラクターインジェクション構成があるかどうかを判断するだけで済みます。存在する場合は、コンストラクターインジェクションを使用します。DefaultBeanFactoryのインスタンス化メソッドを次のように変更します。
/**
* @author mghio
* @since 2021-01-16
*/
public class DefaultBeanFactory extends DefaultSingletonBeanRegistry implements ConfigurableBeanFactory,
BeanDefinitionRegistry {
// other fields and methods ...
private Object doCreateBean(BeanDefinition bd) {
// 1. instantiate bean
Object bean = instantiateBean(bd);
// 2. populate bean
populateBean(bd, bean);
return bean;
}
private Object instantiateBean(BeanDefinition bd) {
// 判断当前 Bean 的 XML 配置是否配置为构造器注入方式
if (bd.hasConstructorArguments()) {
ConstructorResolver constructorResolver = new ConstructorResolver(this);
return constructorResolver.autowireConstructor(bd);
} else {
ClassLoader classLoader = this.getClassLoader();
String beanClassName = bd.getBeanClassName();
try {
Class<?> beanClass = null;
Class<?> cacheBeanClass = bd.getBeanClass();
if (cacheBeanClass == null) {
beanClass = classLoader.loadClass(beanClassName);
bd.setBeanClass(beanClass);
} else {
beanClass = cacheBeanClass;
}
return beanClass.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new BeanCreationException("Created bean for " + beanClassName + " fail.", e);
}
}
}
// other fields and methods ...
}
これまで、XML構成に基づくコンストラクターインジェクションの単純なバージョンが実装されてきました。
総括する
この記事では、XML構成に基づくSpringのコンストラクターインジェクションを簡単に紹介します。実際、最初の記事のSetterインジェクションの基盤により、コンストラクターインジェクションの実装ははるかに難しくありません。ここでの実装は比較的単純ですが、その考え方と一般的なプロセスはSpringの実装の具体的な詳細について詳しく知りたい場合は、ソースコードを確認してください。完全なコードがGitHubにアップロードされました。興味がある場合は、mghio-springにアクセスして、完全なコードを表示できます。次のプレビュー:「Springの簡易バージョンはフィールドアノテーションメソッドインジェクションを実装しています」。