T2+GAE+guice+AIR 多分その2 拡張してみる
T2はDIコンテナがいろいろ選べて、自分の好みのDIコンテナが使えます。
タイトルにもあるように、私のサイトではGoogle Guiceを利用しているのですが、
少々他のDIコンテナに比べて問題があります。
それはT2のもつ拡張性を半減させている点です。
Guiceを利用する場合、
他のDIコンテナ(といってもSeasarしかやってみてませんが。。。)と違い、
複数コンポーネントの取得ができません。
なんだそれって感じですが、これのせいでT2では以下の問題が出てきます。*1
- ExceptionHandlerが拡張できない(使えない)。
- Pluginが複数設定できない。
この二つは結構重要で、
たとえば、独自のException(認証例外とか)を作って、
それにより処理を変えたりができません。*2
またPluginにより、Pageクラス処理の前で、さまざまな事をしたい場合、
それを一つのメソッド内で終える必要があります。
じゃあどうすればよいか。。。
考えられるのは以下で、
- DIコンテナを変える。
- がまん
- 運用で回避
- Adapterを拡張する。
1.は無理じゃなくもないのですが、
すでにGuice向けに開発しているものを、
他の者に変えるのは若干リスクがあります。
2.は勉強目的もあるのでやです
3.は大手企業さんがいうことじゃないんだからありえません。
で4.です。
基本的な枠組みはすでにT2のGuiceAdapterにできているので、
これ自体を拡張します。
GuiceAdapterの拡張
改善策
私のようなしょぼレベルで考えると要は、
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
※注 いい設計ではないと思ってます。
サイトのほうはバックグランドのほうはできました。
あとはフロントの部分だけです。
これが一番時間がかかるんですけどね。。。