Enterprise Java

Guice @Provides vs Provider Classes

Guice is a fast, modular, and annotation-driven dependency injection framework developed by Google for Java applications. It simplifies the process of wiring dependencies by offering multiple strategies such as @Inject, @Provides methods, and custom implementations of the Provider<T> interface. This article delves into how Guice handles dependency provisioning and compares two common approaches—using the @Provides annotation vs creating custom Provider classes.

1. Overview of Guice

Google Guice is a lightweight dependency injection (DI) framework for Java, developed and maintained by Google. It provides a way to decouple application components by managing the creation and injection of objects, helping developers build clean, testable, and maintainable code.

In Guice, dependencies are typically declared using the @Inject annotation, and bindings between abstractions and implementations are configured using modules. Guice supports multiple mechanisms for defining how objects should be created, including:

  • Simple bindings via bind()
  • Provider classes (Provider<T>)
  • Methods annotated with @Provides

To use Guice in a Java Maven project, add the following dependency to the pom.xml:

<dependencies>
    <dependency>
        <groupId>com.google.inject</groupId>
        <artifactId>guice</artifactId>
        <version>7.0.0</version>
    </dependency>
</dependencies>

This dependency gives us access to Guice core APIs, including @Inject, @Provides, Provider<T>, and AbstractModule.

2. Sample Use Case: MessageService

To illustrate both approaches, let’s create a simple interface and implementation: MessageService and EmailService.

public interface MessageService {

    void sendMessage(String message);
}
public class EmailService implements MessageService {

    private static final Logger LOG = Logger.getLogger(EmailService.class.getName());

    
    @Override
    public void sendMessage(String message) {
        LOG.log(Level.INFO, "EmailService: {0}", message);
    }
}

These serve as the target service and its implementation. We will use Guice to inject EmailService wherever MessageService is required.

3. Using @Provides

Guice allows us to use the @Provides annotation inside a module to define exactly how a dependency should be created.

public class AppModuleWithProvides extends AbstractModule {

    @Provides
    public MessageService provideMessageService() {
        // Custom logic could go here
        return new EmailService();
    }
}

Here, the @Provides method defines how to create a MessageService. When Guice encounters a request for MessageService, it calls this method to satisfy the dependency.

Here’s a class that shows this approach in action:

public class GuiceProvidesVsProviderExample {

    public static void main(String[] args) {
        // Using @Provides
        Injector injectorWithProvides = Guice.createInjector(new AppModuleWithProvides());
        MessageService service1 = injectorWithProvides.getInstance(MessageService.class);
        service1.sendMessage("Message sent using @Provides");
    }
}

4. Using a Custom Provider Class

Alternatively, Guice supports custom provider classes that implement the Provider<T> interface.

public class MessageServiceProvider implements Provider<MessageService> {

    @Override
    public MessageService get() {
        // Add initialization logic if needed
        return new EmailService();
    }
}

This Provider class encapsulates the instantiation logic for MessageService. It can be reused, tested independently, and used in multiple modules if needed.

Binding the Provider Class

To use the provider in a module, bind it using toProvider():

public class AppModuleWithProviderClass extends AbstractModule {

    @Override
    protected void configure() {
        bind(MessageService.class).toProvider(MessageServiceProvider.class);
    }
}

This tells Guice to use MessageServiceProvider to resolve all dependencies on MessageService. Let’s verify the dependency with the following class, which demonstrates this approach in action.

public class GuiceProvidesVsProviderExample {

    public static void main(String[] args) {

        // Using Provider class
        Injector injectorWithProvider = Guice.createInjector(new AppModuleWithProviderClass());
        MessageService service2 = injectorWithProvider.getInstance(MessageService.class);
        service2.sendMessage("Message sent using Provider class");
    }
}

Suppose we create an additional implementation of MessageService for SMS, called SMSService.

public class SMSService implements MessageService {
    
    private static final Logger LOG = Logger.getLogger(SMSService.class.getName());

    @Override
    public void sendMessage(String message) {
        LOG.log(Level.INFO, "SMSService: {0}", message);
    }
}
public class SMSServiceProvider implements Provider<MessageService> {

    @Override
    public MessageService get() {
        return new SMSService();
    }
}

Sometimes, we may need to use both the email and SMS implementations in different parts of an application. However, Guice does not permit binding the same interface to multiple implementations without using additional qualifiers.

If we try to bind both classes to the same interface like this:

    @Override
    protected void configure() {
        bind(MessageService.class).toProvider(MessageServiceProvider.class);
        bind(MessageService.class).toProvider(SMSServiceProvider.class);

    }

Guice will throw an error, as it doesn’t allow multiple bindings for the same type without distinguishing between them.

    com.google.inject.CreationException: Unable to create injector, see the following errors:

1) [Guice/BindingAlreadySet]: MessageService was bound multiple times.

Bound at:
1  : AppModuleWithProviderClass.configure(AppModuleWithProviderClass.java:17)
2  : AppModuleWithProviderClass.configure(AppModuleWithProviderClass.java:18)

Learn more:
  https://github.com/google/guice/wiki/BINDING_ALREADY_SET

1 error

======================
Full classname legend:
======================
AppModuleWithProviderClass: "com.jcg.example.AppModuleWithProviderClass"
MessageService:             "com.jcg.example.MessageService"
========================
End of classname legend:
========================

    at com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist (Errors.java:589)
    at com.google.inject.internal.InternalInjectorCreator.initializeStatically (InternalInjectorCreator.java:163)
    at com.google.inject.internal.InternalInjectorCreator.build (InternalInjectorCreator.java:110)
    at com.google.inject.Guice.createInjector (Guice.java:87)
    at com.google.inject.Guice.createInjector (Guice.java:69)
    at com.google.inject.Guice.createInjector (Guice.java:59)
    at com.jcg.example.GuiceProvidesVsProviderExample.main (GuiceProvidesVsProviderExample.java:22

Guice provides a built-in solution using the @Named annotation, which lets us distinguish bindings with unique string identifiers.

public class AppModuleWithProviderClass extends AbstractModule {

    @Override
    protected void configure() {
        bind(MessageService.class)
            .annotatedWith(Names.named("Email"))
            .toProvider(MessageServiceProvider.class);

        bind(MessageService.class)
            .annotatedWith(Names.named("Sms"))
            .toProvider(SMSServiceProvider.class);
    }
}

With this setup, we can now inject different versions of MessageService using the @Named annotation. Here’s how to inject each implementation in a class:

public class NotificationClient {

    private final MessageService emailService;
    private final MessageService smsService;

    @Inject
    public NotificationClient(@Named("Email") MessageService emailService, @Named("Sms") MessageService smsService) {
        this.emailService = emailService;
        this.smsService = smsService;
    }

    public void notifyAll(String message) {
        emailService.sendMessage(message);
        smsService.sendMessage(message);
    }
}  

This class receives both services using @Named, allowing fine-grained control over injected dependencies.

Let’s verify the dependency with the following class:

public class GuiceProvidesVsProviderExample {

    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new AppModuleWithProviderClass());
        NotificationClient client = injector.getInstance(NotificationClient.class);
        client.notifyAll("Hello from Guice!");
    }
}

When executed, the output will be:

Screenshot showing the output for Guice @Provides vs Provider classes comparison

5. Conclusion

This article explored Guice’s support for dependency provisioning using @Provides methods and Provider classes, as well as how to handle multiple bindings of the same type using @Named. Guice’s flexibility allows us to choose the most appropriate approach based on the complexity and modularity of our application.

6. Download the Source Code

This article explored @ Provides vs Provider classes in Guice.

Download
You can download the full source code of this example here: guice provides vs provider classes

Omozegie Aziegbe

Omos Aziegbe is a technical writer and web/application developer with a BSc in Computer Science and Software Engineering from the University of Bedfordshire. Specializing in Java enterprise applications with the Jakarta EE framework, Omos also works with HTML5, CSS, and JavaScript for web development. As a freelance web developer, Omos combines technical expertise with research and writing on topics such as software engineering, programming, web application development, computer science, and technology.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button