views:

52

answers:

3

Here is my client:

class Client {
    @Inject(optional=true) Service service;
}

Sometimes that Service isn't needed, and we know that information when the JVM starts (i.e before the binder is run). How do I make the binding optional? If I don't specify a binding at all it tries to new the Service (and fails because there is no zero-argument constructor: "Error while injecting at package.Client.service(Service.java:40): Could not find a suitable constructor in package.Service."), and I can't do:

binder.bind(Service.class).toInstance(null);

because Guice seems to disallow nulls. Any ideas?

+3  A: 

Are you using Guice 2.0? I've tried this both with Service being an interface (service field is always null) and with it being a class (null if it can't create a new instance with a JIT binding, an instance if it can). Both seem like what you'd expect.

In neither case did I use a binding like bind(Service.class).toInstance(null). If you do this, then you need to make sure that all injection points for Service specify that they allow it to be null. This can be done by annotating the injection point with any annotation called @Nullable (you can make your own or use an existing one):

class Client {
    @Inject @Nullable Service service;
}
ColinD
+2  A: 

I couldn't reproduce this behavior. When I run this example code, the injector creates and the optional injection is left unsatisfied. The program prints null:

public class OptionalInjections {

  static class Service {
    public Service(Void v) {} // not injectable
  }

  static class Client {
    @Inject(optional = true) Service service;
  }

  public static void main(String[] args) {
    Injector i = Guice.createInjector();
    Client client = i.getInstance(Client.class);
    System.out.println(client.service);
  }
}

Could you reproduce this in a JUnit test case and submit it to the Guice issue tracker?

Jesse Wilson
Running your test I get:com.google.inject.ConfigurationException: Error at Scratch$Service.<init>(Scratch.java:15) Error while injecting at Scratch$Client.service(Scratch.java:18): Could not find a suitable constructor in Scratch$Service. Classes must have either one (and only one) constructor annotated with @Inject or a zero-argument constructor.However, I've realised I'm using Guice 1.0 - it isn't something I've ever thought to upgrade. Now on Guice 2.0 and I get null as one might expect. Thanks!
jgubby
A: 

If you want to make the existence of a binding optional you can use @Inject(optional = true) for field and method injections. For contructor and other parameter type injections, you must use a helper class, for example:

class Foo {
  public String hello(Helper helper) {
    return Helper.string;
  }
  private static final class Helper {
    @Inject(optional = true) public String string = "";
  }
}

Note that the above doesn't allow null to be injected, so Foo#hello will never return null. If you do want to allow null, simply add the @Nullable annotation. Keep in mind that code like the following will fail unless you've provided a binding (to null, if nothing else) for String:

class Bar {
  @Inject @Nullable public String hello(@Nullable String string) {
    return string;
  }
}
NikolasCo