Hi Josh, Using the binding mechanism in guice might be a bit of an overkill for simple property bindings, however you have a number of alternatives:
- Properties itself allows for defaults handling, create a property file with the defaults and create one for the application and merge them in the properties object. Binding the resulting properties will have the defaults. - Bind the properties object itself and manually fetch the values from it - Specify defaults in the code, only override when you get a meaningful value injected A more complex way would be to roll your own configuration management. In our projects we use an in-house developed ConfigManager object which is similar to the Properties object but it provides typesafe getters for various primitive types. A generic TypeListener allows the injections of config values into the object. (conceptually very similar of what netflix's governator does) Obviously you can use the governator library also, it'll do the trick (and many others). I believe apache onami has a solution for configuration too. Hope this helps. -- L On Sat, Jan 31, 2015 at 5:25 AM, Joshua Moore-Oliva <[email protected]> wrote: > First off - I am only a few days into guice, so I apologize if my > questions are dumb - I have tried to compensate with what I hope are clear > examples. > > I am attempting to make liberal use of OptionalBinder in my code to allow > some default values to be specified and then potentially overriden by > configuration. I have run into a cascading series of issues. First, lets > start with what works in a simple case that currently works (but does not > have the default value functionality I hope to achieve with OptionalBinder): > > public class Simple { > private final int simpleInt; > private final String simpleStr; > > @Inject > public Simple(@Named("simpleInt") int simpleInt, @Named("simpleStr") > String simpleStr) { > this.simpleInt = simpleInt; > this.simpleStr = simpleStr; > } > > public int getSimpleInt() { > return simpleInt; > } > > public String getSimpleStr() { > return simpleStr; > } > } > > > If I wish to wire up the value of simpleVal from configuration, I can use > Names.bindProperties (which was recommended in the FAQ) > > public class SimpleTest { > > @Test > public void testGetSimpleVal() throws Exception { > Injector inj = Guice.createInjector(getTestModule()); > Simple s = inj.getInstance(Simple.class); > > assertEquals(1, s.getSimpleInt()); > assertEquals("default", s.getSimpleStr()); > } > > private Module getTestModule() { > return new AbstractModule() { > @Override protected void configure() { > bind(Simple.class); > > Properties p = new Properties(); > p.setProperty("simpleInt", "1"); > p.setProperty("simpleStr", "default"); > > Names.bindProperties(binder(), p); > } > }; > } > } > > So far, so good - Names.bindProperties is great, it even converts my > strings to ints ("1" -> 1 for simpleInt) > > Now I attempt to set up default values for simpleInt and simpleStr using > OptionalBinder. I do this by returning another Module with default > bindings. (I assume I should do this on a package not class level, but it > keeps the example simple). > > public class Simple { > private final int simpleInt; > private final String simpleStr; > > @Inject > public Simple(@Named("simpleInt") int simpleInt, @Named("simpleStr") > String simpleStr) { > this.simpleInt = simpleInt; > this.simpleStr = simpleStr; > } > > public int getSimpleInt() { > return simpleInt; > } > > public String getSimpleStr() { > return simpleStr; > } > > public static AbstractModule getDefaultsModule() { > return new AbstractModule() { > @Override protected void configure() { > > OptionalBinder.newOptionalBinder(binder(), > Key.get(Integer.class, Names.named("simpleInt" ))) > .setDefault().toInstance(12345); > > //OptionalBinder.newOptionalBinder(binder(), > Key.get(String.class, Names.named("simpleInt" ))) > // .setDefault().toInstance("12345"); > > OptionalBinder.newOptionalBinder(binder(), > Key.get(String.class, Names.named("simpleStr"))) > .setDefault().toInstance("Simple class default"); > } > }; > } > } > > public class SimpleTest { > > @Test > public void testGetSimpleVal() throws Exception { > Injector inj = Guice.createInjector(Simple.getDefaultsModule(), > getTestModule()); > Simple s = inj.getInstance(Simple.class); > > assertEquals(1, s.getSimpleInt()); > assertEquals("default", s.getSimpleStr()); > } > > private Module getTestModule() { > return new AbstractModule() { > @Override protected void configure() { > bind(Simple.class); > > Properties p = new Properties(); > p.setProperty("simpleInt", "1"); > p.setProperty("simpleStr", "default"); > > Names.bindProperties(binder(), p); > } > }; > } > } > > At this point, I get the following error > > 1) A binding to java.lang.String annotated with > @com.google.inject.name.Named(value=simpleStr) was already configured at > com.baselayer.device.scratch.Simple$1.configure(Simple.java:42). > at > com.baselayer.device.scratch.SimpleTest$1.configure(SimpleTest.java:33) > > Looking into the source code for Names.bindProperties, I see this is > because it does not use setBinding, but rather normal non-optional > .toInstance. > > binder.bind(Key.get(String.class, new NamedImpl(propertyName))). > toInstance(value); > > At this point, I assume there is some good reason that you must use > setBindings, though I will make the suggestion that as a new user of guice, > I would expect a normal non-optional .toInstance to override a .setDefault. > > So, shamelessly copying the source code of Names.bindProperties, I created > my own replacement test module as follows: > > private Module getTestModule() { > return new AbstractModule() { > @Override protected void configure() { > bind(Simple.class); > > Properties p = new Properties(); > p.setProperty("simpleInt", "1"); > p.setProperty("simpleStr", "default"); > > //Names.bindProperties(binder(), p); > //The following code replaces Names.bindProperties > Binder skippedBinder = binder().skipSources(Names.class); > > for (Enumeration<?> e = p.propertyNames(); > e.hasMoreElements(); ) { > String propertyName = (String) e.nextElement(); > String value = p.getProperty(propertyName); > OptionalBinder.newOptionalBinder(skippedBinder, > Key.get(String.class, > Names.named(propertyName))).setBinding().toInstance(value); > } > } > }; > } > > However - unlike Names.bindProperties, the String binding will not stick > to simpleInt. The very useful auto-mutation of String to Int that occurs > with non-optional .toInstance does not appear with .setBinding. Either I > get > > 1) No implementation for java.lang.Integer annotated with > @com.google.inject.name.Named(value=simpleInt) was bound. > while locating java.lang.Integer annotated with > @com.google.inject.name.Named(value=simpleInt) > for parameter 0 at > com.baselayer.device.scratch.Simple.<init>(Simple.java:20) > at > com.baselayer.device.scratch.SimpleTest$1.configure(SimpleTest.java:27) > > > If I do not have any .setDefault bound for simpleInt in > Simple.getDefaultsModule (neither Integer.class or String.class), or I get > > Failed tests: testGetSimpleVal(com.baselayer.device.scratch.SimpleTest): > expected:<1> but was:<12345> > > If I had bound an Integer in Simple.getDefaultsModule with > > OptionalBinder.newOptionalBinder(binder(), Key.get(Integer.class, > Names.named("simpleInt" ))) > .setDefault().toInstance(12345); > > Finally - If I use > > OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, > Names.named("simpleInt" ))) > .setDefault().toInstance("12345"); > > But do NOT overwrite it with setBinding(), then I get the following error > > 1) No implementation for java.lang.Integer annotated with > @com.google.inject.name.Named(value=simpleInt) was bound. > while locating java.lang.Integer annotated with > @com.google.inject.name.Named(value=simpleInt) > for parameter 0 at > com.baselayer.device.scratch.Simple.<init>(Simple.java:20) > at > com.baselayer.device.scratch.SimpleTest$1.configure(SimpleTest.java:30) > (via modules: com.google.inject.util.Modules$OverrideModule -> > com.baselayer.device.scratch.SimpleTest$1) > > > It appears that guice will bind a String to an int with the normal, > non-optional .toInstance, but .setDefault .toInstance refuses to bind a > String to an int with the OptionalBinder. (but .setBinding .toInstance will > set a String to an Integer, as long as a separate specific Integer binding > does not already exist). > > If .setDefault and .setBinding .toInstance had the same semantics as > normal, non-optional .toInstance, I could make my own Names.bindProperties > and use that everywhere instead (with the intent being that if any default > was already set, I would overwrite it) > > However, the only thing I have tried that works is to attempt to bind to > every single instance (ensuring that the exact type has been bound), > something like this for getTestModule() > > private Module getTestModule() { > return new AbstractModule() { > @Override protected void configure() { > bind(Simple.class); > > Properties p = new Properties(); > p.setProperty("simpleInt", "1"); > p.setProperty("simpleStr", "default"); > > //Names.bindProperties(binder(), p); > Binder skippedBinder = binder().skipSources(Names.class); > > for (Enumeration<?> e = p.propertyNames(); > e.hasMoreElements(); ) { > String propertyName = (String) e.nextElement(); > String value = p.getProperty(propertyName); > OptionalBinder.newOptionalBinder(skippedBinder, > Key.get(String.class, > Names.named(propertyName))).setBinding().toInstance(value); > > try { > int i = Integer.parseInt(value); > OptionalBinder.newOptionalBinder(skippedBinder, > Key.get(Integer.class, > Names.named(propertyName))).setBinding().toInstance(i); > } catch (NumberFormatException asdf) { > } > } > } > }; > } > > In order to bind a configuration file's properties to @Named instances, I > need to attempt to convert the String to each possible type if I wish to > load dynamically without some kind of type metadata in the configuration > file as well to know which type I should target the binding to. > > Having outlined all this, a few questions > > 1) Is it feasible for the behaviour of OptionalBinder to allow a > .toInstance to override a .setDefault? If so then things like > Names.bindProperties could (maybe) just work as is. If not, why? (I assume > there is a good reason since .to was explicitly disallowed as an override > for .setDefault in the documentation) > > 2) What is the intended behaviour of Names.bindProperties. Are strings > supposed to be able to inject onto a Integer? Or only inject if a specific > Integer injection doesn't already exist? (Are users supposed to be able to > inject different String and Integer toInstance values for a Named > annotation?) Is this behaviour I am experiencing via .setDefault something > that hasn't been fully thought through, or is there a ruleset for when some > types are supposed to ovverride other types when a more specific mapping > doesn't already exist for the annotation? > Experimenting with Modules.override it seems that normal > non-optional .toInstance starts behaving similarly if the defaults module > binds both an Integer and a String, and the overriding module just binds a > string (As Names.bindProperties). > > 3) Are there any downsides to just OptionalBinding any configuration file > property to a variety of common primitive types to ensure that all default > injection parameters gets overridden by configuration? > > 4) If I just want default values (without the Optional<Foo> support), > should I be using Modules.override instead? > > 5) Is setDefault working as intended, given that is has different > semantics to .setBinding (.setDefault .toInstance String.class will not > inject a String to an Integer in the absence of a Integer injection, but > .setBinding .toInstance will just like normal non-optional .toInstance) > > Thanks for reading if you made it this far in my rocky start to DI in java > :) > > Josh > > -- > You received this message because you are subscribed to the Google Groups > "google-guice" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > To post to this group, send email to [email protected]. > Visit this group at http://groups.google.com/group/google-guice. > To view this discussion on the web visit > https://groups.google.com/d/msgid/google-guice/0c3bb455-d65d-4ed4-bf7d-e84d5f84f7af%40googlegroups.com > <https://groups.google.com/d/msgid/google-guice/0c3bb455-d65d-4ed4-bf7d-e84d5f84f7af%40googlegroups.com?utm_medium=email&utm_source=footer> > . > For more options, visit https://groups.google.com/d/optout. > -- You received this message because you are subscribed to the Google Groups "google-guice" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To post to this group, send email to [email protected]. Visit this group at http://groups.google.com/group/google-guice. To view this discussion on the web visit https://groups.google.com/d/msgid/google-guice/CAD-udUDEqYYBuvNfbiipLbrtEAVoEy19f7KUxmrnvd4mBqxuhw%40mail.gmail.com. For more options, visit https://groups.google.com/d/optout.
