At the previous article, I introduce the DI function in the Dagger.
PPAPで学ぶDaggerによるDI - きしだのはてな(Japanese)
Dagger is known as a DI container, but it is not known that can be used as an asynchronous framework.
There had been the function, but there was not a document about it. nowadays there is a document but we can not notice it can be used for an asynchronous process.
https://google.github.io/dagger/producers.html
At a server side, already they may have used DI containers such as Springframework or CDI.
Moreover the DI on Dagger is not enough for a server side, it can not be a candidate often.
But in the case of using as asynchronous processing framework, it is useful even with SpringFramework or CDI.
Define ExecutorModule
For an asynchronous processing, the module is needed to define an Executor to manage threads.
And @Production annotation is needed here.
@Module public class ExecutorModule { static ExecutorService service; @Provides @Production static Executor executor() { return service == null ? service = Executors.newCachedThreadPool() : service; } }
Defina a call graph
Define the call dependencies as a graph.
It feels like to make DI dependencies to call dependencies as is.
But the defferent annotations are used as bellow.
DI | Async |
---|---|
@Module | @ProducerModule |
@Component | @ProductionComponent |
@Provides | @Produces |
It is needed to add the Executor management module into 'modules' at '@ProductionComponent'.
It is good that users can receive the object directly even if produce method returns '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; } }
There is a method definition named 'task' to process 'ListenableFuture' and brings parallel feeling, addition inserting a wait and logging.
Execution
Execution likes the case of a DI. Since there is solely static method '@Provides', it is not needed to pass any instance to the 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(); } }
Result will be like this.
We can notice that 'pen' and 'apple' is processed in parallel, after finishing both, the 'applePen' is started.
Dagger manages such parallel dependencies.
Handle a collision of same type objects
Using Dagger for asynchronous processing, there are many situations to handle same type objects, as a result, it is not enough to resolve dependencies by type.
For example, make it as an asynchronous process that 'penPineapple' and 'penPineappleApplePen' return String.
@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); }
Then add a process to return String into @ProductionComponent.
@ProductionComponent(modules = {AsyncPiko.class, ExecutorModule.class}) interface Taro { ListenableFuture<ApplePen> applePen(); ListenableFuture<String> ppap(); }
When do so, some error will occur.
It is good point of the Dagger to point out an insufficiency of a dependency at a compile time.
At this case, to define an annotation with '@Qualifier'.
@Qualifier @Retention(RetentionPolicy.RUNTIME) @interface PenPineappleApplePen{}
Append this annotation at both user side and provider side.
@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(); }
Finally, now uneventfully it can be compiled successfully and execute.
The full sources are on Github.
https://github.com/kishida/dagger_sample/tree/async