In the process of building a plugin architecture using Guice's MapBinder, using Guice 3.0, I've run into the issue that Guice throws a CreationException when stripped of all modules, which is a viable configuration in this application. Is there a way to get Guice to inject an empty Map? Or, by extension, an empty set with Multibinder?
For example:
interface PlugIn {
void doStuff();
}
class PlugInRegistry {
#Inject
public PlugInRegistry(Map<String, PlugIn> plugins) {
// Guice throws an exception if OptionalPlugIn is missing
}
}
class OptionalPlugIn implements PlugIn {
public void doStuff() {
// do optional stuff
}
}
class OptionalModule extends AbstractModule {
public void configure() {
MapBinder<String, PlugIn> mapbinder =
MapBinder.newMapBinder(binder(), String.class, PlugIn.class);
mapbinder.addBinding("Optional").to(OptionalPlugIn.class);
}
}
In the documentation for MapBinder, it says:
Contributing mapbindings from different modules is supported. For example, it is okay to have both CandyModule and ChipsModule both create their own MapBinder, and to each contribute bindings to the snacks map. When that map is injected, it will contain entries from both modules.
So, what you do is, don't even add the entry in your basic module. Do something like this:
private final class DefaultModule extends AbstractModule {
protected void configure() {
bind(PlugInRegistry.class);
MapBinder.newMapBinder(binder(), String.class, PlugIn.class);
// Nothing else here
}
}
interface PlugIn {
void doStuff();
}
Then, when you create your injector, if the additional modules exist, great! Add them. If they don't exist, then don't add them. In your class, do this:
class PlugInRegistry {
#Inject
public PlugInRegistry(Map<String, PlugIn> plugins) {
PlugIn optional = plugins.get("Optional");
if(optional == null) {
// do what you're supposed to do if the plugin doesn't exist
}
}
}
Note: You have to have the empty MapBinder, or the Map injection won't work if there are no optional modules present.
Related
I am using Java 8 with a Play framework. My goal is to inject a map whose keys are enum values and values are implementations of a specific interface.
Here is my enum:
public enum Service {
HTML("html"), TEXT("txt");
private String serviceId;
Service(String serviceId) { this.serviceId = serviceId; }
}
I have Executable interface
public interface Executable { void execute(); }
and two classes that implement it:
public class HtmlWorker implements Executable { ... }
public class TextWorker implements Executable { ... }
I would like to be able to inject Map<Service, Executable> serviceMap so I can have access to a specific implementation using a Service key:
public class Processor {
#Inject
Map<Service, Executable> serviceMap;
public void doStuff() {
Executable htmlService = this.serviceMap.get(Service.HTML);
Executable textService = this.serviceMap.get(Service.TEXT);
// do more stuff
}
}
I added bindings to the module class:
public class AppModule extends AbstractModule {
#Override
protected void configure() {
MapBinder<Service, Executable> serviceBinder = MapBinder
.newMapBinder(binder(), Service.class, Executable.class);
serviceBinder.addBinding(Service.HtmlService).to(HtmlWorker.class);
serviceBinder.addBinding(Service.TextService).to(TextWorker.class);
}
The problem is that serviceMap is never injected and it is always null inside Processor. What am I missing?
According to the official MapBinder documentation the MapBinder.addBinding should take the map's key.
As far as concerning your provided example what about changing AbstractModule's code from:
serviceBinder.addBinding(Service.HtmlService).to(HtmlWorker.class);
serviceBinder.addBinding(Service.TextService).to(TextWorker.class);
to
serviceBinder.addBinding(Service.HTML).to(HtmlWorker.class); // <-- see the enum constant here?
serviceBinder.addBinding(Service.TEXT).to(TextWorker.class);
Anyway I don't know where the class Service.HtmlService in your example comes from since you didn't state it anywhere.
I have just learned about Dependency injection (DI) and I am beginning to like it. To inject dependencies I am using Google Guice framework. Everything was running conceptually fine but while writing a module a thought came to my mind that what if my module require dependencies as a constructor, after all, it is just a class extending AbstractModule.
So, basically, I have 3 modules as a whole.
Environment Module
public class EnvModule extends AbstractModule {
#Override
protected void configure() {
install(new Servicemodule());
}
}
ServiceModule
public class ServiceModule extends AbstractModule {
private final boolean isEnabled;
#Override
protected void configure() {
if (isEnabled) {
install (new ThirdModule());
}
}
ThirdModule (It does not take any arguments in any constructor and have some bindings of its own)
Basically, the variable in the service module defines whether my application needs to install the third module or not. And that variable is defined in an application configuration file. So how do I inject that variable in the ServiceModule? As the field is final, setter injection is not possible, is there a way to use construction injection or field injection to inject the value.
I see the following options:
Use system variable:
ServiceModule() {isEnabled = System.getProperty("isThirdModuleEnabled")};
Read the config file directly in the ServiceModule() constructor
Use #Provides:
class ServiceModule ... {
#Provide #Singleton ThirdModuleParam getThirdModuleParam(...) {
//read the config file
ThirdModuleParam res = new ThirdModuleParam();
res.setIsEnabed(...);
return res;
}
}
class ThirdModule {
#Provide SomeThirdModuleClass getIt(ThirdModuleParam param) {
return param.isEnabled() ? new SomeThirdModuleClass() : null;
}
I have 3 beans in one package that I would like to be eager singletons.
public class Module1 implements Module {
#Override
public void configure(Binder binder) {
binder.bind(Bean1.class).asEagerSingleton();
binder.bind(Bean2.class).asEagerSingleton();
binder.bind(Bean3.class).asEagerSingleton();
}
}
How can I configure them all as eager singletons without exact writing class name using Google Guice?
I'm looking for something like marking Bean1, Bean2, Bean3 by some custom annotation or scanning by package name.
I would do something like this:
#Override
protected void configure() {
try {
for (ClassInfo classInfo:
ClassPath.from(getClass().getClassLoader()).getTopLevelClasses("my.package.name")) {
bind(classInfo.load()).asEagerSingleton();
}
} catch (IOException e) { // Do something
}
}
ClassPath is coming from the Guava library which Guice 4 depends. If you're using Guice 3 you will probably need to add this dependency.
There may also be 3rd party libraries that include an #EagerSingleton annotation, FWIW.
I'm trying to use Dagger to do Dependency Injection on an app that I'm building, and running into trouble constructing proper DAGs when I have one package's Module depending on values provided by the Injector (presumably provided by another Module).
If I have a simple module for some configurable variables (that I might want to swap out for testing environments, for example)
#Module(
injects = DependentModule.class,
)
public class ConfigModule {
#Provides #Named("ConfigOption") String provideConfigOption() {
return "This Module's configurable option!";
}
}
and another module depends on it, e.g.
#Module(
injects = {
TopLevelClass.class
}
)
public class DependentModule {
#Inject #Named("ConfigOption") String configOption;
public DependentModule() {
ObjectGraph.create(this).inject(this);
doSomethingWithConfig(configOption);
}
#Provides #Singleton UsefulValue provideUsefulValue() {
// Whatever this module needs to do...
}
}
The line where I try to bootstrap the injection in the constructor fails, and it complains that I haven't specified an explicit injects line in a proper module.
Through trial-and-error I see this goes away if in #Module I add a line include = ConfigModule.class, but this strikes me as semantically wrong, since a) the DAG I'll be creating will now include the values of both modules, rather than just one, and b) it defeats the purpose/flexibility of DI in the first place to link a specific Module rather than simply let Dagger inject the appropriate value.
I'm presuming I shouldn't be creating an Object Graph with this only to inject into it? But then I run into the issue of not linking a specific Module...
Succinctly:
What is the 'proper' way to Inject values into one Modules that may be provided from other Modules? Here I'm using field injection, but my experiments with constructor injection have also resulted in a lot of failure.
Relatedly, when is it appropriate to use addsTo vs. includes?
Thanks :)
You don't need to do any of injection (field or constructor) in one module from another explicitly. Just use addsTo and includes.
includes allows to add modules to another and use everything they provide. Example:
#Module()
public class ModuleA {
#Provides #Named("ValueA") String provideValueA() {
return "This is ValueA";
}
}
#Module(
includes = ModuleA.class
)
public class ModuleB {
// ValueA comes from ModuleA
#Provides #Named("ValueB") String provideValueB(#Named("ValueA") String valueA) {
return valueA + " and ValueB";
}
}
addsTo is used with ObjectGraph.plus(Object... modules). When graph is already created and contains some modules (e.g. in Application class), you can create new graph (e.g. in Activity) using plus. Example:
#Module()
public class ApplicationModule {
#Provides #Named("ValueA") String provideValueA() {
return "This is ValueA";
}
}
#Module(
addsTo = ApplicationModule.class
)
public class ActivityModule {
// ValueA comes from ApplicationModule
#Provides #Named("ValueB") String provideValueB(#Named("ValueA") String valueA) {
return valueA + " and ValueB";
}
}
public class DemoApplication extends Application {
private ObjectGraph graph;
#Override public void onCreate() {
super.onCreate();
graph = ObjectGraph.create(getModules().toArray());
}
protected List<Object> getModules() {
return Arrays.asList(
new ApplicationModule()
);
}
public void inject(Object object) {
graph.inject(object);
}
public ObjectGraph getObjectGraph() {
return graph;
}
}
public class DemoActivity extends Activity {
private ObjectGraph activityGraph;
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create the activity graph by .plus-ing our modules onto the application graph.
DemoApplication application = (DemoApplication) getApplication();
activityGraph = application.getApplicationGraph().plus(new ActivityModule());
// Inject ourselves so subclasses will have dependencies fulfilled when this method returns.
activityGraph.inject(this);
}
#Override protected void onDestroy() {
// Eagerly clear the reference to the activity graph to allow it to be garbage collected as
// soon as possible.
activityGraph = null;
super.onDestroy();
}
}
Also you can check this example to create scopes of graphs.
I'd need something like
#DefaultInstance(Level.NORMAL)
enum Level {NORMAL, FANCY, DEBUGGING}
which would make Guice to return Level.NORMAL for the expression
injector.getInstance(Level.class)
There's no such thing like #DefaultInstance. As a workaround I've tried #ProvidedBy with a trivial Provider, but it doesn't work.
Maybe overriding modules could help you. A default level can be configured using AppLevel module:
public class AppModule extends AbstractModule {
#Override
public void configure() {
bind(Level.class).toInstance(Level.NORMAL);
// other bindings
}
}
and a specific one can be configured in a small overriding module:
public class FancyLevelModule extends AbstractModule {
#Override
public void configure() {
bind(Level.class).toInstance(Level.FANCY);
}
}
At the end just create an injector overriding the AppModule with a specific Level config:
public static void main(String[] args) {
Injector injector =
Guice.createInjector(
Modules.override(new AppModule()).with(new FancyLevelModule())
);
System.out.println("level = " + injector.getInstance(Level.class));
}
UPDATE
This problem can be solved in a bit different way. Let's say that Level is used in a class as an injected field:
class Some
{
#Injected(optional = true)
private Level level = Level.NORMAL;
}
A default level will be initialized as part of the creation of instances of Some. If some Guice config module declares some other level it will be optionally injected.
A solution, but unfortunatelly not using annotations, would be:
enum Level
{
NORMAL, FANCY, DEBUGGING;
static final Level defaultLevel = FANCY; //put your default here
}
then define module like this:
public class DefaultLevelModule extends AbstractModule
{
#Override public void configure()
{
bind(Level.class).toInstance(Level.defaultLevel);
}
}
It's the issue 295 and looks like a very trivial bug.
I've patched it for myself and maybe one day somebody there will fix this very old issue, too.