Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing Guice bindings at runtime

I would like to be able to change the Guice injections at runtime to support multiple injections based on user input. This is what I would like to achieve:

public interface IDao {
    public int someMethod();
}

public class DaoEarth implements IDao {
    @Override
    public int someMethod(){ ... }
}

public class DaoMars implements IDao {
    @Override
    public int someMethod(){ ... }
}

public class MyClass {
    @Inject
    private IDao myDao;

    public int myMethod(String domain) {
        //If Domain == Earth, myDao should be of the type DaoEarth

        //If Domain == DaoMars, myDao should be of the type DaoMars
    }
}

I was thinking of writing my own Provider, but I don't know how to use that provider to change my bindings at runtime. Any input is welcome and appreciated :)!

Update Here's what I currently came up with, it's not as pretty as I'd like, so I'm still looking for feedback

public class DomainProvider {
    @Inject @Earth
    private IDaoProvider earthDaoProvider;

    @Inject @Mars
    private IDaoProvider marsDaoProvider;

    public IDaoProvider get(Domain domain){
        switch (domain){
            case EARTH:
                return earthDaoProvider;
            case MARS:
                return marsDaoProvider;
        }
    }

    public IDaoProvider get(String domain){
        Domain parsedDomain = Domain.valueOf(domain.toUpperCase());
        return get(parsedDomain);
    }
}

//MarsDaoProvider would be equivalent
public class EarthDaoProvider implements IDaoProvider {
    @Inject @Earth
    private IDao earthDao;

    public IDao getDao() {
        return earthDao;
    }
}

// This means that in "MyClass", I can do:
public class MyClass {
    @Inject
    private DomainProvider domainProvider;

    public int myMethod(String domain) {
        IDaoProvider daoProvider = domainProvider.get(domain);
        IDao dao = daoProvider.getDao();

        //Now "dao" will be of the correct type based on the domain
    }
}

//Of course elsewhere I have the bindings set like
bind(IDao.class).annotatedWith(Earth.class).to(EarthDao.class);
like image 774
Nepoxx Avatar asked Jun 05 '14 18:06

Nepoxx


People also ask

How does Guice binding work?

Using Guice If it's a simple object, it'll instantiate it and pass it in. If it has dependencies, it will resolve those dependencies, pass them into it's constructor, then pass the resulting object into your object.

What does bind mean in Guice?

A binding is an object that corresponds to an entry in the Guice map. You add new entries into the Guice map by creating bindings.

What is @named annotation in Guice?

Guice provides another way also to map bindings without creating a custom annoation. It allows so using @Named annotation.


1 Answers

Your version is almost perfect as it is: You're going to need to inject some kind of object that returns one or the other based on code you write, and don't need assisted injection or anything like that. That said, you can skip some of the boilerplate:

public class DomainProvider {
    // Just inject Providers directly without binding them explicitly.
    @Inject @Earth Provider<IDao> earthDaoProvider;
    @Inject @Mars Provider<IDao> marsDaoProvider;

    public Provider<IDao> get(Domain domain){
        switch (domain){
            case EARTH:
                return earthDaoProvider;
            case MARS:
                return marsDaoProvider;
        }
    }

    public Provider<IDao> get(String domain){
        Domain parsedDomain = Domain.valueOf(domain.toUpperCase());
        return get(parsedDomain);
    }
}
    

Your MyClass in that case would be exactly identical. Here, Provider is either the one-method generic interface com.google.inject.Provider, or the equivalent builtin javax.inject.Provider that it extends. Read more about Guice Providers on the relevant Guice wiki topic.

bind(IDao.class).annotatedWith(Earth.class).to(EarthDao.class);
// You can now inject "@Earth IDao" and also "@Earth Provider<IDao>".

Basically, if you bind a key Foo (to a class, provider, @Provides method, or instance), you automatically get to inject either a Foo or Provider<Foo> with no additional work. Providers are also a great way to ensure that you get a new instance with every call to get, if that's what you want; with your original, you'll always get the same instance of EarthDao or MarsDao for any given DomainProvider you inject. (If you have a scoped binding like @Singleton, Guice will respect that too; Provider just lets Guice get involved, rather than reusing a plain old Java reference.)

This means you can skip your custom EarthDaoProvider and MarsDaoProvider, unless you really need to perform any external initialization on them—at which point you'd probably be better off calling bind(EarthDao.class).toProvider(EarthDaoProvider.class) so the preparation also happens when injecting EarthDao directly. You could also just have DomainProvider return an IDao instance directly by calling get on the appropriate Provider, and be assured that it'll be a new instance every time.

like image 148
Jeff Bowman Avatar answered Oct 19 '22 18:10

Jeff Bowman