T2+GAE+guice+AIR 多分その2 拡張してみる

T2はDIコンテナがいろいろ選べて、自分の好みのDIコンテナが使えます。
タイトルにもあるように、私のサイトではGoogle Guiceを利用しているのですが、
少々他のDIコンテナに比べて問題があります。
それはT2のもつ拡張性を半減させている点です。

Guiceを利用する場合、
他のDIコンテナ(といってもSeasarしかやってみてませんが。。。)と違い、
複数コンポーネントの取得ができません。

なんだそれって感じですが、これのせいでT2では以下の問題が出てきます。*1

  1. ExceptionHandlerが拡張できない(使えない)。
  2. Pluginが複数設定できない。

この二つは結構重要で、
たとえば、独自のException(認証例外とか)を作って、
それにより処理を変えたりができません。*2
またPluginにより、Pageクラス処理の前で、さまざまな事をしたい場合、
それを一つのメソッド内で終える必要があります。

じゃあどうすればよいか。。。
考えられるのは以下で、

  1. DIコンテナを変える。
  2. がまん
  3. 運用で回避
  4. Adapterを拡張する。

1.は無理じゃなくもないのですが、
すでにGuice向けに開発しているものを、
他の者に変えるのは若干リスクがあります。

2.は勉強目的もあるのでやです

3.は大手企業さんがいうことじゃないんだからありえません。

で4.です。
基本的な枠組みはすでにT2のGuiceAdapterにできているので、
これ自体を拡張します。

GuiceAdapterの拡張

問題点

まず何が問題かを考えると、
Guiceがあるクラス(インターフェース)に対して、
複数のクラスを定義できないことが問題です。*3
なので、上記の二つの問題が出てきます。

改善策

私のようなしょぼレベルで考えると要は、
1対多のリレーションが貼れないから、
この問題が起きるであれば、
1対多のリレーションを貼るものを作ればよいとおもいます。
かつGuiceAdapterでは最初から、
ModuleをServiceLoaderでファイルを読み込むので
同じようにファイルでそのリレーションを
作ってしまえばいいじゃん、と考えました。

実装

でとりあえず作ってみました。

package jp.stk.gae.adapter;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.LineIterator;
import org.t2framework.commons.util.CollectionsUtil;
import org.t2framework.commons.util.Reflections.ClassUtil;
import org.t2framework.t2.adapter.GuiceAdapter;
import org.t2framework.t2.handler.ExceptionHandler;
import org.t2framework.t2.plugin.Plugin;

public class StkGuiceAdapter extends GuiceAdapter {
        //インターフェースとクラスをひもづけるマップ
	protected Map<Class<?>, List<Class<?>>> componentsMap = CollectionsUtil
			.newConcurrentHashMap();

	@Override
	public void init() {
		super.init();
        //とりあえずT2のクラスだけリレーションを張る。
		componentsMap.put(ExceptionHandler.class, load(ExceptionHandler.class));
		componentsMap.put(Plugin.class, load(Plugin.class));
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> List<T> getComponents(Class<? super T> componentClass) {
        //所持していなければ、同じ動作
		if (!componentsMap.containsKey(componentClass)) {
			return super.getComponents(componentClass);
		}

		List<T> resultList = new ArrayList<T>();
         //リレーションマップより本当に紐づているクラスリストを取得
		List<Class<?>> classList = (List<Class<?>>)componentsMap.get(componentClass);
		for (Class<?> clazz : classList) {
			try {
				T t = (T) injector.getInstance(clazz);
				resultList.add(t);
			} catch (Throwable throwable) {
                //TODO もうちょっとかんがえる。
				throwable.printStackTrace();
				continue;
			}
		}
		if (resultList.isEmpty()) {
			return super.getComponents(componentClass);
		}
		return resultList;
	}

    //上の処理のExceptionHandler版
	@Override
	public List<ExceptionHandler<Throwable, Exception>> createExceptionHandlers() {
		try {
			return this.getComponents(ExceptionHandler.class);

		} catch (Throwable t) {
			return CollectionsUtil.emptyList();
		}
	};

    //ファイルからリレーションの作成処理
    //ServiceLoaderと同じで、引数のクラス名から、ファイル名を作成し、
    //ファイルより、ひもづけさせるクラスを取得する。
	@SuppressWarnings("unchecked")
	private <T> List<Class<?>> load(Class<T> serviceClass) {
         //一応スレッドセーフなりすと
        //TODO 必要ない?
		List<Class<?>> serviceList = new CopyOnWriteArrayList();

		try{
			LineIterator iterator = null;
			for(iterator = 
IOUtils.lineIterator(StkGuiceAdapter.class.getResourceAsStream("/META-INF/services/" + serviceClass.getName()) , "UTF-8")
					;iterator.hasNext();){
				String line = iterator.nextLine();
				Class<?> clazz = ClassUtil.forName(line);
				serviceList.add(clazz);
			}
		}catch(Exception e){
            //TODO やる気をだす
			throw new RuntimeException(e);
		}
		return serviceList;
	}

	@Override
	public void destroy() {
		super.destroy();
		componentsMap.clear();
		componentsMap = null;
	}
}

まだまだ改良中ですが、とりあえず動いているので、
あとはパフォーマンス考えて直します。

設定

まずModuleを作ります。

package jp.stk.gae.module;


import jp.stk.gae.handler.StkExceptionHandler;
import jp.stk.gae.plugin.AuthPlugin;
import jp.stk.gae.plugin.PagePlugin;
import jp.stk.gae.plugin.RequestPlugin;
import jp.stk.gae.plugin.impl.AuthPluginImpl;
import jp.stk.gae.plugin.impl.RequestPluginImpl;
import jp.stk.gae.service.CategoryService;
import jp.stk.gae.service.FuriganaService;
import jp.stk.gae.service.QuestionService;
import jp.stk.gae.service.impl.CategoryServiceImpl;
import jp.stk.gae.service.impl.FuriganaServiceImpl;
import jp.stk.gae.service.impl.QuestionServiceImpl;

import org.t2framework.t2.plugin.Plugin;

import com.google.inject.AbstractModule;
import com.google.inject.Scopes;
import com.google.inject.servlet.ServletModule;

public class ApplicationModule extends AbstractModule {

	@Override
	protected void configure() {
		install(new ServletModule());
		install(new JdoModule());
		install(new GaeServiceModule());
                //実際は、一つぐらいは基底クラス(ExceptionHandler)の実態クラスとして、定義しておいた方がよい
		bind(StkExceptionHandler.class).toInstance(new StkExceptionHandler());
        //こんな感じで
		bind(Plugin.class).to(PagePlugin.class).in(Scopes.SINGLETON);
		bind(AuthPlugin.class).to(AuthPluginImpl.class).in(Scopes.SINGLETON);
		bind(RequestPlugin.class).to(RequestPluginImpl.class).in(Scopes.SINGLETON);


		bind(QuestionService.class).toInstance(new QuestionServiceImpl());
		bind(FuriganaService.class).toInstance(new FuriganaServiceImpl());
		bind(CategoryService.class).toInstance(new CategoryServiceImpl());
	}
}

次に、META-INF/service/の下に、
ロード用のファイルを作成します。
ファイル:com.google.inject.Module

jp.stk.gae.module.ApplicationModule

ファイル:org.t2framework.t2.handler.ExceptionHandler

jp.stk.gae.handler.StkExceptionHandler

ファイル:org.t2framework.t2.plugin.Plugin

org.t2framework.t2.plugin.Plugin
jp.stk.gae.plugin.AuthPlugin
jp.stk.gae.plugin.RequestPlugin

これで、複数のPluginや、ExceptionHandlerが定義できるようになります。
もし、元になるファイルが、増えた場合はさらにそのリレーションを作る
ファイルを用意して、読み込ませればいいのかなーと思ってます。

例:
ファイル名:relation.map

org.t2framework.t2.plugin.Plugin
org.t2framework.t2.handler.ExceptionHandler

※注 いい設計ではないと思ってます。

サイトのほうはバックグランドのほうはできました。
あとはフロントの部分だけです。
これが一番時間がかかるんですけどね。。。

*1:他にもあるかもしれないですが

*2:GlobalExceptionHandlerを拡張することはできます

*3:Namedや独自アノテーションという方法もありますが...