Spring Framework Advent Calendar 2011 part.12 JSR-330 への対応

Spring Framework Advent Calender の 12 日目です。昨日に引き続き、アノテーションについての整理をしていきます。

JSR-330 Dependency Injection for Java

JSR-330 は、いわゆる DI の標準仕様を定めるものです。Bob Lee (Guice 作者) と Rod Johnson (Spring Framework 作者) がスペックリードをしています。

名称 説明
@Inject Inject する箇所を示すアノテーション
@Qualifier コンポーネントを限定するための識別子。メタアノテーション
@Named @Qualifier の利用例のひとつ。名前ベースの修飾子
@Scope Injector が生成したインスタンスを使い回すかどうかを指定するメタアノテーション
@Singleton @Scope の利用例のひとつ。コンポーネントが一度だけインスタンス化されることを明示する
Provider T 型のインスタンスを返すプロバイダー。アノテーションではない。通常、Injector とセットで利用される


説明を見ても、分かったような分からないような...。名称に関しては Guice を使っている人のほうが馴染み深いかもしれません。

インストール

maven でのインストール方法のみ書きます。 ディレクティブに以下を追記してください。

  <dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
  </dependency>

利用例

Inject する例 (Spring 的には @Autowired)
@Controller
@SessionAttributes("registerForm")
public class ApplicationController {

    @Inject
    private EchoService echoService;
    

ここではフィールドに記述していますが、コンストラクターやメソッドに書くこともできます。

DI で管理するコンポーネントを宣言する例 (Spring 的には @Component)
@Named
public class DefaultEchoService implements EchoService {

    @Override
    public String echo(String str) {
        return str;
    }
}

@Named("foobar") のように書くこともできます。指定した名前でコンポーネントがDIコンテナに登録されます。何も書かないと、クラス名の先頭を小文字にしたものが登録されるようです(Spring の場合)。

Singleton (Spring ではデフォルト)
@Named("myEchoService")
@Singleton
public class DefaultEchoService implements EchoService {

    @Override
    public String echo(String str) {
        return str;
    }
}

スコープを Singleton にしたい場合に記述します。ただし、Spring のデフォルトは Singleton なので、書く必要があまりないでしょう。一方で JSR-330 の仕様を積極的に採用するなら、Scope のデフォルトは Prototype である必要があります*1。これを実現するには、Jsr330ScopeMetadataResolver を使います。

<context:component-scan base-package="example"  scope-resolver="org.springframework.context.annotation.Jsr330ScopeMetadataResolver" />

上記のように設定すると、デフォルトの Scope が Prototype になります。

@Qualifier, @Scope

これらはメタアノテーションのため、直接記述することはありません。
メタアノテーションとは、アノテーションに記述するアノテーションのことです。実際、たとえば @Named の定義は以下のようになっています。

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {

    /** The name. */
    String value() default "";
}

同様にして、ユーザー (あるいはサードパーティーベンダー) が @Qualifier や @Scope を利用した任意のアノテーションを作成することができます。ただし、アノテーションを定義しただけでは何も起こりませんので、それを DI コンテナ側で解釈するための実装も用意する必要があるでしょう。たとえば、Spring であれば、ScopeMetadataResolver の実装を独自に作成することで @Scope を利用した独自アノテーションの解釈を与えることができます。

Provider
public class Car {
     @Inject Car(Provider<Seat> seatProvider) {
        Seat driver = seatProvider.get();
        Seat passenger = seatProvider.get();
        ...
    }
}

@Named
public class SeatProvider implements Provider<Seat> {
    public Seat get() {
        return new Seat();
    }

インスタンスの生成を自前で管理したい場合に利用します。DI 対象でないコンポーネントを Provider でラップして、Inject 対象にするといったことも可能です。Provider の実装は別で定義しておき @Inject します。

*1:javax.inject.Scope の Javadoc に書かれています。http://docs.oracle.com/javaee/6/api/javax/inject/Scope.html