burak emre burak emre - 3 months ago 64
Java Question

Fail-safe injector for child modules. Optional bindings in google guice

I have a child module called ChildPlugin and I inject classes from main module as follows:

public class ChildPlugin {
private ExampleClass demo;

@Inject
public void setDemo(ExampleClass demo) {
this.demo = demo;
}
}


The problem is that I don't know whether the main module binds
ExampleClass
and if it's not Guice throws an exception when creating the injector. What I want to do is to make Guice pass
null
or
Optional.empty
if ExampleClass is not binded.

I do not have access to the main module so I cannot change binder for
ExampleClass
to
OptionalBinder
, I tried
@Nullable
and
Optional<ExampleClass>
in
ChildPlugin.setDemo
method but it didn't work.

Answer

there are 2 ways to do this.

Optional injection

Use the com.google.inject.Inject annotation. This one allows you to specify optional on the annotation. See this example:

public class GuiceInjectOptional extends AbstractModule {

    @Override
    protected void configure() {

        // method 1: 
        bind(B.class).in(Singleton.class);

    }

    public static class A {

        private String name;
        // non null constructor so that A can't be instantiated automatically by guice
        public A(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "I am: " + name;
        }
    }

    public static class B {

        @Inject(optional=true)
        A obj;

        void run() {
            System.out.println("Object is: " + obj);
        }
    }

    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new GuiceInjectOptional());
        injector.getInstance(B.class).run();;
    }

}

The annotation on class B indicates that A is optional. It does not get injected. Running the snippet prints:

Object is: null

Method 2 (which is the way you'd do it after guice 4+). You can specify optional bindings. These bindings even allow you to define default values if you like. You can then inject an optional value like in this snippet I wrote up:

public class GuiceInjectOptional extends AbstractModule {

    @Override
    protected void configure() {
        // set up optional binding for A.
        OptionalBinder.newOptionalBinder(binder(), A.class);

        bind(B.class).in(Singleton.class);
    }

    public static class A {

        private String name;
        // non null constructor so that A can't be instantiated automatically by guice
        public A(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "I am: " + name;
        }
    }

    public static class B {

        @Inject
        Optional<A> obj;

        void run() {
            System.out.println("Object is present: " + obj.isPresent());
            System.out.println("Object is: " + obj);
        }
    }

    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new GuiceInjectOptional());
        injector.getInstance(B.class).run();;
    }

}

The Inject annotation is now non-optional, but guice is aware that class A may or may not have been bound. Running the snippet will print:

Object is present: false
Object is: Optional.empty

Finally, you can then just bind A normally and guice will inject it:

public class GuiceInjectOptional extends AbstractModule {

    @Override
    protected void configure() {
        // set up optional binding for A.
        OptionalBinder.newOptionalBinder(binder(), A.class);

        bind(A.class).toInstance(new A("Pandaa!"));
        bind(B.class).in(Singleton.class);
    }

    public static class A {

        private String name;
        // non null constructor so that A can't be instantiated automatically by guice
        public A(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "I am: " + name;
        }
    }

    public static class B {

        @Inject
        Optional<A> obj;

        void run() {
            System.out.println("Object is present: " + obj.isPresent());
            System.out.println("Object is: " + obj);
        }
    }

    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new GuiceInjectOptional());
        injector.getInstance(B.class).run();;
    }

}

And the above will print:

Object is present: true
Object is: Optional[I am: Pandaa!]

And this is how you have fail safe optional bindings with guice :) I hope this helps.

Edit: I just saw the guice-3 tag, so you'll want to use the optional annotation method rather than the optional binder. With the optional annotation it will stay null instead of being an Optional value.

Artur

Comments