レベルエンター山本大のブログ

面白いプログラミング教育を若い人たちに

BLOCKVROCKリファレンス目次はこちら

TypeSafeDIProxy パターンまたはGenerics DI Proxy パターン

パターンというか、、、、Springで、Guiceっぽくキャスト無しにするための実装方法

対象とする問題

・DIコンテナから、サービスオブジェクトを取得するときにキャストするのがダサい。
GuiceGenericsをまねしたい。

Type Safe DI Proxy (タイプセーフなDIプロキシ)パターンによる解決策

Type Safe DI Proxyパターンとは、Genericsを使ってサービスオブジェクトの取得時にキャスト不要にするパターンです。
それ以上の効果を見込んでみたんですが、どうもそれ以上でも以下でもなさそうです。

例として、いつものDIのサンプルを使います。

オブジェクト 説明
Service サービスオブジェクトのインターフェイス
ServiceImpl サービスオブジェクトの実装
Dependency 依存性オブジェクトのインターフェイス
DependencyImpl 依存性オブジェクトの実態
ApplicationStart サンプルの起動ポイント
TypeSafeDIProxy SpringのApplicationContextを仲介し、getBean()メソッドをタイプセーフにするクラス

サービスオブジェクトと、ディペンデンシィオブジェクトの2つに対して、インターフェイスと具象クラスのペアを用意します。

このサンプルのポイントである、TypeSafeDIProxyクラスから見て行きます。
このクラスは、SpringのApplicationContextを仲介し、getBean()メソッドをタイプセーフにします。

TypeSafeDIProxy

package net.kronos_jp.generics;

import org.springframework.context.ApplicationContext;

public class TypeSafeDIProxy {
	
	ApplicationContext context;
	public TypeSafeDIProxy(ApplicationContext paContext){
		this.context = paContext;
	}
	
	public <T> T getBean(Class<T> beanClass){
		return (T)this.context.getBean(beanClass.getSimpleName());
	}
}

上記のgetBean()の戻り値は、引数で受け取ったClass型との互換性を必要とするようになります。


次に、アプリケーションの起動クラスは以下です。
SpringのコンテキストをTypeSafeDIProxyクラスでラップして生成しています。

getBean()メソッドで、キャストをせずにサービスオブジェクトが取り出せていることに注目してください。
Genericsの機能で、getBeanメソッドの引数に設定した「Service.class」を戻り値の型として制限しているためです。

ApplicationStart

package net.kronos_jp.generics;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ApplicationStart {
  public static void main(String[] args) {
    // コンテキストを取得
    TypeSafeDIProxy context = 
      new TypeSafeDIProxy(
        new ClassPathXmlApplicationContext(
          "net/kronos_jp/generics/application.xml"
        )
    );

    // サービスオブジェクトを取得
    Service service = context.getBean(Service.class);
          
    // インジェクトされた依存オブジェクトのメソッドを実行
    service.getDependency().hello();
  }
}

ためしに、getBean()メソッドの戻り値を受け取る変数にServiceインターフェイスと互換性の無い型をしていするとコンパイルエラーが表示されるのが分かります。



アプリケーションの設定ファイルでは、サービスオブジェクトのIDを、サービスインターフェイスのクラス名「Service」として定義しています。
これにより、Class型のgetSimpleName()メソッドで受け取れるクラス名と一致します。

application.xml

<?xml version="1.0" encoding="SHIFT-JIS"?>
<beans
  xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

  <bean id="Service" class="net.kronos_jp.generics.ServiceImpl">
    <property name="dependency" ref="dependency"/>
  </bean>
	
  <bean id="dependency" class="net.kronos_jp.generics.DependencyImpl">
    <property name="name" value="yamamoto"/>
  </bean>
</beans>