Share
Embed
Dagger Reflect - The circle from runtime to compile time and back again
Back in the day Java dependency injection frameworks were purely runtime based like Spring and Guava. Dagger 1 introduced a compile time component as well as compile time safety and Dagger 2 took the concept even further. Now we're going back to runtime with Dagger Reflect in an effort to improve local developer build speeds. This talk will cover the reasons for building dagger reflect, how it works under the hood, how to use it in your project as well as how it is developed.
Presented at Droidcon SF Nov 58, 2019
Nelson Osacky
November 25, 2019
Transcript
200 slides
-
Me • Android for 9 years • Chocolate and Running
• Previously at Square in SF on Square Register and Build Tools • Currently Working at SoundCloud in Berlin • Maintainer of Fladle - Gradle Plugin for Firebase Test Lab https://github.com/runningcode/fladle • Gradle Doctor - https://github.com/runningcode/gradle- doctor -
public class Store {a private Item item; public Store()a{ item
= new ItemImpl1(); }a }b -
public class Store {a private Item item; public Store( )a{
itema=bnew ItemImpl1(); }a }b -
public class Store {a private Item item; public Store( Itembitem
)a{ this.item = item; }a }b -
Spring <bean id="item1" class="com.soundcloud.ItemImpl1" /> <bean id="store" class="com.soundcloud.Store"> <constructor-arg type="ItemImpl1"
index="0" name="item" ref="item1" /> </bean> -
Guice public class Store {c private Item item; public Store(Item
item) {d this.item = item; }a }b -
Guice public class Store {c private Item item; @Inject public
Store(Item item) {d this.item = item; }a }b -
Dagger public class Store {c private Item item; @Inject public
Store(Item item) {d this.item = item; }a }b -
Dagger @Module public class PlayerModule { @Provides public PlayerProvider providePlayerProvider(AppFeatures
appFeatures) { return new PlayerProviderImpl(appfeatures); } } -
Super rough comparison (not to scale) 0 25 50 75
100 Guice Dagger 1 Dagger 2 Compile Time Runtime -
@Component public interface ApplicationComponent {b @Component.Factory interface Builder {b} }a
DaggerApplicationComponent .factory() .create(this) -
@Component public interface ApplicationComponent {b void inject(MyApplication application); @Component.Factory interface
Builder {b} }a DaggerApplicationComponent .factory() .create(this) -
@Component public interface ApplicationComponent {b void inject(MyApplication application); @Component.Factory interface
Builder {b} }a DaggerApplicationComponent .factory() .create(this) .inject(this) -
class MyApplication : Application(), HasAndroidInjector { override fun onCreate() {
super.onCreate() DaggerApplicationComponent .factory() .create(this) .inject(this) } } -
public final class DaggerApplicationComponent { private void initialize(final ApplicationModule applicationModuleParam,
final FacebookModule facebookModuleParam, final ApiModule apiModuleParam, final AppFeaturesModule appFeaturesModuleParam, final. BaseAnalyticsModule baseAnalyticsModuleParam, final AdAnalyticsModule adAnalyticsModuleParam, final AppBoyModule appBoyModuleParam, final Application applicationParam) { this.userTopTracksFragmentSubcomponentFactoryProvider = new Provider<UserTopTracksModule_BindUserTopTracksFragment.UserTopTracksFragmentSubcomponent.Factory>() { @Override public UserTopTracksModule_BindUserTopTracksFragment.UserTopTracksFragmentSubcomponent.Factory get( ) { return new UserTopTracksFragmentSubcomponentFactory();} }; this.soundRecorderServiceSubcomponentFactoryProvider = new Provider<CreatorsModule_ProvidesSoundRecorder.SoundRecorderServiceSubcomponent.Factory>() { @Override public CreatorsModule_ProvidesSoundRecorder.SoundRecorderServiceSubcomponent.Factory get() { return new SoundRecorderServiceSubcomponentFactory();} }; this.recordFragmentSubcomponentFactoryProvider = new Provider<CreatorsModule_ProvidesRecordFragment.RecordFragmentSubcomponent.Factory>() { @Override public CreatorsModule_ProvidesRecordFragment.RecordFragmentSubcomponent.Factory get() { return new RecordFragmentSubcomponentFactory();} }; this.recordAppWidgetProviderSubcomponentFactoryProvider = new Provider<CreatorsModule_ProvidesRecordAppWidgetProvider.RecordAppWidgetProviderSubcomponent.Factory>() { @Override public CreatorsModule_ProvidesRecordAppWidgetProvider.RecordAppWidgetProviderSubcomponent.Factory get( ) { return new RecordAppWidgetProviderSubcomponentFactory();} }; this.homescreenWidgetBroadcastReceiverSubcomponentFactoryProvider = new Provider<HomescreenWidgetModule_HomescreenWidgetBroadcastReceiver.HomescreenWidgetBroadcastReceiverSubcomponent.Factory>() { @Override public HomescreenWidgetModule_HomescreenWidgetBroadcastReceiver.HomescreenWidgetBroadcastReceiverSubcomponent.Factory get( ) { return new HomescreenWidgetBroadcastReceiverSubcomponentFactory();} }; this.mainActivitySubcomponentFactoryProvider = new Provider<ActivityBuilder_BindMainActivity.MainActivitySubcomponent.Factory>() { @Override public ActivityBuilder_BindMainActivity.MainActivitySubcomponent.Factory get() { return new MainActivitySubcomponentFactory();} }; this.launcherActivitySubcomponentFactoryProvider = new Provider<ActivityBuilder_BindLauncherActivity.LauncherActivitySubcomponent.Factory>() { @Override public ActivityBuilder_BindLauncherActivity.LauncherActivitySubcomponent.Factory get() { return new LauncherActivitySubcomponentFactory();} }; this.resolveActivitySubcomponentFactoryProvider = new Provider<ActivityBuilder_BindResolveActivity.ResolveActivitySubcomponent.Factory>() { @Override public ActivityBuilder_BindResolveActivity.ResolveActivitySubcomponent.Factory get() { return new ResolveActivitySubcomponentFactory();} }; this.changeStorageLocationActivitySubcomponentFactoryProvider = new Provider<ActivityBuilder_BindChangeLocationStorageActivity.ChangeStorageLocationActivitySubcomponent.Factory>() { @Override public ActivityBuilder_BindChangeLocationStorageActivity.ChangeStorageLocationActivitySubcomponent.Factory get( ) { return new ChangeStorageLocationActivitySubcomponentFactory();} }; this.fullScreenVideoActivitySubcomponentFactoryProvider = new Provider<ActivityBuilder_BindFullScreenVideoActivity.FullScreenVideoActivitySubcomponent.Factory>() { @Override public ActivityBuilder_BindFullScreenVideoActivity.FullScreenVideoActivitySubcomponent.Factory get( ) { return new FullScreenVideoActivitySubcomponentFactory();} }; this.goOnboardingActivitySubcomponentFactoryProvider = new Provider<ActivityBuilder_BindGoOnboardingActivity.GoOnboardingActivitySubcomponent.Factory>() { @Override public ActivityBuilder_BindGoOnboardingActivity.GoOnboardingActivitySubcomponent.Factory get( ) { -
public final class DaggerApplicationComponent {a public static ApplicationComponent.Builder factory() {b
return DaggerReflect.factory(ApplicationComponent.Builder.class); }c }d -
public final class DaggerApplicationComponent {a public static ApplicationComponent.Builder factory() {b
return DaggerReflect.factory(ApplicationComponent.Builder.class); }c }d Generated by Dagger Reflect -
public final class DaggerReflect {a public static <F> F factory(Class<F>
factoryClass) {b }c }d -
public final class DaggerReflect {a public static <F> F factory(Class<F>
factoryClass) {b }c }d In Dagger Reflect Library -
public final class DaggerReflect {a public static <F> F factory(Class<F>
factoryClass) {b return ComponentFactoryInvocationHandler.forComponentFactory(factoryClass); }c }d In Dagger Reflect Library -
public interface InvocationHandler { public Object invoke(Object proxy, Method method,
Object[] args) } java.lang.Reflect -
static <F> F forComponentFactory(Class<F> factoryClass) {a requireAnnotation(factoryClass, Component.Factory.class); Class<?> componentClass
= requireEnclosingClass(factoryClass); return newProxy( factoryClass, new ComponentFactoryInvocationHandler( componentClass, () -> ComponentScopeBuilder.buildComponent(componentClass))); }b In Dagger Reflect Library -
static <F> F forComponentFactory(Class<F> factoryClass) {a requireAnnotation(factoryClass, Component.Factory.class); Class<?> componentClass
= requireEnclosingClass(factoryClass); return newProxy( factoryClass, new ComponentFactoryInvocationHandler( componentClass, () -> ComponentScopeBuilder.buildComponent(componentClass))); }b In Dagger Reflect Library -
static <F> F forComponentFactory(Class<F> factoryClass) {a requireAnnotation(factoryClass, Component.Factory.class); Class<?> componentClass
= requireEnclosingClass(factoryClass); return newProxy( factoryClass, new ComponentFactoryInvocationHandler( componentClass, () -> ComponentScopeBuilder.buildComponent(componentClass))); }b In Dagger Reflect Library -
static <F> F forComponentFactory(Class<F> factoryClass) {a requireAnnotation(factoryClass, Component.Factory.class); Class<?> componentClass
= requireEnclosingClass(factoryClass); return newProxy( factoryClass, new ComponentFactoryInvocationHandler( componentClass, () -> ComponentScopeBuilder.buildComponent(componentClass))); }b In Dagger Reflect Library -
static <F> F forComponentFactory(Class<F> factoryClass) {a requireAnnotation(factoryClass, Component.Factory.class); Class<?> componentClass
= requireEnclosingClass(factoryClass); return newProxy( factoryClass, new ComponentFactoryInvocationHandler( componentClass, () -> ComponentScopeBuilder.buildComponent(componentClass))); }b In Dagger Reflect Library -
static <F> F forComponentFactory(Class<F> factoryClass) {a requireAnnotation(factoryClass, Component.Factory.class); Class<?> componentClass
= requireEnclosingClass(factoryClass); return newProxy( factoryClass, new ComponentFactoryInvocationHandler( componentClass, () -> ComponentScopeBuilder.buildComponent(componentClass))); }b In Dagger Reflect Library -
static <F> F forComponentFactory(Class<F> factoryClass) {a requireAnnotation(factoryClass, Component.Factory.class); Class<?> componentClass
= requireEnclosingClass(factoryClass); return newProxy( factoryClass, new ComponentFactoryInvocationHandler( componentClass, () -> ComponentScopeBuilder.buildComponent(componentClass))); }b In Dagger Reflect Library -
static <F> F forComponentFactory(Class<F> factoryClass) {a requireAnnotation(factoryClass, Component.Factory.class); Class<?> componentClass
= requireEnclosingClass(factoryClass); return newProxy( factoryClass, new ComponentFactoryInvocationHandler( componentClass, () -> ComponentScopeBuilder.buildComponent(componentClass))); }b In Dagger Reflect Library -
static <F> F forComponentFactory(Class<F> factoryClass) {a requireAnnotation(factoryClass, Component.Factory.class); Class<?> componentClass
= requireEnclosingClass(factoryClass); return newProxy( factoryClass, new ComponentFactoryInvocationHandler( componentClass, () -> ComponentScopeBuilder.buildComponent(componentClass))); }b In Dagger Reflect Library -
static ComponentScopeBuilder buildComponent(Class<?> componentClass) {a Component component = requireAnnotation(componentClass, Component.class);
Set<Annotation> scopeAnnotation = findScopes(componentClass.getAnnotations()); return create(component.modules(), component.dependencies(), scopeAnnotation, null); }b In Dagger Reflect Library -
static ComponentScopeBuilder buildComponent(Class<?> componentClass) {a Component component = requireAnnotation(componentClass, Component.class);
Set<Annotation> scopeAnnotation = findScopes(componentClass.getAnnotations()); return create(component.modules(), component.dependencies(), scopeAnnotation, null); }b In Dagger Reflect Library -
static ComponentScopeBuilder buildComponent(Class<?> componentClass) {a Component component = requireAnnotation(componentClass, Component.class);
Set<Annotation> scopeAnnotation = findScopes(componentClass.getAnnotations()); return create(component.modules(), component.dependencies(), scopeAnnotation, null); }b In Dagger Reflect Library -
static ComponentScopeBuilder buildComponent(Class<?> componentClass) {a Component component = requireAnnotation(componentClass, Component.class);
Set<Annotation> scopeAnnotation = findScopes(componentClass.getAnnotations()); return create(component.modules(), component.dependencies(), scopeAnnotation, null); }b In Dagger Reflect Library -
reflect-compiler Generates the glue to link your code to the
reflection based dagger implementation -
@Test public void componentProvider() {a ComponentProvider component = backend.create(ComponentProvider.class); assertThat(component.string()).isEqualTo("foo");
}b @Component(modules = ComponentProvider.Module1.class) interface ComponentProvider { String string(); @Module abstract class Module1 { @Provides static String string() { return "foo"; } } } -
@RunWith(Parameterized.class) public final class IntegrationTest { @Parameters(name = "{0}") public
static Object[] parameters() { return Backend.values(); } @Parameter public Backend backend = backend.create(ComponentProvider.class); } -
enum Backend {a abstract <C> C create(Class<C> componentClass); REFLECT {b
@Overridea <F>aFacreate(Class<F>acomponentClass) { returnaDaggerReflect.create(componentClass); }c },d CODEGEN }e -
enum Backend {a abstract <C> C create(Class<C> componentClass); REFLECT {b
@Overridea <F>aFacreate(Class<F>acomponentClass) { returnaDaggerReflect.create(componentClass); }c },d CODEGEN { @Override <F> F create(Class<F> componentClass) { return DaggerCodegen.create(componentClass); } }; }e -
@RunWith(Parameterized.class) public final class IntegrationTest { @Parameters(name = "{0}") public
static Object[] parameters() { return Backend.values(); } @Parameter public Backend backend = backend.create(ComponentProvider.class); } -
private void ignoreReflectionBackend() { assumeTrue("Not yet implemented for reflection backend",
backend != Backend.REFLECT); } -
test.filter { // dagger-reflect does not produce the exact same
behavior as dagger-compiler for @Reusable. excludeTest 'dagger.functional.ReusableTest', null // TODO reflect bug! Need something like ByteBuddy for proxying classes at runtime. excludeTest 'dagger.functional.builder.BuilderBindsInstanceParameterTest', null // TODO reflect bug! Generics don't work well. excludeTest 'dagger.functional.GenericTest', 'complexGenerics' } -
class MyApplication : Application(), HasAndroidInjector { override fun onCreate() {
super.onCreate() DaggerApplicationComponent .factory() .create(this) .inject(this) } } -
implementation deps.dagger.dagger kapt deps.dagger.compiler private fun useDaggerReflect() : Boolean {
return properties["dagger.reflect"] == "true") } -
implementation deps.dagger.dagger if ( useDaggerReflect() ) { implementation deps.dagger.reflectRuntime kapt
deps.dagger.daggerReflectCompiler } else { kapt deps.dagger.compiler }q -
implementation deps.dagger.dagger if (useDaggerReflect()) { implementation deps.dagger.reflectRuntime kapt deps.dagger.daggerReflectCompiler }
else { kapt deps.dagger.compiler }q -
implementationadeps.dagger.dagger implementationbdeps.dagger.daggerAndroid if (useDaggerReflect()) {c implementation deps.dagger.reflectRuntime kapt deps.dagger.daggerReflectCompiler }celse
{a kapt deps.dagger.compiler kapt deps.dagger.daggerAndroidCompiler }b -
Swap Dagger for Dagger Reflect in every module and for
Dagger Android -
Swap Dagger for Dagger Reflect in every module and for
Dagger Android and for every new module -
implementationadeps.dagger.dagger implementationbdeps.dagger.daggerAndroid if (useDaggerReflect()) {c implementation deps.dagger.reflectRuntime kapt deps.dagger.daggerReflectCompiler }celse
{a kapt deps.dagger.compiler kapt deps.dagger.daggerAndroidCompiler }b -
implementationadeps.dagger.dagger implementationbdeps.dagger.daggerAndroid lintChecks deps.dagger.daggerReflectLint if (useDaggerReflect()) {c implementation deps.dagger.reflectRuntime kapt
deps.dagger.daggerReflectCompiler }celse {a kapt deps.dagger.compiler kapt deps.dagger.daggerAndroidCompiler }b -
Swap Dagger for Dagger Reflect in every module and for
Dagger Android and for every new module and don’t forget the lint check -
configurations.all config@ {a dependencies.all {b //bIf we depend on the
daggerbruntime, also add the daggerbreflect runtime. ifb(group == daggerGroupId && name == "dagger") {c dependencies {d add([email protected], "$com.jakewharton.dagger:dagger-reflect:${extension.daggerReflectVersion}") }e }f }g resolutionStrategy {h dependencySubstitution {o // Substitute dagger compiler for dagger reflect compiler. substitute( module("$com.google.dagger:dagger-compiler“) ).apply { with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) because("We want to build faster.") }p substitute(module("$com.google.dagger:dagger-android-processor")) .with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) }q }r }s -
configurations.all config@ {a dependencies.all {b //bIf we depend on the
daggerbruntime, also add the daggerbreflect runtime. ifb(group == daggerGroupId && name == "dagger") {c dependencies {d add([email protected], "$com.jakewharton.dagger:dagger-reflect:${extension.daggerReflectVersion}") }e }f }g }s -
configurations.all config@ {a dependencies.all {b //bIf we depend on the
daggerbruntime, also add the daggerbreflect runtime. ifb(group == daggerGroupId && name == "dagger") {c dependencies {d add([email protected], "$com.jakewharton.dagger:dagger-reflect:${extension.daggerReflectVersion}") }e }f }g }s -
configurations.all config@ {a dependencies.all {b //bIf we depend on the
daggerbruntime, also add the daggerbreflect runtime. ifb(group == daggerGroupId && name == "dagger") {c dependencies {d add([email protected], "$com.jakewharton.dagger:dagger-reflect:${extension.daggerReflectVersion}") }e }f }g resolutionStrategy {h dependencySubstitution {o // Substitute dagger compiler for dagger reflect compiler. substitute( module("$com.google.dagger:dagger-compiler") ).apply { with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) because("We want to build faster.") }p substitute(module("$com.google.dagger:dagger-android-processor")) .with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) }q }r }s -
configurations.all config@ {a resolutionStrategy {h dependencySubstitution {o // Substitute dagger
compiler for dagger reflect compiler. substitute( module("$com.google.dagger:dagger-compiler") ).apply { with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) because("We want to build faster.") }p }q }r }s -
configurations.all config@ {a resolutionStrategy {h dependencySubstitution {o // Substitute dagger
compiler for dagger reflect compiler. substitute( module("$com.google.dagger:dagger-compiler") ).apply { with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) because("We want to build faster.") }p substitute(module("$com.google.dagger:dagger-android-processor")) .with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) }q }r }s -
configurations.all config@ {a resolutionStrategy {h dependencySubstitution {o // Substitute dagger
compiler for dagger reflect compiler. substitute( module("$com.google.dagger:dagger-compiler") ).apply { with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) because("We want to build faster.") }p substitute(module("$com.google.dagger:dagger-android-processor")) .with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) }q }r }s -
configurations.all config@ {a dependencies.all {b //bIf we depend on the
daggerbruntime, also add the daggerbreflect runtime. ifb(group == daggerGroupId && name == "dagger") {c dependencies {d add([email protected], "$com.jakewharton.dagger:dagger-reflect:${extension.daggerReflectVersion}") }e }f }g resolutionStrategy {h dependencySubstitution {o // Substitute dagger compiler for dagger reflect compiler. substitute( module("$com.google.dagger:dagger-compiler") ).apply { with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) because("We want to build faster.") }p substitute(module("$com.google.dagger:dagger-android-processor")) .with(module("$com.jakewharton.dagger:dagger-reflect-compiler:${extension.daggerReflectVersion}")) }q }r }s -
• Slower startup time • Lazy reflection (doesn’t scan entire
app at startup) • Different behavior than production • Not all Dagger features are supported yet • Poor error messages -
• Match Dagger with Features • Improve Error Messages •
Improve Runtime Performance • Kotlin Dagger Reflect -
Relative build performance J genating J only K generating K
only K & J generating K & J Time https://eng.uber.com/measuring-kotlin-build-performance/ -
At SoundCloud • Long running 20% project • Been using
it since July • Using a fork of Dagger Reflect with our supported features • Devs can opt in using dagger.reflect=true -
58.89s Dagger 19% faster builds 47.55s Dagger Reflect Average Build
Time - Last 4 Weeks -
• Thanks to Riccardo for testing Delect and help with
open sourcing • Thanks to my team for testing out Dagger Reflect • Thanks to SoundCloud for letting me have the time to work on this • Thanks Jake Wharton for reviewing my code and building and releasing 0.1.0 -
More information • DIY: Build your own dependency injection library
- Pierre- Yves Ricau - https://academy.realm.io/posts/android- pierre-yves-ricau-build-own-dependency-injection/ • Dagger Reflect: https://github.com/jakewharton/dagger- reflect • Dagger Reflect Gradle Plugin: https://github.com/ soundcloud/delect • A brief history of dependency injection http://mvpjava.com/ brief-history-dependency-injection/