java.util.ServiceLoaderを使う。
DIコンテナやらOSGiやら使う程でもないけど、最低限の拡張性はとりあえず担保しておきたいので使う感じなのだけど、コアAPIに含まれているjava.util.ServiceLoaderは本当に使い辛い。
というか、そのままでは使えないのでユーティリティ的でいつも似てるけどちょっと違うコードをそこら中に書き散らす訳で。
まずは、javadocをちゃんと読んでクダサシ。
- 実装を切り替える為のインターフェース
package conf; /** * @author taichi */ public interface Service { String getName(); void execute(String parameter); }
- 定義したインターフェースのデフォルト実装
package conf; /** * @author taichi */ public class DefaultService implements Service { @Override public String getName() { return ServiceProvider.DEFAULT_NAME; } @Override public void execute(String parameter) { System.out.println(parameter); } }
- プロバイダ構成ファイル
conf.DefaultService
- 定義したインターフェースの実装クラスを生成するローダ
package conf; import java.util.HashMap; import java.util.Map; import java.util.ServiceLoader; /** * @author taichi */ public class ServiceProvider { static final String SYSTEM_PROPERTY_NAME = ServiceProvider.class.getName(); static final String DEFAULT_NAME = "default"; protected Map<String, Service> services = new HashMap<String, Service>(); public void load() { this.load(Thread.currentThread().getContextClassLoader()); } public void load(ClassLoader loader) { ServiceLoader<Service> sl = ServiceLoader.load(Service.class, loader); for (Service s : sl) { this.services.put(s.getName(), s); } } public void dispose() { this.services.clear(); } public Service getService() { String name = System.getProperty(SYSTEM_PROPERTY_NAME); return getService(name); } public Service getService(String name) { Service s = this.services.get(name); if (s == null) { s = this.services.get(DEFAULT_NAME); } return s; } }
幾つかポイントがあるます。
- 同期処理を一切していない事。必要なら呼び出し元が行う。
- 拡張用インターフェースに定義したgetNameメソッド。
- オーバーロードされているloadメソッド。クラスローダでハマるのは面倒じゃんよ。
デフォルトの処理はあるにせよ、好きなクラスローダを引数として渡せるのは大事。
コンテキストクラスローダーを過信してはいけませぬ。 - オーバーロードされているgetServiceメソッド。
何気なく使うと、システムプロパティを参照する様になっているますので、後から好きな実装が動かせるます。
ライブラリとして既に持っている実装をデフォルト実装として取り出す様になっているので取り敢えず何かが動きます。
こういうのを見ると、Javaって面倒な言語だなヲイとか思うんだけど、JDTがマジ最強なのでそう簡単には離れられなくて、致し方ないので使いますわな。