Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Comparing DI frameworks

Comparing DI frameworks

Talk at Mobius 2017, Saint Petersburg with Danny Preussler.
Dependency-injection on Android. Comparison of frameworks, history & internals.

stephanenicolas

April 18, 2017
Tweet

More Decks by stephanenicolas

Other Decks in Programming

Transcript

  1. +stephane nicolas @PreusslerBerlin Senior Android Dev @ Groupon OSS: Dart,

    TP, BoundBox, … Lead Android Dev @ Viacom Google Developer Expert Combined ~40 years of Java Coding
  2. Inversion of Control Common implementations: • Factory • Service Locator

    • Dependency Injection https://martinfowler.com/articles/dipInTheWild.html#YouMeanDependencyInversionRight
  3. Inversion of Control Common implementations: • Factory tracker = Factory.createTracker()

    • Service Locator • Dependency Injection https://martinfowler.com/articles/dipInTheWild.html#YouMeanDependencyInversionRight
  4. Inversion of Control Common implementations: • Factory • Service Locator

    tracker = Locator.get(Tracker.class) • Dependency Injection https://martinfowler.com/articles/dipInTheWild.html#YouMeanDependencyInversionRight
  5. Inversion of Control Common implementations: • Factory • Service Locator

    • Dependency Injection @Inject Tracker tracker; https://martinfowler.com/articles/dipInTheWild.html#YouMeanDependencyInversionRight
  6. Dependency Injection • Field injection @Inject Tracker tracker; • Constructor

    injection • Setter injection • Method injection
  7. A brief history of DI libs for Java / Android

    Revolutions are the locomotives of history. Karl Marx
  8. A brief history of DI libs for Java / Android

    March 8, 2007: Guice 1.0 is released. Guice is annotation based to perform DI which is a huge improvement over former frameworks. It uses reflection to access annotations, create instances and inject stuff.
  9. A brief history of DI libs for Java / Android

    October 2009: JSR 330 final draft released. • Guice is de facto the first implementation of the JSR 330
  10. A brief history of DI libs for Java / Android

    May 2010: RoboGuice was launched ! • First DI lib on Android. • Based on Guice (reflection). • Supports view bindings, extras, events, etc..
  11. A brief history of DI libs for Java / Android

    June 2012: Dagger is started ! • The goal is to create a compile time implementation of JSR 330.
  12. A brief history of DI libs for Java / Android

    May 2013: Dagger 1.0.0 is launched ! • Compile time implementation of JSR 330. No more reflection or very very limited. • Annotation processing at compile time. • Generated code is used to assign members & create instances.
  13. A brief history of DI libs for Java / Android

    April 2015: Dagger 2.0.0 is launched ! • Faster than Dagger 1 • Easier error messages
  14. A brief history of DI libs for Java / Android

    October 2016: Toothpick 1.0.0 ! • As fast as the daggers. • Hybrid compile time and runtime. • More flexible, simpler, amazing test support.
  15. A brief history of DI libs for Java / Android

    Many libs now : • Light saber (kotlin) • Proton • Feather • Tiger (Dagger 2 improvements)
  16. What is reflection ? • uses OOO concepts to represent

    objects, classes, methods, constructors, fields, annotations, etc. • is an API to get a view of runtime java objects. • is standard java. • is relatively easy to use.
  17. What is reflection ? MyClass object = Myclass.class .getConstructors()[0].newInstance(); Method

    setter = Myclass.class .getDeclaredMethod(“setFoo”, {String.class}); setter.setAccessible(true); setter.invoke(object, “set via reflection”); Field foo = MyClass.class.getDeclaredField(“foo”); foo.setAccessible(true); String value = foo.get(object);
  18. Why is reflection slow (on Android) ? On a PC

    JVM ◦ Reflection calls are cached after 15 calls ◦ They are then transformed into normal code (JIT) ◦ 15 is parametrized by sun.reflect.inflation system property
  19. Why is reflection slow (on Android) ? • On Android

    Dalvik ◦ Reflection calls are not cached, no JIT ◦ The dex format is not efficient for reflection ◦ There was a bug that slowed down access to annotations by reflection (before GingerBread) • On Android Art ◦ In Nougat, reflection calls are now cached using JIT ◦ But data structure of odex is still slow ◦ Bug is solved
  20. Dagger vs Toothpick: Round 1: Usage Scope scope = openScope("APPLICATION");

    scope.installModules(new BaseModule(context)); scope.inject(this); Toothpick
  21. Dagger vs Toothpick: Round 2: Setup @Module class BaseModule {

    BaseModule(Application context){} … @Provides public Tracker provideTracker() { return new GoogleTracker(); } } D agger
  22. Dagger vs Toothpick: Round 2: Setup @Component(modules = {BaseModule.class}) interface

    AppComponent { void inject(MyActivity activity); } D agger
  23. Dagger vs Toothpick: Round 2: Setup class BaseModule extends Module

    { public BaseModule(Application context){ bind(Tracker.class) .to(GoogleTracker.class); Toothpick
  24. Dagger vs Toothpick: Round 3: Scopes @ActivityScope @Subcomponent(modules = {ScopeModule.class})

    interface ScopeComponent { void inject(ScopeActivity activity); } D agger
  25. Dagger vs Toothpick: Round 3: Scopes @Module static class ScopeModule

    { ... @Provides @ActivityScope public Activity provideActivity() { return activity; } } D agger
  26. Dagger vs Toothpick: Round 3: Scopes @Component(modules = {BaseModule.class}) interface

    AppComponent { void inject(LonelyActivity activity); ScopeComponent plus(ScopeModule module); } D agger
  27. Dagger vs Toothpick: Round 3: Scopes @Override public void onCreate()

    { super.onCreate(); DaggerDependencies_AppComponent.builder() .baseModule(new BaseModule(this)).build() .plus(new ScopeModule(this)) .inject(this) D agger
  28. Dagger vs Toothpick: Round 3: Scopes Scope scope = openScope("APPLICATION",

    "MY_ACTIVITY") scope.installModules(new ScopeModule(this))); Toothpick
  29. Dagger vs Toothpick: Round 3: Scopes Scope scope = openScope("APPLICATION",

    "MY_ACTIVITY") scope.installModules(new ScopeModule(this))); .inject(this) Toothpick
  30. Dagger vs Toothpick: Round 4: Tests @Mock Tracker tracker; class

    TestModule extends Dependencies.BaseModule { @Provides public Tracker provideTracker() { return tracker; } } } D agger
  31. Dagger vs Toothpick: Round 4: Tests @Mock Tracker tracker; @Rule

    public ToothPickRule toothPickRule = new ToothPickRule( this, "APPLICATION_SCOPE"); Toothpick
  32. Dagger vs Toothpick: Round 4: Tests @Mock Tracker tracker; @Mock

    Navigator navigator; @Mock Logger logger; class TestModule extends Dependencies.BaseModule { @Provides public Tracker provideTracker() { return tracker; } @Provides public Navigator provideNavigator() { return tracker; } @Provides public Logger provideLogger() { return logger; } } D agger
  33. Dagger vs Toothpick: Round 4: Tests @Mock Tracker tracker; @Mock

    Navigator navigator; @Mock Logger logger; @Rule public ToothPickRule toothPickRule = new ToothPickRule( this, "APPLICATION_SCOPE"); Toothpick
  34. Dagger vs Toothpick: Round 5: Performance Costs of creating a

    Component/Scope • Dagger 1: 20 ms • Dagger 2: 22 ms • Toothpick: 1 ms
  35. Dagger vs Toothpick: Round 5: Performance Costs of usage with

    1000 injections: • Dagger 1: 33 ms • Dagger 2: 31 ms • Toothpick: 35 ms
  36. Dagger vs Toothpick: Round 5: Performance Costs of usage with

    6400 injections: • Dagger 1: 45 ms • Dagger 2: 42 ms • Toothpick: 66 ms
  37. What is annotation processing ? Annotation processing: • is an

    API to get a view of java classes before they are compiled. • is standard java. • uses different concepts to represent classes (mirrors & TypeElements), methods & constructors (ExecutableElements), constructors, fields (Elements), annotations, etc. • is not easy to use, not easy to debug, not easy to memorize and learn. • annotation processors can generate code and/or resources.
  38. What is annotation processing ? TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    Set<Modifier> modifiers = executableElement.getModifiers(); if (modifiers.contains(PRIVATE)) { ... }
  39. Which code is generated ? @Module class SlidesModule { @Provides

    DisplayOut displayOut(Resolution resolution){ return new UcsbDisplayOut(resolution); } } D agger
  40. Which code is generated ? @Generated public final class SlidesModule_DisplayOutFactory

    implements Factory<DisplayOut> { private final SlidesModule module; private final Provider<Resolution> resolutionProvider; public static SlidesModule_DisplayOutFactory create( SlidesModule module, Provider<Resolution> resolutionProvider) {..} @Override public DisplayOut get() { return module.displayOut(resolutionProvider.get()}; } } D agger
  41. Which code is generated ? Toothpick class UcsbDisplayOut { @Inject

    UcsbDisplayOut(Resolution resolution) { …. } }
  42. Which code is generated ? Toothpick public final class UcsbDisplayOut$$Factory

    implements Factory<UcsbDisplayOut> { @Override public UcsbDisplayOut createInstance(Scope scope) { Resolution resolution = scope.getInstance(Resolution.class); return new UcsbDisplayOut(resolution); } }
  43. Which code is generated ? Basically, both libs generate: •

    Factories to create instances • MemberInjectors to assign members Moreover Dagger generates code for: • Modules, Components And Tootpick can also generate code for: • Registries
  44. Which code is generated ? Dagger: • generates a static

    graph, • generated code only calls generated code • very efficient • but all wiring is static • hard to modify for testing D agger
  45. Which code is generated ? Toothpick: • generates a dynamic

    graph • generated code calls runtime code to get the bindings • a bit less efficient • but more flexible • easier to change for testing. Toothpick
  46. Conclusion: • Dagger provides compile-time scope verification • Dagger might

    be a little more efficient • Toothpick avoids boilerplate code • Toothpick is easier for testing • Toothpick scopes are more clear Dagger vs Toothpick: Overall