Dagger is a compile-time dependency injection framework for Java and Android that generates code to perform dependency injection. This comprehensive guide covers Dagger 2 usage, components, modules, scopes, and best practices.
Basic Dagger Setup
1. Dependencies Configuration
Maven:
<properties>
<dagger.version>2.48.1</dagger.version>
</properties>
<dependencies>
<dependency>
<groupId>com.google.dagger</groupId>
<artifactId>dagger</artifactId>
<version>${dagger.version}</version>
</dependency>
<dependency>
<groupId>com.google.dagger</groupId>
<artifactId>dagger-compiler</artifactId>
<version>${dagger.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
Gradle:
dependencies {
implementation 'com.google.dagger:dagger:2.48.1'
annotationProcessor 'com.google.dagger:dagger-compiler:2.48.1'
}
2. Basic Dependency Injection
Simple Classes:
// Domain model
public class User {
private final String name;
private final String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
// Getters
public String getName() { return name; }
public String getEmail() { return email; }
}
// Repository interface
public interface UserRepository {
User findById(Long id);
void save(User user);
}
// Repository implementation
public class UserRepositoryImpl implements UserRepository {
@Override
public User findById(Long id) {
// Implementation to fetch user from database
return new User("John Doe", "[email protected]");
}
@Override
public void save(User user) {
// Implementation to save user to database
System.out.println("Saving user: " + user.getName());
}
}
// Service class that depends on repository
public class UserService {
private final UserRepository userRepository;
@Inject
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUser(Long id) {
return userRepository.findById(id);
}
public void createUser(String name, String email) {
User user = new User(name, email);
userRepository.save(user);
}
}
Dagger Modules and Components
3. Basic Module and Component
AppModule.java:
import dagger.Module;
import dagger.Provides;
import javax.inject.Singleton;
@Module
public class AppModule {
@Provides
@Singleton
public UserRepository provideUserRepository() {
return new UserRepositoryImpl();
}
@Provides
@Singleton
public UserService provideUserService(UserRepository userRepository) {
return new UserService(userRepository);
}
}
AppComponent.java:
import dagger.Component;
import javax.inject.Singleton;
@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
UserService getUserService();
UserRepository getUserRepository();
// Factory method to create instances
static AppComponent create() {
return DaggerAppComponent.create();
}
}
4. Using Dagger in Application
MainApplication.java:
public class MainApplication {
private static AppComponent appComponent;
public static void main(String[] args) {
// Initialize Dagger component
appComponent = AppComponent.create();
// Use injected dependencies
UserService userService = appComponent.getUserService();
User user = userService.getUser(1L);
System.out.println("User: " + user.getName() + " - " + user.getEmail());
userService.createUser("Jane Smith", "[email protected]");
}
public static AppComponent getAppComponent() {
return appComponent;
}
}
Advanced Dagger Features
5. Multiple Modules and Component Dependencies
DatabaseModule.java:
@Module
public class DatabaseModule {
@Provides
@Singleton
public DataSource provideDataSource() {
// Configure and return DataSource
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUsername("user");
dataSource.setPassword("password");
return dataSource;
}
@Provides
@Singleton
public JdbcTemplate provideJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
NetworkModule.java:
@Module
public class NetworkModule {
private final String baseUrl;
public NetworkModule(String baseUrl) {
this.baseUrl = baseUrl;
}
@Provides
@Singleton
public OkHttpClient provideOkHttpClient() {
return new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
}
@Provides
@Singleton
public Retrofit provideRetrofit(OkHttpClient okHttpClient) {
return new Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
@Provides
@Singleton
public UserApi provideUserApi(Retrofit retrofit) {
return retrofit.create(UserApi.class);
}
}
Advanced AppComponent.java:
@Singleton
@Component(modules = {
AppModule.class,
DatabaseModule.class,
NetworkModule.class
})
public interface AppComponent {
UserService getUserService();
DataSource getDataSource();
UserApi getUserApi();
// Component builder for modules with constructor parameters
@Component.Builder
interface Builder {
Builder networkModule(NetworkModule networkModule);
Builder appModule(AppModule appModule);
Builder databaseModule(DatabaseModule databaseModule);
AppComponent build();
}
static AppComponent create() {
return DaggerAppComponent.builder()
.appModule(new AppModule())
.databaseModule(new DatabaseModule())
.networkModule(new NetworkModule("https://api.example.com/"))
.build();
}
}
6. @Binds and Abstract Modules
ServiceModule.java:
@Module
public abstract class ServiceModule {
@Binds
public abstract UserRepository bindUserRepository(UserRepositoryImpl impl);
@Binds
@Singleton
public abstract NotificationService bindNotificationService(
EmailNotificationService impl);
}
// Interface and implementation
public interface NotificationService {
void sendNotification(String message, String recipient);
}
public class EmailNotificationService implements NotificationService {
@Inject
public EmailNotificationService() {}
@Override
public void sendNotification(String message, String recipient) {
System.out.println("Sending email to " + recipient + ": " + message);
}
}
Scopes and Custom Scopes
7. Custom Scopes
Custom Scopes:
import javax.inject.Scope;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationScope {}
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestScope {}
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface SessionScope {}
Scoped Modules:
@Module
public class ScopedModule {
@Provides
@ApplicationScope
public ApplicationConfig provideApplicationConfig() {
return new ApplicationConfig();
}
@Provides
@RequestScope
public RequestContext provideRequestContext() {
return new RequestContext();
}
@Provides
@SessionScope
public UserSession provideUserSession() {
return new UserSession();
}
}
Constructor Injection vs Field Injection
8. Different Injection Methods
Constructor Injection (Recommended):
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
private final NotificationService notificationService;
@Inject
public OrderService(OrderRepository orderRepository,
PaymentService paymentService,
NotificationService notificationService) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
this.notificationService = notificationService;
}
}
Field Injection:
public class ShoppingCartController {
@Inject
private ShoppingCartService shoppingCartService;
@Inject
private ProductService productService;
@Inject
private UserService userService;
// Must be called after instance creation to inject fields
@Inject
public void setupDependencies() {
// Optional setup method called after field injection
}
}
Method Injection:
public class ReportGenerator {
private DataFormatter dataFormatter;
private TemplateEngine templateEngine;
@Inject
public void setDependencies(DataFormatter dataFormatter,
TemplateEngine templateEngine) {
this.dataFormatter = dataFormatter;
this.templateEngine = templateEngine;
}
}
Qualified Dependencies
9. Using @Named and @Qualifier
Custom Qualifiers:
import javax.inject.Qualifier;
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface DatabaseUrl {}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiKey {}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Production {}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Development {}
Qualified Providers:
@Module
public class ConfigurationModule {
@Provides
@DatabaseUrl
public String provideDatabaseUrl() {
return "jdbc:mysql://localhost:3306/production";
}
@Provides
@ApiKey
public String provideApiKey() {
return System.getenv("API_KEY");
}
@Provides
@Production
public DataSource provideProductionDataSource(@DatabaseUrl String url) {
return createDataSource(url, "prod_user", "prod_pass");
}
@Provides
@Development
public DataSource provideDevelopmentDataSource() {
return createDataSource("jdbc:h2:mem:testdb", "sa", "");
}
private DataSource createDataSource(String url, String user, String password) {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl(url);
ds.setUsername(user);
ds.setPassword(password);
return ds;
}
}
Using Qualified Dependencies:
public class ApplicationService {
private final DataSource productionDataSource;
private final DataSource developmentDataSource;
private final String apiKey;
@Inject
public ApplicationService(@Production DataSource productionDataSource,
@Development DataSource developmentDataSource,
@ApiKey String apiKey) {
this.productionDataSource = productionDataSource;
this.developmentDataSource = developmentDataSource;
this.apiKey = apiKey;
}
}
Component Dependencies and Subcomponents
10. Subcomponents for Scoped Dependencies
RequestComponent.java:
@RequestScope
@Subcomponent(modules = RequestModule.class)
public interface RequestComponent {
// Factory method to create instances
@Subcomponent.Factory
interface Factory {
RequestComponent create();
}
// Methods to expose dependencies
RequestContext getRequestContext();
UserService getUserService();
OrderService getOrderService();
// Inject into classes
void inject(OrderController orderController);
}
RequestModule.java:
@Module
public class RequestModule {
@Provides
@RequestScope
public RequestContext provideRequestContext() {
return new RequestContext();
}
@Provides
@RequestScope
public OrderService provideOrderService(UserService userService,
PaymentService paymentService) {
return new OrderService(userService, paymentService);
}
}
Main Application Component:
@Singleton
@Component(modules = {
AppModule.class,
DatabaseModule.class,
NetworkModule.class
})
public interface ApplicationComponent {
// Expose factory for subcomponents
RequestComponent.Factory requestComponent();
// Other dependencies...
UserService getUserService();
PaymentService getPaymentService();
}
Usage in Web Framework:
public class OrderController {
private final RequestComponent requestComponent;
@Inject
public OrderController(ApplicationComponent appComponent) {
this.requestComponent = appComponent.requestComponent().create();
this.requestComponent.inject(this);
}
public void processOrder(Order order) {
OrderService orderService = requestComponent.getOrderService();
orderService.process(order);
}
}
Dagger in Web Applications
11. Spring Boot Integration
SpringBootDaggerIntegration.java:
@Configuration
public class DaggerConfig {
@Bean
@Singleton
public ApplicationComponent applicationComponent() {
return DaggerApplicationComponent.builder()
.appModule(new AppModule())
.databaseModule(new DatabaseModule())
.networkModule(new NetworkModule("https://api.example.com"))
.build();
}
}
@Service
public class UserService {
private final com.example.dagger.UserService daggerUserService;
@Inject
public UserService(ApplicationComponent applicationComponent) {
this.daggerUserService = applicationComponent.getUserService();
}
public User findUser(Long id) {
return daggerUserService.getUser(id);
}
}
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
@Inject
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.findUser(id);
return ResponseEntity.ok(user);
}
}
Testing with Dagger
12. Test Modules and Components
TestDatabaseModule.java:
@Module
public class TestDatabaseModule {
@Provides
@Singleton
public DataSource provideTestDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("test-schema.sql")
.build();
}
@Provides
@Singleton
public UserRepository provideTestUserRepository() {
return new MockUserRepository();
}
}
TestAppComponent.java:
@Singleton
@Component(modules = {
TestDatabaseModule.class,
NetworkModule.class
})
public interface TestAppComponent extends AppComponent {
@Component.Builder
interface Builder {
Builder testDatabaseModule(TestDatabaseModule module);
Builder networkModule(NetworkModule module);
TestAppComponent build();
}
}
Unit Tests:
public class UserServiceTest {
private UserService userService;
private TestAppComponent testComponent;
@BeforeEach
void setUp() {
testComponent = DaggerTestAppComponent.builder()
.testDatabaseModule(new TestDatabaseModule())
.networkModule(new NetworkModule("http://test.api.com"))
.build();
userService = testComponent.getUserService();
}
@Test
void testGetUser() {
User user = userService.getUser(1L);
assertNotNull(user);
assertEquals("Test User", user.getName());
}
}
// Mock implementation for testing
class MockUserRepository implements UserRepository {
@Override
public User findById(Long id) {
return new User("Test User", "[email protected]");
}
@Override
public void save(User user) {
// Mock implementation
}
}
Advanced Patterns
13. Provider Injection and Lazy Injection
Provider and Lazy Usage:
public class OrderProcessor {
private final Provider<OrderService> orderServiceProvider;
private final Lazy<ReportService> reportServiceLazy;
private final Set<Provider<EventListener>> eventListeners;
@Inject
public OrderProcessor(Provider<OrderService> orderServiceProvider,
Lazy<ReportService> reportServiceLazy,
Set<Provider<EventListener>> eventListeners) {
this.orderServiceProvider = orderServiceProvider;
this.reportServiceLazy = reportServiceLazy;
this.eventListeners = eventListeners;
}
public void processOrder(Order order) {
// Get new instance each time
OrderService orderService = orderServiceProvider.get();
orderService.process(order);
// Get instance only when needed (lazy initialization)
ReportService reportService = reportServiceLazy.get();
reportService.generateReport(order);
// Multiple implementations
eventListeners.forEach(provider -> {
EventListener listener = provider.get();
listener.onOrderProcessed(order);
});
}
}
14. Multibinding for Multiple Implementations
Multibinding Module:
@Module
public abstract class EventListenerModule {
@Binds
@IntoSet
public abstract EventListener bindEmailEventListener(EmailEventListener impl);
@Binds
@IntoSet
public abstract EventListener bindSmsEventListener(SmsEventListener impl);
@Binds
@IntoSet
public abstract EventListener bindPushNotificationEventListener(
PushNotificationEventListener impl);
}
// Usage
public class EventDispatcher {
private final Set<EventListener> eventListeners;
@Inject
public EventDispatcher(Set<EventListener> eventListeners) {
this.eventListeners = eventListeners;
}
public void dispatchEvent(Event event) {
eventListeners.forEach(listener -> listener.handle(event));
}
}
Best Practices and Common Patterns
15. Dagger Best Practices
Component Organization:
// Good: Organized by feature or layer
@Singleton
@Component(modules = {
DataModule.class, // Database and repository layer
ServiceModule.class, // Business logic layer
WebModule.class, // Web and API layer
ConfigModule.class // Configuration
})
public interface ApplicationComponent {
// Expose only what's needed
UserService getUserService();
OrderService getOrderService();
}
// Good: Use @Binds for interface implementations
@Module
public abstract class ServiceModule {
@Binds
public abstract UserRepository bindUserRepository(UserRepositoryImpl impl);
@Binds
public abstract PaymentService bindPaymentService(StripePaymentService impl);
}
// Good: Scoped dependencies
@Module
public class SessionModule {
@Provides
@SessionScope
public UserSession provideUserSession() {
return new UserSession();
}
}
Error-Prone Patterns to Avoid:
// ❌ Avoid: Circular dependencies
public class ServiceA {
@Inject
public ServiceA(ServiceB serviceB) { }
}
public class ServiceB {
@Inject
public ServiceB(ServiceA serviceA) { } // Circular dependency!
}
// ✅ Solution: Use Provider or refactor
public class ServiceA {
@Inject
public ServiceA(Provider<ServiceB> serviceBProvider) { }
}
// ❌ Avoid: Too many constructor parameters
public class Service {
@Inject
public Service(A a, B b, C c, D d, E e, F f, G g) { } // Too many!
}
// ✅ Solution: Split into smaller services or use factories
public class Service {
@Inject
public Service(ServiceDependencies deps) { }
}
@Module
public class ServiceDependenciesModule {
@Provides
public ServiceDependencies provideDependencies(A a, B b, C c) {
return new ServiceDependencies(a, b, c);
}
}
Key Benefits of Dagger
- Compile-time Safety: Errors are caught at compile time rather than runtime
- Performance: No reflection, generated code is efficient
- Testability: Easy to replace dependencies with test doubles
- Scalability: Handles complex dependency graphs
- Maintainability: Clear dependency structure and relationships
- No Boilerplate: Reduces manual dependency management code
Dagger provides a robust, type-safe dependency injection solution that's particularly valuable in large-scale applications where maintainability and testability are crucial.