SpringのシンプルなバージョンでSetterインジェクションを実装する方法

序文

前回の記事で、IoCとAOPの簡略版が実装されると述べましたが、今日ようやく完成しました。Java開発言語を使用している人はSpring開発フレームワークを使用または聞いたことがあると思いますが、エンタープライズレベルの開発のほとんどはそれなしでは実行できません。公式Webサイトから、そのエコロジーが非常に大きく、 Springフレームワークの誕生はJava開発者にとって大きなメリットであると言えます。2004年のリリース以来、Springはいくつかの問題を解決するために多くの機能を導入してきました。エンタープライズ開発のポイント、その中で最も重要なのは、私たちがよく耳にするIoCおよびAOP機能です。関連する知識と詳細が多いため、それらをいくつかの記事に分けて紹介します。今日(および最初の)はXMLベースの構成セッターインジェクションを実装する方法を見てみましょう。

前提知識

XML構成ファイルを使用するため、最初にXMLファイルを読み取り、必要なデータ構造に変換します。XMLファイルを解析するためのこれらのメソッド(JDOM、XOM、DOM4J)がありますが、これらに限定されません。はシンプルで使いやすいdom4jなので、その基本的な知識を簡単に理解する必要があります。実際、これらは非常に簡単な方法と基本的な使用法にすぎません。2つ目は、使用経験が必要なことです。 Springフレームワーク上記は、Springの合理化されたコア部分の単純な実装です。はい、はい、これらの基本的な前提条件があれば十分です。

基本的なデータ構造の抽象化

いくつかの簡単なアイデアや設計のコーディングと実装を開始する前に、まずSpringでBeanとして管理されているオブジェクトを呼び出し、次にこのBeanを中心に他の操作を設計します。これにより、Beanの定義が統一され、標準化されているため、最初のインターフェースBeanDefinitionが出てきます。今回必要な基本情報には、以下に示すように、Beanの名前、Beanが属するクラスの名前、シングルトンかどうか、スコープなどが含まれます。

SpringのシンプルなバージョンでSetterインジェクションを実装する方法

BeanDefinitionができたので、次のステップは、このBeanDefinitionに基づいて対応するBeanインスタンスを作成することです。これには、作成作業を完了するためのファクトリファクトリインターフェイスが必要です。Bean作成インターフェイスの名前はBeanFactoryで、さまざまな条件に従って提供されます。対応するBeanインスタンス関数(beanIdなど)を作成しますが、作成の前提は、最初にBeanDefinitionを登録してから、特定の条件に従ってBeanDefinitionを取得することです。単一責任の原則に従って、この関数は次のように完了する必要があります。新しいインターフェース。主にBeanDefinitionの登録と取得に使用されるため、BeanDefinitionRegistryという名前が付けられています。必要なBeanDefinitionはどこで入手できますか?もちろん、XML構成ファイルから取得したXML構成メソッドに基づいていることは明らかです。また、単一責任の原則に従って、XMLBeanDefinitionReaderという名前のクラスもこのことを完了するために必要です。この部分の全体的な構造は次のとおりです。 :

SpringのシンプルなバージョンでSetterインジェクションを実装する方法

次の質問は、XMLなどの構成ファイルリソースをどのように表現するかです。これらの構成は、プログラムの一種のリソースであり、リソースとして統合して抽象化し、ストリーム(InputStream)オブジェクトインターフェイスに対応するリターンリソースを提供します。この種類リソースの量は、プロジェクト、ローカルファイル、またはリモートから取得できます。これらはすべて一種のリソースであり、構造は次のとおりです。

SpringのシンプルなバージョンでSetterインジェクションを実装する方法

最後に、上記のクラスを組み合わせて呼び出す関数を提供して、XML構成ファイルの解析を完了してBeanDefinitionに挿入し、コンテナーに挿入します。これは、プログラムコンテキストの責任として機能し、ApplicationContextという名前を付けることもできます。リソースのタイプに基づきます。FileSystmXmlApplicationContext、ClassPathXmlApplicationContextなどのさまざまなクラスに分割され、これらには構成ファイルをResourceに変換するプロセスがあり、共通の親抽象クラスはテンプレートメソッドを使用して抽象化できます。 、以下に示すように:
SpringのシンプルなバージョンでSetterインジェクションを実装する方法
上記の分析結果の要約、予備的なクラス図の設計は次のとおりです。
SpringのシンプルなバージョンでSetterインジェクションを実装する方法

セッターインジェクションの目標を最終的に達成するには、次の2つのステップに分けることができます。

XML構成ファイル内のタグをBeanDefinitionに解析し、コンテナーに挿入します

セッターインジェクションを実装する

以下では、これら2つの部分に分けて、達成方法を説明します。

構成ファイルの分析

次の内容の構成ファイルapplicationcontext-config1.xmlがあるとします。


<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.e3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd"
>

    <bean id="orderService" class="cn.mghio.service.version1.OrderService" />

</beans>

最後に、IDがorderServiceで、タイプがcn.mghio.service.version1.OrderServiceであるBeanDefinitionを解析する必要があります。これをテストクラスに変換する場合は、次のテストクラスに合格する必要があります。


/**
 * @author mghio
 */
public class BeanFactoryTest {

    private Resource resource;
    private DefaultBeanFactory beanFactory;
    private XmlBeanDefinitionReader reader;

    @BeforeEach
    public void beforeEach() {
        resource = new ClassPathResource("applicationcontext-config1.xml");
        beanFactory = new DefaultBeanFactory();
        reader = new XmlBeanDefinitionReader(beanFactory);
    }

    @Test
    public void testGetBeanFromXmlFile() {
        reader.loadBeanDefinition(resource);
        BeanDefinition bd = beanFactory.getBeanDefinition("orderService");

        assertEquals("cn.mghio.service.version1.OrderService", bd.getBeanClassNam());
        OrderService orderService = (OrderService) beanFactory.getBean("orderService");
        assertNotNull(orderService);
    }

    @Test
    public void testGetBeanFromXmlFileWithInvalidBeanId() {
        assertThrows(BeanCreationException.class, () -> beanFactory.getBean("notExistsBeanId"));
    }

    @Test
    public void testGetFromXmlFilWithFileNotExists() {
        resource = new ClassPathResource("notExists.xml");
        assertThrows(BeanDefinitionException.class, () -> reader.loadBeanDefinition(resource));
    }

}

ここで重要なのは、XmlBeanDefinitionReaderクラスのloadBeanDefinitionを実装して、構成からBeanDefinitionをロードおよび挿入する方法であることがわかります。分析について検討した後、2つの主要なステップがあることがわかりました。最初のステップは、 XML構成とそれをBeanDefinitionに変換します。これには上記が必要です。dom4jが提供する機能の2番目のステップは、解析されたBeanDefinitionをコンテナーに挿入することです。これは、BeanDefinitionRegistryインターフェースを組み合わせてBeanDefinitionを登録する機能を提供することで完了します。XML構成を読み取るクラスXmlBeanDefinitionReaderのコード実装はすぐに記述でき、このクラスのコードの一部は次のとおりです。


/**
 * @author mghio
 */
public class XmlBeanDefinitionReader {

    private static final String BEAN_ID_ATTRIBUTE = "id";
    private static final String BEAN_CLASS_ATTRIBUTE = "class";

    private BeanDefinitionRegistry registry;

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        this.registry = registry;
    }

    @SuppressWarnings("unchecked")
    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);
                this.registry.registerBeanDefinition(beanId, bd);
            }
        } catch (DocumentException | IOException e) {
            throw new BeanDefinitionException("IOException parsing XML document:" + configurationFile, e);
        }
    }
}

次に、BeanFactoryのgetBeanメソッドが呼び出されると、Beanの完全修飾名に基づいてインスタンスを作成できます(PS:インスタンスキャッシュは当面考慮されません)。メソッド実装のメインコードは次のとおりです。

public Object getBean(String beanId) {
    BeanDefinition bd = getBeanDefinition(beanId);
    if (null == bd) {
        throw new BeanCreationException("BeanDefinition does not exists, beanId:" + beanId);
    }
    ClassLoader classLoader = this.getClassLoader();
    String beanClassName = bd.getBeanClassNam();
    try {
        Class<?> clazz = classLoader.loadClass(beanClassName);
        return clazz.newInstance();
    } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
        throw new BeanCreationException("Created bean for " + beanClassName + " fail.", e);
    }
}

構成ファイルの解析が完了したので、Setterインジェクションを実装する方法を見てみましょう。

セッターインジェクションの実装方法

XML構成ファイルに基づくSetterインジェクションの最初の実装は、基本的にXML構成ファイルを解析し、次にオブジェクトプロパティのsetXXXメソッドを呼び出して構成値を設定することです。構成ファイルapplicationcontext-config2.xmlは次のとおりです。

<`` `
?xml version =" 1.0 "encoding =" UTF-8 "?>
<beans xmlns =" http://www.springframework.org/schema/beans "
xmlns:xsi =" http:// www。 e3.org/2001/XMLSchema-instance "
xsi:schemaLocation =" http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd "> ;

<bean id="stockDao" class="cn.mghio.dao.version2.StockDao"/>

<bean id="tradeDao" class="cn.mghio.dao.version2.TradeDao"/>

<bean id="orderService" class="cn.mghio.service.version2.OrderService">
    <property name="stockDao" ref="stockDao"/>
    <property name="tradeDao" ref="tradeDao"/>
    <property name="num" value="2"/>
    <property name="owner" value="mghio"/>
    <property name="orderTime" value="2020-11-24 18:42:32"/>
</bean>

</ Beans>


我们之前使用了 BeanDefinition 去抽象了标签,这里面临的第一个问题就是要如何去表达配置文件中的标签,其中 ref 属性表示一个 beanId、value 属性表示一个值(值类型为:Integer、String、Date 等)。观察后可以发现,标签本质上是一个 K-V 格式的数据(name 作为 Key,ref 和 value 作为 Value),将这个类命名为 PropertyValue,很明显一个 BeanDefinition 会有多个 PropertyValue,结构如下:

![](https://s4.51cto.com/images/blog/202101/26/5acb471e0c63964dd686cd932fbf8979.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)

这里的 value 有两种不同的类型,一种是表示 Bean 的 id 值,运行时会解析为一个 Bean 的引用,将其命名为 RuntimeBeanReference,还有一种是 String 类型,运行时会解析为不同的类型,将其命名为 TypeStringValue。第二个问题就是要如何将一个类型转换为另一个类型呢?比如将上面配置中的字符串 2 转换为整型的 2、字符串 2020-11-24 18:42:32 转换为日期,这类通用的问题前辈们已经开发好了类库处理了,这里我们使用 commons-beanutils 库提供的 BeanUtils.copyProperty(final Object bean, final String name, final Object value) 方法即可。然后只需在之前 XmlBeanDefinitionReader 类的 loadBeanDefinition 方法解析 XML 配置文件的时解析标签下的标签并设置到 BeanDefinition 的 propertyValues 属性中,DefaultBeanFactory 中的 getBean 方法分为实例化 Bean 和读取向实例化完成的 Bean 使用 Setter 注入配置文件中配置属性对应的值。XmlBeanDefinitionReader 的 loadBeanDefinition() 方法代码修改为:

public void loadBeanDefinition(Resource resource){
try(InputStream is = resource.getInputStream()){
SAXReader saxReader = new SAXReader();
ドキュメントdocument = saxReader.read(is);
要素ルート= document.getRootElement(); // <beans>
Iterator <Element> iterator = root.elementIterator();
while(iterator.hasNext()){
要素要素= iterator.next();
文字列beanId = element.attributeValue(BEAN_ID_ATTRIBUTE);
文字列beanClassName = element.attributeValue(BEAN_CLASS_ATTRIBUTE);
BeanDefinition bd = new GenericBeanDefinition(beanId、beanClassName);
parsePropertyElementValue(element、bd); // <プロパティ>を解析します
this.registry.registerBeanDefinition(beanId、bd);
}
} catch(DocumentException | IOException e){
throw new BeanDefinitionException( "IOException parsing XML document:" + resource、e);
}
}

private void parsePropertyElementValue(Element element、BeanDefinition bd){
Iterator <Element> iterator = element.elementIterator(PROPERTY_ATTRIBUTE);
while(iterator.hasNext()){
要素propertyElement = iterator.next();
文字列propertyName = propertyElement.attributeValue(NAME_ATTRIBUTE);
if(!StringUtils.hasText(propertyName)){
return;
}

    Object value = parsePropertyElementValue(propertyElement, propertyName);
    PropertyValue propertyValue = new PropertyValue(propertyName, value);
    bd.getPropertyValues().add(propertyValue);
}

}

プライベートオブジェクトparsePropertyElementValue(Element propertyElement、String propertyName){
String elementName =(propertyName!= null)?
"プロパティの<property>要素 '" + propertyName + "'": "<constructor-arg> element";

boolean hasRefAttribute = propertyElement.attribute(REF_ATTRIBUTE) != null;
boolean hasValueAttribute = propertyElement.attribute(VALUE_ATTRIBUTE) != null;

if (hasRefAttribute) {
    String refName = propertyElement.attributeValue(REF_ATTRIBUTE);
    RuntimeBeanReference ref = new RuntimeBeanReference(refName);
    return ref;
} else if (hasValueAttribute) {
    String value = propertyElement.attributeValue(VALUE_ATTRIBUTE);
    TypedStringValue valueHolder = new TypedStringValue(value);
    return valueHolder;
} else {
    throw new RuntimeException(elementName + " must specify a ref or value");
}

}

DefaultBeanFactoryのgetBean方法はまた、ビーンプロパティ噴射動作を追加し、以下のようにコードの一部である:
Javaの
パブリックオブジェクトgetBean(文字列beanId){
BeanDefinition BD = getBeanDefinition(beanId);
// 1.インスタンス化ビーン
オブジェクトビーン= instantiateBean(BD) ;
// 2. Beanを設定
するpopulateBean(bd、bean);
beanを返す;
}

プライベートオブジェクトinstantiateBean(BeanDefinition bd){
ClassLoader classLoader = this.getClassLoader();
文字列beanClassName = bd.getBeanClassName();
{
Class <?> clazz = classLoader.loadClass(beanClassName);を試してください
clazz.newInstance();を返します。
} catch(ClassNotFoundException | IllegalAccessException | InstantiationException e){
throw new BeanCreationException( "Created bean for" + beanClassName + "fail。"、e);
}
}

private void
PopulateBean(BeanDefinition bd、Object Bean){ List <PropertyValue> propertyValues = bd.getPropertyValues();
if(propertyValues == null || propertyValues.isEmpty()){
return;
}

BeanDefinitionResolver resolver = new BeanDefinitionResolver(this);
SimpleTypeConverted converter = new SimpleTypeConverted();
try {
    for (PropertyValue propertyValue : propertyValues) {
        String propertyName = propertyValue.getName();
        Object originalValue = propertyValue.getValue();
        Object resolvedValue = resolver.resolveValueIfNecessary(originalValue);

        BeanUtils.copyProperty(bean, propertyName, resolvedValue);
    }
} catch (Exception e) {
    throw new BeanCreationException("Failed to obtain BeanInfo for class [" + bd.getBeanClassName() + "]");
}

}



至此,简单的 Setter 注入功能已完成。

**总结**

本文简单概述了基于 XML 配置文件方式的 Setter 注入简单实现过程,整体实现 Setter 注入的思路就是先设计一个数据结构去表达 XML 配置文件中的标签数据(比如上面的 PropertyValue),然后再解析配置文件填充数据并利用这个数据结构完成一些功能(比如 Setter 注入等)。感兴趣的朋友可以到这里 mghio-spring (https://github.com/mghio/mghio-spring) 查看完整代码。

おすすめ

転載: blog.51cto.com/15075507/2607603