Search code examples
javadependency-injectionguiceoptional-binding

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.


Solution

  • 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