M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

Spring XMLでScalaビーン定義する方法、関数指向なビーン定義の方法、ほか

とくに直近どうこうしようというわけでもないのですが、Spring Scalaについて調べたので翻訳を載せておきます。
訳出したのはSpring ScalaGitHubに掲載されていたWikiドキュメントです。
プロジェクトの製品同様Apache 2.0 ライセンスなのかなー、そういうのよくわかんないなーと。

それほど長くもない文章だったので、以下の4ページすべてをまとめて掲載します:


     * * *

Spring XMLScalaビーンを定義する

デフォルトでは、ScalaのクラスはJavaBeansのプロパティ規約(例えば、`String getFoo()` とか `void setFoo(String)`といったもの)に準拠していません。
この規約の代わりに、Scalaは独自の規約を持っています:ゲッターメソッドには`get`というプレフィックスを使用せず、フィールド名をそのままメソッド名として使用します(例:`String foo()`)。Scalaのセッターメソッドには末尾に`_$eq`を付けたフィールド名を使用します (`void foo_$eq(String)`)。

例えば、以下のScalaクラスは:

class Person(val firstName: String, val lastName: String) {
    var middleName: String =  _
}

Javaクラスとして見た場合、以下のようになります:

public class JavaPerson {
    private final String firstName;
    private String middleName;
    private final String lastName;

    public JavaPerson(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String firstName() {
        return firstName;
    }

    public String lastName() {
        return lastName;
    }

    public String middleName() {
        return middleName;
    }

    public void middleName_$eq(String middleName) {
        this.middleName = middleName;
    }
}

ご覧のとおり、Scalaコンパイラはすべてのvalのためにゲッターメソッドを自動生成し、加えてvarである`middleName`のためにはセッターメソッド〔とゲッターメソッド〕を自動生成します。

SpringコンテナはJavaBean規約にしたがったプロパティにしか対応していません。ScalaクラスをScalaビーンとして関連付けるためには、以下の3つの異なる解法から1つを選ぶ必要があります。

コンストラクタによる依存性注入

Spring XMLのアプリケーション・コンテキストにおいてScalaビーンを紐付けるためにとれるもっとも容易かつ好ましい方法は、シンプルにコンストラクタ・インジェクションを使用するものです。

例えば、私たちは以下のScalaクラスを使用しているとしましょう:

class Person(val firstName: String, val lastName: String)

あなたはこのクラスを次のようにして紐付けることができます:

<bean id="person" class="Person">
    <constructor-arg value="John"/>
    <constructor-arg value="Doe"/>
</bean>

あるいはより簡潔に、c名前空間を使用することもできます:

<bean id="constructor" class="Person" c:firstName="John" c:lastName="Doe"/>

注意点として、`val`とともにコンストラクタ・インジェクションを使用する場合、`Person`クラスは〔コンストラクタを通じて設定されるプロパティに関して〕イミュータブルになってしまいます。
Scalaのような関数型言語では、イミュータブルなデータ構造を推奨しています。このようなわけで、ScalaでSpringを使用する場合、コンストラクタによる依存性注入は好ましい方法です。

@BeanPropertyアノテーション

前述のコードで、デフォルトではScalaクラスはプロパティに関するJavaBeans規約には従いませんでした。
しかしながら、`@scala.reflect.BeanProperty`を使用すれば、Scalaコンパイラに対して、JavaBeans規約に沿ったゲッター/セッターメソッドを自動生成するよう指示することができます。

例えば、次のクラスは:

class Person(val firstName: String, val lastName: String) {
    @BeanProperty var middleName: String =  _
}

Javaクラスとして見た場合、以下のようになります:

public class JavaPerson {
    private final String firstName;
    private String middleName;
    private final String lastName;

    public JavaPerson(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String firstName() {
        return firstName;
    }

    public String lastName() {
        return lastName;
    }

    public String middleName() {
        return middleName;
    }

    public void middleName_$eq(String middleName) {
        this.middleName = middleName;
    }
    
    public String getMiddleName() {
        return middleName;
    }
    
    public void setMiddleName(String middleName) {
        this.middleName = middleName;
    }
}

ゲッターメソッドとセッターメソッドがクラスの末尾に追加されていることに気付かれたことでしょう。

このクラスは今やJavaBean規約に準拠しているため、標準的なXMLビーン定義で紐付けることが可能です。

<bean id="person" class="Person" c:firstName="John" c:lastName="Doe">
    <property name="middleName" value="de la"/>
</bean>

Spring Scalaを使用する

`@BeanProperty`アノテーションを使用する方法の対案として、バージョン3.2以降のSpringフレームワークでは、`BeanInfoFactory`ストラテジ・インターフェースを使用して、任意のゲッター/セッターを指定できるようになりました。
Spring Scalaには、Scalaの規約に準拠したゲッター/セッターをサポートする`BeanInfoFactory`インターフェースの実装が含まれています。
これにより、コンフィギュレーションに項目を追加することなく、Springが自動でゲッター/セッターを判別できるようになります。

実際的な言い方をすると、Spring Scalaのjarファイルをクラスパス上に配置するだけでScalaのプロパティがサポートされる、ということです。

例えば、以下のようなクラスを:

class Person(val firstName: String, val lastName: String) {
    var middleName: String =  _
}

以下のようにして紐付けることができるのです:

<bean id="person" class="Person" c:firstName="John" c:lastName="Doe">
    <property name="middleName" value="de la"/>
</bean>

middleNameフィールドには`@BeanProperty`がありません。
Spring Scalaを使用することで、難しい設定など一切なく、SpringコンテナはScalaのプロパティに対応してしまいました。

まとめ

要約すると、Spring XMLScalaクラスを紐付けるための 好ましい方法 は、valと組み合わせて用いられる コンストラクタによる依存性注入 です。
しかしもし プロパティによる依存性注入 が避けられないのであれば、Spring Scala によりScalaのセッターメソッドに対応することができます(Spring 3.2以上が必要)。

最後に、XMLではなくScalaコードによりビーンを定義する方法として、`関数型のビーン定義`〔次々節〕 も使用できます。


     * * *

Spring XMLScalaコレクションを関連付ける

Scalaコード内でもJavaコレクションを使用することはできますが、それよりもScalaクラス・ライブラリで定義されている、より強力なコレクションAPIを使用する方が理にかなっているでしょう。
このAPIはコレクションの広範にわたる機能を提供するだけでなく、ミュータブル/イミュータブルのそれぞれのヴァリアントもまた提供しています。

ScalaコレクションAPIをサポートするため、Spring XML は2つの方法を用意しました: `PropertyEditor`とXML名前空間です。
もちろん、`関数型のコンフィギュレーション`〔次節〕もまたScalaのコレクションを設定するために使用可能です。

Scalaコレクション `PropertyEditor`

Springは異なる型のオブジェクトを相互に変換するため`PropertyEditor`を使用します。
Spring ScalaScalaコレクションAPIをサポートするプロパティ・エディタのセットを持っています。

  • `Seq` (ミュータブル/イミュータブルいずれも)
  • `IndexedSeq` (ミュータブル/イミュータブルいずれも)
  • `ResizableArray`
  • `LinearSeq` (ミュータブル/イミュータブルいずれも)
  • `Buffer`
  • `Set` (ミュータブル/イミュータブルいずれも)
  • `Map` (ミュータブル/イミュータブルいずれも)

これらのコレクションに対するサポートを有効にするには、以下のようにXMLの要素をアプリケーション・コンテキストに追加する必要があります:

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="propertyEditorRegistrars">
        <bean class="org.springframework.scala.beans.propertyeditors.ScalaEditorRegistrar"/>
    </property>
</bean>

それが済んだら、コレクションの関連付けができるようになります。
例えば、以下のクラスでは:

class ScalaCollectionBean(val scalaSeq: Seq[String])

以下のように関連付けができるようになります:

<bean id="scalaCollection" class="ScalaCollectionBean">
    <constructor-arg>
        <list>
            <value>Foo</value>
            <value>Bar</value>
        </list>
    </constructor-arg>
</bean>

カーテンの裏側では、Spring Scalaの`ScalaCollectionEditor`が、`<list>`(実際は`java.util.List`)とScalaの`Seq`とを変換しているのです。

`scala-util` XMLスキーマ

Spring Javaにおける`util`名前空間と同様、Spring Scalaには`scala-util`名前空間が用意されています。
`Seq`、`Set`、そして`Map`を定義するため、この名前空間には、それぞれ`seq`、`set`、そして`map`要素が含まれています。

例えば、以下の`ScalaCollectionBean`の例をご覧ください:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:scala-util="http://www.springframework.org/schema/scala/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/scala/util http://www.springframework.org/schema/scala/util/scala-util.xsd">

<bean id="scalaCollection" class="org.springframework.scala.demo.collection.ScalaCollectionBean">
   <constructor-arg ref="scalaSeq"/>
</bean>

<scala-util:seq id="scalaSeq">
   <value>Foo</value>
   <value>Bar</value>
</scala-util:seq>


     * * *

関数型のビーン定義

`XMLによるビーン定義`(前々節)に加えて、Spring Scalaは、ScalaクラスによってSpringビーンの定義をする方法も提供しています。
このアプローチは、アノテーションではなく関数に基づいているという点を除けば、標準のSpringにおける`@Configuration`を使用する方法と似通っています。

`FunctionalConfiguration`を使用したビーン定義

`FunctionalConfiguration`トレイトをコンフィギュレーション・クラスにミックスインするだけで、関数型のSpringコンフィギュレーションを作成できます。
ビーンはトレイト上の`bean`という名前のメソッドで定義され、インスタンス化されます。

私たちは以下の`Person` クラスを使用しているとします:

class Person(val firstName: String, val lastName: String) {
    var father: Person = _
    var mother: Person = _
}

もっともシンプルな形式では、関数型コンフィギュレーションは以下のようになります:

class PersonConfiguration extends FunctionalConfiguration {
    bean() {
        new Person("John", "Doe")
    }
}

このコンフィギュレーションは自動生成された名前でもっとPersonビーンのシングルトンを登録するものです。
これは以下のSpring XMLとまったく同義の内容です:

<beans>
    <bean class="Person">
        <constructor-arg value="John"/>
        <constructor-arg value="Doe"/>
    </bean>
</beans>

もちろん、名前を指定したり、エイリアスを与えたり、スコープを設定して、ビーンを登録することもできます:

class PersonConfiguration extends FunctionalConfiguration {
    bean("john", aliases = Seq("doe"), scope = BeanDefinition.SCOPE_PROTOTYPE) {
        new Person("John", "Doe")
    }
}

このコンフィギュレーションでは、あるPersonインスタンスが、"john"と"doe"という名前で, prototypeスコープで登録されています。

FunctionalConfigApplicationContext

`ClassPathXmlApplicationContext`クラスの初期化時にSpring XMLファイルが入力ファイルとして使用され、`AnnotationConfigApplicationContext`クラスの初期化時に `@Configuration`アノテーションが施されたクラスが入力データとして使用されるのと同様に、`FunctionalConfigApplicationContext`コンパニオン・オブジェクトには`FunctionalConfiguration` トレイトが入力データとして使用されます:

object PersonConfigurationDriver extends App {
    val applicationContext = FunctionalConfigApplicationContext[PersonConfiguration]
    val john = applicationContext.getBean(classOf[Person])
    println(john.firstName)
}

ビーン登録の便宜ためのメソッド

プロトタイプ〔必要になるたびに新しくインスタンス化されるビーン〕を登録するための方法としてもう1つ、`prototype`という便利なメソッドが存在します。
次のように:

class PersonConfiguration extends FunctionalConfiguration {
    prototype("john") {
        new Person("John", "Doe")
    }
}

`prototype`に加えて、`singleton`というメソッドも存在しますが、こちらはシングルトンを登録するためのものです。

ビーン参照

ここまでご覧にいれていなかったのですが、実のところ`bean()`メソッドは戻り値を持つため、変数に代入することができます。
`bean()`メソッドの戻り値の型は`BeanLookupFunction[T]`です。ここで`T`は定義されたビーンのクラスの型です。
`BeanLookupFunction`は本質的には引数を取らないScalaの関数であり、実行された時に定義されたビーンを調べます〔つまり必要になった時点ではじめて対象のクラスの初期化を試みる〕。
戻り値は他のビーンを参照する目的で使用することができます。例えば以下のように:

class PersonConfiguration extends FunctionalConfiguration {
    val jack = bean() {
        new Person("Jack", "Doe")
    }

    val jane = bean() {
        new Person("Jane", "Doe")
    }

    val john = bean() {
        val john = new Person("John", "Doe")
        john.father = jack()
        john.mother = jane()
        john
    }
}

上記の例のなかで、私たちは3つのビーン(ジャックとジェーン、そしてジョン)を定義し、それらを3つのvalに格納しました。
これらのvalは`BeanLookupFunction`型なので、ビーン定義に照会しPersonインスタンスを得るためにはこれを実行する必要があります(そのような次第で丸カッコが使用されているのです)。

`bean()`がそのものずばり`T`型のインスタンスではなく、 関数である`() => T`を返すのはビーンのスコープ管理と関係があります。
もし仮に`bean()`がT型インスタンスを返したら、そしてその値を変数に格納してしまうとしたら、その変数はいつ使用しても〔メモリ上の〕同じビーンを参照することになってしまいます。これはビーンのスコープがシングルトンでない場合ですらそうなるということです。
関数が返されることで、ビーンが要求されるごとに都度ビーンの照会が行われ、必要な場合は新しいインスタンスが初期化されることを確実にできます。
この点で注目すべきことに`singleton()`メソッドは`T`型インスタンスを返します。これはビーンが要求されるたび同じビーン・インスタンスを参照すればよいためです。

コンフィギュレーションの複合構造化

関数型コンフィギュレーションScalaクラスにより実装されているので、継承、抽象クラス、その他の機能を使って、複数のクラスからコンフィギュレーションを組み立てることもできます。

abstract class PersonConfiguration extends FunctionalConfiguration {
    val firstName: String
    val lastName: String
    bean() {
        new Person(firstName, lastName)
    }
}

class JohnDoeConfiguration extends PersonConfiguration {
    val firstName = singleton() {
        "John"
    }
    val lastName = singleton() {
        "Doe"
    }
}

もちろんトレイトも使えます。ただしいずれの`BeanLookupFunction`のフィールドも lazy とする必要がある点には注意してください。
そうないと、これらのフィールドは適切に初期化されず、例外が発生してしまいます:

trait FirstNameConfig extends FunctionalConfiguration {
    lazy val firstName = bean() {
        "John"
    }
}

trait LastNameConfig extends FunctionalConfiguration {
    lazy val lastName = bean() {
        "Doe"
    }
}

class JohnDoeConfiguration extends FirstNameConfig with LastNameConfig {
    val john = bean() {
        new Person(firstName(), lastName())
    }
}

XMLと`@Configuration`クラスによる定義を読み込む

すべてのSpringビーン定義を`FunctionalConfiguration`で済まさないといけないということはありません。
Spring XMLと`@Configuration`アノテーションの情報はそれぞれ`importXml`と`importClass`というメソッドでインポートすることができます。これら異なるコンテキストで定義されたビーンたちは、`FunctionalConfiguration`に定義された`getBean()`メソッドを通じて参照することができます。

<beans>
    <bean id="firstName" class="java.lang.String">
        <constructor-arg value="John"/>
    </bean>
    <bean id="lastName" class="java.lang.String">
        <constructor-arg value="Doe"/>
    </bean>
</beans>
class PersonConfiguration extends FunctionalConfiguration {
    importXml("classpath:/names.xml")

    val john = bean() {
        new Person(getBean("firstName"), getBean("lastName"))
    }
}

直前の例では、`firstName` と `lastName` というビーン定義を参照するため、Spring XMLファイルがインポートされています。
同様のことは`@Configuration`クラスについても実行できます:

@Configuration
public class NameConfiguration {
    @Bean
    public String firstName() {
        return "John";
    }
    @Bean
    public String lastName() {
        return "Doe";
    }
}
class PersonConfiguration extends FunctionalConfiguration {
    importClass(classOf[NameConfiguration])

    val john = bean() {
        new Person(getBean("firstName"), getBean("lastName"))
    }
}

初期化/破棄時に実行される関数を登録する

Spring XMLでinitメソッドとdestroyメソッドを登録するのと同様、`FunctionalConfiguration`でもこれらのメソッド登録が行えます。
2つの関数はいずれも`T => Unit`として定義されます。このとき`T`は登録されているビーンの型を表します。
このことはつまり、登録されているビーンは基本的に2つの関数のパラメータとして渡されるということを意味しています。

例えば、以下は関数型コンフィギュレーションにおけるCommons DBCPデータストアの典型的な登録のされ方を示しています:

class DataSourceConfiguration extends FunctionalConfiguration {
    val dataSource = bean("dataSource") {
        val dataSource = new BasicDataSource()
        dataSource.setDriverClassName("com.mysql.jdbc.Driver")
        dataSource.setUrl("jdbc:mysql://localhost/mydb")
        dataSource.setUsername("foo")
        dataSource.setPassword("bar")
        dataSource
    } destroy {
        _.close()
    }
}

上記の例の中で、私たちは`dataSource`ビーンを定義し、続いて必要とされるプロパティのすべてを設定しています。
ここでは`init`関数を使うまでもなく初期化処理を済ませることが出来ました。`destroy`関数の中では、`BasicDataSource`オブジェクトの`close()` メソッドが呼び出されています。これによって、アプリケーション・コンテキストがシャットダウンする際、データソースへの接続もクローズされることが確実なものとすることができます。これでコネクション・リークが発生することはありません。

ビーン・プロファイル

いずれのビーン定義も`profile`ブロックで囲うことで、それぞれのプロファイルがアクティブなときのみ囲われたビーンが登録されるようにできます。

class DataSourceConfiguration extends FunctionalConfiguration {
    profile("dev") {
        bean("dataSource") {
            val dataSource = new BasicDataSource()
            // Set up properties
            dataSource
        } destroy {
            _.close()
        }
    }
    profile("prod") {
        bean("dataSource") {
            val dataSource = new OracleDataSource()
            // Set up properties
            dataSource
        }
    }
}

上記の例では、`dev`プロファイルが有効なときのみ`BasicDataSource`が登録され、`prod`プロファイルが有効なときのみ`OracleDataSource` が登録されます。


     * * *

Scalaコード内でSpringテンプレートを使用する

Springのテンプレートは、種々のデータアクセスや一般的なリソース・ハンドリングを支援する便利なユーティリティ・クラス群です。
Spring Javaでは、これらのテンプレートはたいてい(匿名インナークラスの)コールバックとともにはたらいています:

ConnectionFactory connectionFactory = ...
JmsTemplate template = new JmsTemplate(connectionFactory);

template.send("queue", new MessageCreator() {
    public Message createMessage(Session session) throws JMSException {
        return session.createTextMessage("Hello World");
    }
});

Message message = template.receive("queue");
if (message != null && message instanceof TextMessage) {
    System.out.println(((TextMessage) message).getText());
}
else {
    System.out.println("No text message received");
}

Scalaでは、この目的のために〔匿名インナークラスではなく〕関数を使用するほうが理にかなっていまs。
Spring ScalaはテンプレートをよりScalaの言語構造やコーディング・スタイルに適したものにするためのラッパーを提供しています。
一般的に、これらのScalaテンプレート・ラッパーには、Scala言語内でJavaバージョンのテンプレートを直接使用する場合と比べて、3つのメリットがあります:

  • コールバック・インターフェースの代わりに関数を使用できること
  • Javaバージョンであれば`null`が返されたシチュエーションで`Option`が使用できること
  • クラス・パラメータの代わりにクラス・マニフェストが使用できること

これら3つの改善により、前掲のJavaコードはScalaであれば次のように記述できます:

val connectionFactory : ConnectionFactory = ...
val template = new JmsTemplate(connectionFactory)

template.send("queue") {
    session: Session => session.createTextMessage("Hello World")
}

template.receive("queue") match {
    case Some(textMessage: TextMessage) => println(textMessage.getText)
    case None => println("No text message received")
}

クラス・マニフェストを使用することで以下のようなコードは:

RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject("http://example.com", String.class);

Scalaでは次のように記述できます:

val template = new RestTemplate()
val result = template.getForAny[String]("http://example.com")

Scalaフレンドリーなテンプレート

一般に、Scalaバージョンのテンプレート・ラッパーは、Javaの対応するモジュールと同じ──あいだに`scala`パッケージが挿入されることを除いて──同じパッケージに所属しています。
したがって、`JmsTemplate`のScalaバージョンは`org.springframework.scala.jms.core`パッケージに所属し、`SimpleJdbcTemplate`は`org.springframework.scala.jdbc.core.simple`パッケージに所属します。

ここまで解説をしてきたように、Scalaフレンドリーなテンプレートとして以下があります:

  • `SimpleJdbcTemplate`
  • `JmsTemplate`
  • `RestTemplate`
  • `TransactionTemplate`

これらのすべてで先ほど解説した改善がなされています。すなわち:関数、`Option`、そしてクラス・マニフェストの使用です。

`TransactionManagement`トレイト

`TransactionTemplate`に関連し、Spring Scalaは`TransactionManagement`トレイトも提供しています。
このトレイトは`transactional`という名前の単一のメソッドを持っています。このメソッドはパラメータとして関数をとります。
関数内のコードは標準のSpring transaction managementを使用してトランザクションとして実行されます。

例えば以下のように:

class TransactionalExample extends TransactionManagement {
    val dataSoure: DataSource = ...
    val jdbcTemplate = new SimpleJdbcTemplate(dataSource)
    val transactionManager = new DataSourceTransactionManager(dataSource)

    val result: String = transactional() {
        status => {
            jdbcTemplate.queryForObject[String]("SELECT FIRST_NAME FROM USERS WHERE ID = 1")
        }
    }
}

     * * *

原典はSpring ScalaGitHubに掲載されていたWikiドキュメントです。


Spring関連の訳出記事まとめ - M12i.