前回は、DaggerのDI機能を紹介しました。
PPAPで学ぶDaggerによるDI - きしだのはてな
DaggerはDIコンテナとしては知られていますが、非同期処理フレームワークとして使えることはあまり知られていないと思います。機能はあったのに、ドキュメントがなかったし、ドキュメントも非同期処理に使えることがわかりにくいし。
https://google.github.io/dagger/producers.html
サーバーサイドではSpringFrameworkやCDIなどのDIコンテナがすでに使われているのと、DaggerのDIでは機能不足であるため、Daggerが候補になることはあまりありません。
でも、非同期処理フレームワークとしてであれば、SpringFrameworkやCDIを使っている状況でも有用です。
ExecutorModuleの定義
非同期処理を行うために、スレッド管理のためのExecutorを定義するモジュールが必要になります。ここには@Productionが必要になります。
@Module public class ExecutorModule { static ExecutorService service; @Provides @Production static Executor executor() { return service == null ? service = Executors.newCachedThreadPool() : service; } }
呼び出しグラフの定義
呼び出しの依存関係をグラフとして定義します。DIの依存関係を、そのまま呼び出しの依存関係にする感じですが、アノテーションは次のように別のものを使います。
DI | Async |
---|---|
@Module | @ProducerModule |
@Component | @ProductionComponent |
@Provides | @Produces |
@ProductionComponentのmodulesに、Executor管理のモジュールを追加しておく必要があります。ListenableFutureを返す処理であっても、利用側はそのままオブジェクトを受け取れるのがいいところ。
@ProducerModule public class AsyncPiko { @ProductionComponent(modules = {AsyncPiko.class, ExecutorModule.class}) interface Taro { ListenableFuture<ApplePen> applePen(); } @Produces public ListenableFuture<Pen> iHaveAPen() { return task("pen", Pen::new, 100); } @Produces public ListenableFuture<Apple> iHaveAnApple() { return task("apple", Apple::new, 200); } @Produces public ListenableFuture<ApplePen> applePen(Pen pen, Apple apple) { return task("applepen", () -> new ApplePen(apple, pen), 100); } private static <T> ListenableFuture<T> task(String name, Supplier<T> supplier, long millis) { ListenableFutureTask<T> task = ListenableFutureTask.create(() -> { System.out.println("start " + name); try { Thread.sleep(millis); } catch (InterruptedException ignored) { } T result = supplier.get(); System.out.println("finish " + name); return result; }); task.run(); return task; } }
並列処理感を出したりListenableFutureの処理をするために、ウェイトを入れてログを出すtaskメソッドを定義しています。
実行
実行はDIのときと同じ感じになります。ExecutorModuleにはstaticメソッドの@Providesしかないため、Daggerのbuilderにインスタンスを渡す必要はありません。
public class AsyncPPAP { public static void main(String[] args) throws InterruptedException, ExecutionException { AsyncPiko.Taro pikotaro = DaggerAsyncPiko_Taro.builder() .asyncPiko(new AsyncPiko()) .build(); ListenableFuture<ApplePen> applePen = pikotaro.applePen(); System.out.println("start ppap"); System.out.println(applePen.get()); System.out.println("finish ppap"); ExecutorModule.service.shutdown(); } }
実行するとこんな感じになります。
penとappleが並列に処理されて、両方が終わったときにapplePenの処理が始まっていることがわかります。
このような並列的な依存関係の処理を、Daggerが管理してくれます。
同じ型のオブジェクトがかぶった場合
非同期処理のためにDaggerを使うと、同じ型の値を扱うことが多くなるので、型による依存性解決だけでは足りなくなります。
次のように、penPineappleやpenPineappleApplePenは文字列を返す非同期処理であることにしてみます。
@Produces public ListenableFuture<String> penPineapple(Pen pen) { return task("penPineapple", () -> pen + "パイナッポー", 200); } @Produces public ListenableFuture<String> penPineappleApplePen(ApplePen applePen, String penPineapple) { return task("penPineappleApplePen", () -> penPineapple + applePen, 50); }
そして、@ProductionComponentにStringを返す処理を追加します。
@ProductionComponent(modules = {AsyncPiko.class, ExecutorModule.class}) interface Taro { ListenableFuture<ApplePen> applePen(); ListenableFuture<String> ppap(); }
そうすると、なんかエラーが出ます。
このように、依存情報の不備をコンパイル時に見つけてくれるのが、Daggerのいいところですね。
そういう場合には、@Qualifierなアノテーションを定義します。
@Qualifier @Retention(RetentionPolicy.RUNTIME) @interface PenPineappleApplePen{}
このアノテーションを、利用側と提供側につけます。
@Produces @PenPineappleApplePen public ListenableFuture<String> penPineappleApplePen(ApplePen applePen, String penPineapple) { return task("penPineappleApplePen", () -> penPineapple + applePen, 50); }
@ProductionComponent(modules = {AsyncPiko.class, ExecutorModule.class}) interface Taro { @PenPineappleApplePen ListenableFuture<String> ppap(); }
そうすると、無事コンパイルが通って実行することができます。
ソースはgithubで。
https://github.com/kishida/dagger_sample/tree/async