Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

Springの気が利かない部分を少し改良してみる(1)


こんばんは、阪本です。

DI/AOPコンテナで有名どころと言えば「Spring」ですよね。
純国産のDI/AOPコンテナに、「Seasar2」もありますね。

私も、普段はSpringを使っているんですが、ところどころ思うことがあります。
「Springイケてない、Seasar2の方が気が利いてるなぁ」と。


その1つとして、XML定義において「初期化時に自由にメソッドが呼べないこと」があります。

「いやいや、Springでも、beanタグにinit-method属性があるじゃないか」とおっしゃる人もいると思いますが、これだとメソッドに引数を渡せないのが、時と場合によっては非常に不便なんです。
あと、メソッドを1個しか呼べないのも。

Seasar2ならinitMethod「タグ」があり、引数も呼び出す回数も思いのまま。

ということで、Springの気が利かない、この初期化メソッド呼び出しを、
Seasar2のように、引数を渡したり複数回呼び出したりできるように改良をチャレンジしてみます。

最終的には、こんな風に書けるようになることがゴール。

<bean id="table" class="sample.Table">
  <init-method>self.addColumn("userId", "ユーザID")</init-method>
  <init-method>self.addColumn("userName", "ユーザ名")</init-method>
</bean>

その前に、まずはSpringで独自のタグを作成する方法について、メモしておきます。
(いろいろ探してみたけど、英語のサイトしかなかった・・・)

独自定義のタグを作ってみる

一旦本題から離れて、独自定義のタグを作る方法について書きます。

SpringのBean定義で以下のようなオブジェクト構造を作る場合、

普通に書くと、

<bean id="table" class="sample.Table">
  <property name="columns">
    <list>
      <bean class="sample.Column">
        <property name="id" value="userId" />
        <property name="name" value="ユーザID" />
      </bean>
      <bean class="sample.Column">
        <property name="id" value="userName" />
        <property name="name" value="ユーザ名" />
      </bean>
    </list>
  </property>
</bean>

となりますが、この の部分を独自定義のタグ「column」に置き換えてみます。


今回作るファイルは、以下の5つ。

「〜Parser.java」というファイルは独自のタグを作成する分だけ作成します。

XSDファイルの作成

まずは、タグの構造を定義するXSDファイルを作成します。

sample.xsd

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<xsd:schema xmlns="http://www.foo.com/schema/sample"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://www.foo.com/schema/sample">

  <xsd:element name="column">
    <xsd:complexType>
      <xsd:attribute name="id" type="xsd:string" use="required" />
      <xsd:attribute name="name" type="xsd:string" use="required" />
    </xsd:complexType>
  </xsd:element>

</xsd:schema>

これにより、columnタグの定義ができました。

ハンドラとパーサの作成

次に、XMLを解析してオブジェクトを生成するハンドラとパーサを作成します。
ここでは、columnタグを解析するパーサを登録します。

SampleBeanXmlHandler.java

public class SampleBeanXmlHandler extends NamespaceHandlerSupport {
    public void init() {
        // columnタグを解析するオブジェクトを登録する
        registerBeanDefinitionParser("column", new ColumnBeanDefinitionParser());
    }
}

ColumnBeanDefinitionParser.java

public class ColumnBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
    protected Class<?> getBeanClass(Element element) {
        // Columnクラスのオブジェクトを生成することをSpringに伝える
        return Column.class;
    }

    protected void doParse(Element element, ParserContext parserContext,
                BeanDefinitionBuilder builder) {
        // XMLのcolumnタグのid属性の値を取得する
        String id = element.getAttribute("id");

        if (StringUtils.hasLength(id)) {
            // 属性が指定されている場合は、Columnのidプロパティに値をセットする
            builder.addPropertyValue("id", id);
        }

        String name = element.getAttribute("name");
        if (StringUtils.hasLength(name)) {
            builder.addPropertyValue("name", name);
        }
    }
}

namespaceとXSDファイルの関連付け

src/main/resources/META-INF配下にspring.schemasファイルを作成して、XSDのURLとXSDファイルの関連付けを行います。

http\://www.foo.com/schema/sample/sample.xsd=sample/xml/sample.xsd

URLがhttpから始まっていますが、あくまでこれは名前であり、
実際に上記URLにファイルを配置しなくても動作します。
(Spring等では、実際にインターネット上にXSDファイルを配置しています。)

namespaceとハンドラの関連付け

spring.schemasと同じく、src/main/resources/META-INF配下にspring.handlersファイルを作成して、namespaceとハンドラの関連付けを行います。

http\://www.foo.com/schema/sample=sample.xml.SampleBeanXmlHandler

作成したタグの使用

設定は完了したので、最後はタグを使用する方法です。
beansタグのxmlns属性で、作成したタグのnamespaceを定義します。
あとは、<namespace:タグ名>の形式で記述するだけ。

context.xml

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

  <bean id="table" class="sample.Table">
    <list>
      <sample:column id="userId" name="ユーザID" />
      <sample:column id="userName" name="ユーザ名" />
    </list>
  </bean>

</beans>

これで、独自定義のタグ「column」が使用できるようになりました。

そして

本題の続きは次回ということで^^;

では。