CDIでアプリケーション設定をインジェクション
この記事は Java EE Advent Calendar 2013 の12/8分の記事です。
昨日はHirofumi Iwasakiさんの WebLogicをGlassFishの商用版として使う方法 です。
明日は@emaggameさんです。よろしくお願いします。
CDI と Commons Configrationと組み合わせて、アプリケーション独自の設定をインジェクションすることを考えてみます。
Java EE のアプリケーションでは、アプリケーションが使用する独自コンフィグ情報(よくあるのは接続先IPなど)を以下のような手段で作成することができます。
web.xmlに定義する
- context-paramに定義してServletContext経由でロード
XMLの定義
<context-param> <param-name>key</param-name> <param-value>value</param-value> </context-param>
Javaで読み込み
ServletContext context = getServletConfig().getServletContext();
String val = context.getInitParameter("key");
- env-entryに定義して@ResouceでEJBにインジェクション
XMLの定義
<env-entry> <env-entry-name>key</env-entry-name> <env-entry-type>java.lang.String</env-entry-type> <env-entry-value>value</env-entry-value> </env-entry>
@Resourceでインジェクション
@Resouce(name = "key") private String parameter;
独自XMLに定義し、読み込んだコンフィグ情報をシングルトン等に格納する
- 実装省略。Web上に色々と例があると思います。
Java EE で用意されているweb.xmlやejb-jar.xmlのコンフィグ化の仕組みでは、XMLであってもデータの階層構造を示しにくい形式であり、アプリケーションのコンフィグ化にはまだまだ独自ファイルを作成してロードしている人も多いと思います。しかし、シングルトンはテスト時に取得する値を書き換えににくい、書き込みを許すとグローバル変数化する等、その依存性の強さからあまり好まれる実装ではありません。ここでは、CDIを使ってパラメータがインジェクションできないか考えてみます。
やりたいこと
コンフィグ例
階層構造を表したいので、XMLにしてみる。クラスパス中にファイルは置く。
<?xml version="1.0" encoding="UTF-8"?> <distination> <machine> <name>Distination Machine 1</name> <ipaddr>192.168.0.1</ipaddr> </machine> </distination>
コンフィグファイルのパス指定
どこにAP独自のコンフィグファイルを置いてあるのか、何らかの方法で指定することが必要です。ハードコーディングするのも、コンフィグの位置をweb.xml等から取得するのも嫌なので、CDIのProducerメソッドで指定してみました。
public class ConfigPathResolver { @Produces @ConfigPath public String configFilePath() { return "servlet/appConf.xml"; } }
コンフィグのインジェクション
以下のように自作CDI修飾子 @Config に引数を与えてコンフィグのキーを指定します。
以下の例ではmachine.ipaddrと指定すると、前述したコンフィグから文字列『192.168.0.1』が取得できる。
@Inject @Config("machine.ipaddr") private String ipaddr;
実装する
@Configを作る
@Nonbindingを忘れるとアノテーションのパラメータも修飾子の対象に含まれ、@Config("key1")と@Config("key2")は別の修飾子として扱われるので注意。
@Qualifier @Retention(RUNTIME) @Target({FIELD, PARAMETER, METHOD, TYPE}) public @interface Config { @Nonbinding String value() default ""; }
@ConfigPathを作る
こちらは非常にシンプルです。
@Qualifier @Retention(RUNTIME) @Target({METHOD, FIELD, PARAMETER, TYPE}) public @interface ConfigPath {}
コンフィグの読み込みをProducerメソッドで作る
javax.ejb.Singletonを使って実装してみました。java.inject.Singletonでは@Startupが使えないので、アプリケーションのデプロイ時にコンフィグがロードできず、初回アクセス時までロード処理ができません。
ファイルのパスは前述したように、CDIのProducerの仕組みを使って注入しています。また、コンフィグの処理自体はCommons Configurationを使いました。
@Singleton @Startup public class ConfigProducer { @Inject @ConfigPath private String filepath; private Configuration configration; @PostConstruct public void initialize() { try { configration = new XMLConfiguration(file path); } catch (ConfigurationException e) { throw new InjectConfigrationException(e); } } @Produces @Config public String getString(InjectionPoint ip) { Config configAnnotation = ip.getAnnotated().getAnnotation(Config.class); String configKey = configAnnotation.value(); if ("".equals(configKey)) { throw new IllegalArgumentException( "クラス " + ip.getMember().getDeclaringClass().getName() + " の" + "フィールド " + ip.getMember().getName() + " にコンフィグキー名が未設定です。" + "@Config(key)の形式でインジェクションするプロパティのキーを設定してください。"); } String val = configration.getString(configKey); if (val == null) { throw new IllegalArgumentException( "クラス " + ip.getMember().getDeclaringClass().getName() + " の" + "フィールド " + ip.getMember().getName() + " において" + "@Configに指定されたキー " + configKey + " が ファイル" + filepath + " に存在しません。"); } return val; } }
最後に
今回の設定ファイルの位置をCDIの@Producesを使って注入したように、何らかの汎用的な設定をJavaコードで書いてCDIで注入するやり方はもっと色々なことができそうです。Producerメソッドの仕組みを使うと、GuiceのAbstructModuleのように、Java EE 環境でも設定をJavaで書くことができます。XML地獄が良いか、アノテーションだらけになるか、Javaコードで設定も書くか、悩ましいところです。
参考にした記事
Commons Configrationについては以下の記事を参考にしました。
ありがとうございます。
http://d.hatena.ne.jp/daisuke-m/searchdiary?word=%2A%5Bcommons%5D