Guice Throwing Providers

In my current project we are using guice for managing dependencies. Guice is very easy to start with and you can integrate it into your project with minimum effort.

Guice comes with standard extensions. At one point I had to take a look at the throwing providers extension. The reason is according to the documentation guice isn’t very good at handling exceptions that occur during provision. During startup when guice wires up dependencies if you need to do some I/O operations (loading property files for example) it is better to use throwing providers.

So far so good. The problem about throwing providers is that the documentation found here is really confusing. Code for some of the classes (FeedFactory) mentioned in the documentation is not provided.

Another confusing point is that according to the documentation @CheckedProvides has all the benefits of @Provides methods, and also allows you to specify exceptions. However there is a fundamental difference between @Provides and @CheckProvides. When you annotate a method with @Provides the type of the object you want to inject is the same as the method return type. On the other hand when you use @CheckedProvides you can inject the provider not the object that the provider provides.

Let me try to give an example.

Let’s say that I have a Foo class with a dependency on Bar class:

package com.example;

public class Foo {
    private final Bar bar;
    public Foo(Bar provider){
        this.bar = provider;
    }
    public Bar getBar() {
        return bar;
    }
}

 

package com.example;

public class Bar {
    private String id;
    public Bar() {
        this.id = "Default id";
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getId() {
        return id;
    }
}

In order to inject Bar dependency I use the @Inject annotation on Foo constructor. So Foo becomes:

package com.example;

import com.google.inject.Inject;

public class Foo {
    private final Bar bar;
    @Inject
    public Foo(Bar bar){
        this.bar = bar;
    }
    public Bar getBar() {
        return bar;
    }
}

Let’s assume that I would like to create a Bar instance myself and inject it into Foo and creating a Bar instance might throw the following exception.

package com.example;

public class AppException extends Exception{

    public AppException(String message) {
        super(message);
    }
}

In this case I need to create a throwing provider.

The first step to create a throwing provider is to extend CheckedProvider interface:

package com.example;

import com.google.inject.throwingproviders.CheckedProvider;

public interface BarProvider<T> extends CheckedProvider<T> {
    T get() throws AppException;
}

After creating the interface that extends CheckedProvider you have to install ThrowingProviderBinder in your module and annotate a method with @CheckedProvides:

package com.example;

import com.google.inject.AbstractModule;
import com.google.inject.throwingproviders.CheckedProvides;
import com.google.inject.throwingproviders.ThrowingProviderBinder;

public class AppModule extends AbstractModule {
    protected void configure() {
        install(ThrowingProviderBinder.forModule(this));
    }
    @CheckedProvides(BarProvider.class)
    Bar getInner() throws AppException {
        Bar bar = new Bar();
        bar.setId("A different id");
        return bar;
    }
}

Documentation says @CheckedProvides has all the benefit of @Provides so the above steps should be enough in order to inject Bar instance that we created with a different id into Foo. Let’s try:

package com.example;

import com.google.inject.Guice;
import com.google.inject.Injector;

public class App {
    public static void main(String[] args) {
        Injector in = Guice.createInjector(new AppModule());
        Foo foo = in.getInstance(Foo.class);
        System.out.println(foo.getBar().getId());
    }
}

When you run the above example the result is the following:

Default id

So what happened? Guice did not call our getInner method annotated with @CheckedProvides. Instead it created the default instance of Bar class with Default id.

The reason is that methods annotated with @CheckedProvides works quite differently than methods annotated with @Provides.

@CheckedProvides creates a dynamic implementation of the interface given as the value. (BarProvider.class in our case) As a result you cannot inject Bar directly but can inject a BarProvider.

Let’s modify our Foo class:

package com.example;

import com.google.inject.Inject;

public class Foo {
    private final Bar bar;

    @Inject
    public Foo(BarProvider<Bar> provider) throws AppException {
        this.bar = provider.get();
    }
    public Bar getBar() {
        return bar;
    }
}

Let’s run our application again.

A different id

And now we see the expected result.

But wait a minute. Is this really an elegant solution to deal with exceptions. I don’t think so.

First and most important of all, Foo class now needs to know about BarProvider. This makes my model dependent on Guice.

Second point is that for each exception that you would like to handle you need to repeat the steps above. Boilerplate code is bigger than the initialization logic itself.

Leave a comment