Thanks, that helped me ramp up on the guice ecosystem and configuration. However - the fact that setDefault has different binding coercion semantics to setBinding smells like a bug to me. Is there any reason that its current behaviour is the correct one? If not I might file a bug report.
On Saturday, January 31, 2015 at 4:42:38 AM UTC-7, Laszlo Ferenczi wrote: > > 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] > <javascript:>> 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] <javascript:>. >> To post to this group, send email to [email protected] >> <javascript:>. >> 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/a88525ad-a952-4364-9e30-d6bce7ea173a%40googlegroups.com. For more options, visit https://groups.google.com/d/optout.
