google-site-verification: google61fe8ba583a51912.html
Dependency Injection with Dagger in Java

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

  1. Compile-time Safety: Errors are caught at compile time rather than runtime
  2. Performance: No reflection, generated code is efficient
  3. Testability: Easy to replace dependencies with test doubles
  4. Scalability: Handles complex dependency graphs
  5. Maintainability: Clear dependency structure and relationships
  6. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper