DIVE INTO S2 .HACK : INCLUDE PART II

Seasar2に慣れ親しむためにS2Containerのコードをプチハックしてみる第2弾。 v(^^)v

  • カスタムPathResolverを使って、S2Container間の依存関係(include)をDEPENDENCY-LOOKUPからDEPENDENCY-INJECTIONにしてみる。

koichikさんのアドバイスをもとに、PathResolverインターフェースを実装した AliasAwarePathResolverクラス を作ってから、デフォルトのPathResolverを差し替えることで、できちゃいました。これぞまさしく OPEN-CLOSED PRINCIPLE !!!

    • テスト用のdiconファイルの構成は前回と同じで、こんな感じです。*1

■org/seasar/framework/container/factory/AliasAwarePathResolverTestNode0.dicon







component0
component1
component2

"Here is node0 container."

■org/seasar/framework/container/factory/AliasAwarePathResolverTestNode1.dicon




"Here is node1 container."

■org/seasar/framework/container/factory/AliasAwarePathResolverTestNode2a.dicon



"Here is node2a container."

■org/seasar/framework/container/factory/AliasAwarePathResolverTestNode2b.dicon



"Here is node2b container."

    • テストコードは、こんな感じです。ポイントは赤い部分で“S2Container自体の振る舞いを変更”している点です。S2Container本体を構成するコンポーネントのうち、差し替えたいコンポーネントを作って*2、それをコンテナ制御用のdiconファイル*3に設定して、S2ContainerFactory.configure("制御用diconファイルのパス") とするだけで、カスタムS2Containerの出来上がりです。素晴らしい!!!

■org/seasar/framework/container/factory/AliasAwarePathResolverTest.java


package org.seasar.framework.container.factory;

import java.util.List;

import junit.framework.TestCase;

import org.seasar.framework.container.S2Container;

/**
* @author hidebsd
*/
public class AliasAwarePathResolverTest extends TestCase {

private static final String []S2CFG = "org/seasar/framework/container/factory/AliasAwarePathResolverConfiguration.dicon"[];

private static final String PATH0 = "org/seasar/framework/container/factory/AliasAwarePathResolverTestNode0.dicon";

private static final String PATH1 = "org/seasar/framework/container/factory/AliasAwarePathResolverTest1.dicon";

public void setUp() {
[]S2ContainerFactory.configure(S2CFG)[];
}

public void testDefaultSettings() throws Exception {
S2Container container = S2ContainerFactory.create(PATH0);
container.init();
List list = (List) container.getComponent("collection");
for (int i = 0; i < list.size(); ++i) {
if (i == 2) {
assertEquals("node" + i, "Here is node" + i + "a container.",
list.get(i));
} else {
assertEquals("node" + i, "Here is node" + i + " container.",
list.get(i));
}
}
}

public void testOverrideSettings() throws Exception {
S2Container container = S2ContainerFactory.create(PATH1);
container.init();
List list = (List) container.getComponent("collection");
for (int i = 0; i < list.size(); ++i) {
if (i == 2) {
assertEquals("node" + i, "Here is node" + i + "b container.",
list.get(i));
} else {
assertEquals("node" + i, "Here is node" + i + " container.",
list.get(i));
}
}
}
}

    • で、入れ替えるクラスを作りました。今回は、PathResolverインターフェースを実装したAliasAwarePathResolverなるクラスを作成。中身は前回のIncludeTagHandlerに追加した処理とほぼ同じ。

■org/seasar/framework/container/factory/AliasAwarePathResolver.java


package org.seasar.framework.container.factory;

import java.util.HashMap;
import java.util.Map;

import org.seasar.framework.util.ClassUtil;

/**
* @author hidebsd
*/
public class AliasAwarePathResolver implements PathResolver {

private Map aliasDefinitions = new HashMap();

private String prefix = "@";

private String delimiter = ":";

/**
* AliasAwarePathResolverを構築します。
*/
public AliasAwarePathResolver() {
}

/**
* プリフィックスとセパレータを指定して、AliasAwarePathResolverを構築します。
*
* @param prefix
* 別名を示すプリフィックス
* @param delimiter
* 別名とパスを区切るセパレータ
*/
public AliasAwarePathResolver(String prefix, String delimiter) {
this.prefix = prefix;
this.delimiter = delimiter;
}

/**
* パスとその別名が指定された場合には、これを登録し、パスまたは別名が指定された場合には、適切なパスを返します。
*
* @param context
* コンテキスト
* @param path
* パス
* @see org.seasar.framework.container.factory.PathResolver#resolvePath(java.lang.String,
* java.lang.String)
*/
public String resolvePath(String context, String path) {

if (!path.startsWith(prefix)) {
return path;
}
int nameSep = path.indexOf(delimiter);
if (nameSep < 0) {
// Resolve path
String name = path.substring(prefix.length());
path = resolvePath(name);
} else if (nameSep > 1) {
// Register alias-path mapping
String name = path.substring(prefix.length(), nameSep);
path = path.substring(nameSep + delimiter.length());
registerPath(name, path);
path = []ClassUtil.getResourcePath(getClass()).replaceAll("\\.class$", "Dummy.dicon")[];
} else {
// 'path' attibute value was specified like "@:....".
throw new IllegalStateException(
"Alias name of S2Container was empty.");
}
return path;
}

private void registerPath(String name, String path) {
if (aliasDefinitions.get(name) != null) {
return;
}
aliasDefinitions.put(name, path);
}

private String resolvePath(String name) {
String path = (String) aliasDefinitions.get(name);
if (path == null) {
throw new IllegalStateException("Alias name '" + name
+ "' was not defined.");
}
return path;
}
}

    • で、S2Containerの挙動制御用diconはこんな感じで。PathResolverインターフェースを実装しているクラスがあると差し替えられるので、これでOK。

■org/seasar/framework/container/factory/AliasAwarePathResolverConfiguration.dicon





    • あ、それから、diconファイルパスの別名を登録する場合、返すパスがないのですが、なにかしらdiconファイルのパスを返す必要があったので*4、ダミーのdiconファイルを作成。上記ソースコードの青色部分で使用してます。

■org/seasar/framework/container/factory/AliasAwarePathResolverDummy.dicon



    • では、テスト開始〜!
    • RED RED RED RED GREEN!
    • YATTAA!!!
    • 次回は、ResourceResolverを使ったやり方に挑戦!*5 *6

*1:xml宣言とDTDの部分は省略してあります

*2:特定のインターフェースを実装したクラス

*3:デフォルトはクラスパスルートのs2container.dicon

*4:Alias登録の場合に、前回と同様にnullを返したらおこられちゃいました…

*5:S2ContainerFactory.DefaultProvider を継承したクラスで対応した方が良さげかも…?

*6:なんか カ☆ナ☆リ 楽しくなってきました♪