2

I am attempting to inject an annotated variable into the REQUEST scope:

Map<Key<?>, Object> seedMap = ImmutableMap.<Key<?>, Object>builder().
  put(Key.get(String.class, Names.named("name")), name).build();
return ServletScopes.scopeRequest(new InjectingCallable<>(injector, 
  GetModule.class), seedMap).call();

Where, InjectingCallable injects GetModule inside the REQUEST scope:

/**
 * A Callable that is constructed in one scope and injects a Callable into a potentially separate
 * scope.
 * <p/>
 * @param <V> the type of object returned by the Callable
 * @author Gili Tzabari
 */
public final class InjectingCallable<V> implements Callable<V>
{
    private final Injector injector;
    private final Class<? extends Callable<V>> delegate;

    /**
     * Creates a new InjectingCallable.
     * <p/>
     * @param injector the Guice injector
     * @param delegate the class to inject and delegate to
     */
    public InjectingCallable(Injector injector, Class<? extends Callable<V>> delegate)
    {
        Preconditions.checkNotNull(injector, "injector may not be null");
        Preconditions.checkNotNull(delegate, "delegate may not be null");

        this.injector = injector;
        this.delegate = delegate;
    }

    @Override
    public V call() throws Exception
    {
        return injector.getInstance(delegate).call();
    }
}

GetModule is defined as follows:

@RequestScoped
private static class GetModule implements Callable<Module>
{
    private final String name;
    private final Session session;

    @Inject
    public GetModule(@Named("name") String name, Session session)
    {
        this.name = name;
        this.session = session;
    }
}

When I run this code I get this error:

1) No implementation for java.lang.String annotated with @com.google.inject.name.Named(value=name) was bound.
  while locating java.lang.String annotated with @com.google.inject.name.Named(value=name)

If I bind the same variable to the global scope it works. If I remove the annotation, it works. This problem seems to be specific to Request-scoped annotated variables. Any ideas?

Gili
  • 86,244
  • 97
  • 390
  • 689
  • Hey, FYI for ImmutableMap you don't need to specify the generics on the `builder()` call on the right hand side of the equals sign since you have the generics on the left hand side. That's the whole point of the static builder() method. i.e. - `ImmutableMap, Object> seedMap = ImmutableMap.builder().put(...).build()`. – Tom Jan 26 '12 at 06:16
  • 1
    @Tom, I get a compiler error if I do that. Under Java7 update 2 it resolves the right-hand side to by default. Try compiling it on your end and let me know if you see the same. – Gili Jan 26 '12 at 17:34

1 Answers1

5

The problem is that you don't have a binding for this type. Just because you are explicitly seeding the value does not mean you don't have to bind it. you could say:

bind(String.class)
    .annotatedWith(Names.named("name"))
    .toProvider(Providers.<String>of(null));

and then if the name variable has the value "foo", you will get "foo" injected because you're seeding it. Seeding a value places it in the scope (which is just a cache) so that Guice won't run the provider of the value. By using a provider of null you can just let the value blow up if it's not seeded.

In short, Guice requires that you specify a way to provision every dependency, regardless of whether you plan to manually seed scopes (which should be a fairly rare thing btw).

Some unsolicited advice: - Please avoid injecting the injector. It makes catching these kinds of problems harder. It's best to have a single "root object". This is the single object you need to call injector.getInstance to create. For a lot of applications, this can just be your application server. (e.g. - injector.getInstance(MyServer.class).startServer()). Why does this help you? It makes it easier to detect at startup that all your dependencies are satisfied. If you inject the injector during a request and can call it to create arbitrary objects, you run the risk of getting some provision error due to a missing binding much later during runtime. Also if you do all your getInstance calls early on, it's easier to write tests that do this for you so that you can simply run a test to know that your Guice bindings are satisfied.

UPDATE:

If I bind the same variable to the global scope it works.

Hmm, did you basically do what I did? If so, my explanation above explains why that works :-).

If I remove the annotation, it works.

The reason this works is because Guice does have a binding for String since String has an empty constructor :-). Basically you have to have a single @Inject-able constructor, a no-arg constructor, or a provider to bind a type.

Alice Purcell
  • 12,622
  • 6
  • 51
  • 57
Tom
  • 21,468
  • 6
  • 39
  • 44
  • Btw, I'm not saying that one should *never* inject the injector... but it is very rarely needed and should probably only be used in situations where you're writing a guice-based framework for someone where you want to provide them a special way of injecting things. And even when you do that, you ought to try and make the appropriate `requireBinding` calls so that there are no surprises after startup. – Tom Jan 26 '12 at 06:20
  • I ended up using a child injector (with a private module) instead. I found this approach clearer than binding a Provider that is never used. Thank you for the clarification! – Gili Jan 27 '12 at 16:29
  • Consider using a custom Provider that throws a helpful IllegalStateException (e.g. "@Named(\"name\") String instance not provided in request scope") -- that way if you make a mistake, you get more help than a NullPointerException – Alice Purcell Jun 03 '13 at 16:50
  • @chrispy -- sure, I guess, but I probably wouldn't inject the message. I keep a utility class around for common provider stuff, and one them is a "failing provider" that lets you give it a RuntimeException or a message that gets wrapped in IllegalStateException. It's particularly useful in tests but also can be used like my example above. – Tom Jun 07 '13 at 14:22