Guice, Settings, and Boilerplate Code

I have an application that uses Guice and reads in some configuration settings from a configuration file. I load it like this:

@Provides @Singleton Settings provideSettings() { // code that loads the settings } 

Some objects need certain settings; other objects need other settings. It seems to me it makes sense to pass these things in the constructor, but then I get a lot of code templates, for example:

 @Provides @Named("integerValueSetting1") int provideIntegerValueSetting1(Settings settings) { return settings.getInteger("integerValueSetting1"); } 

And I have to make such an @Provides method for each type of installation, plus I need to annotate the corresponding settings in the constructor. For instance:

 @Inject public MyClass(@Assisted String objectName, @Named("integerValueSetting1") myValue) { // blah blah constructor } 

That doesn't seem to save me! Even worse would be if I created a custom annotation class for each setting. There must be a better way, right?

One solution might be to pass the Settings object directly, but this violates best practices: only embed direct dependencies ...

+4
source share
2 answers

If you have a ton of settings and want to avoid creating a new anchor annotation for each of them, you can try to put them in an enumeration and use this enumeration in a general anchor annotation. This may be a bit of a difficult decision, but it can also save a pattern that you are trying to avoid.

This way you can match object references (IDE-friendly) instead of strings (slow and fragile) and create only one anchor annotation.

 public enum Config { DB_NAME("db_name"), DB_HOST("db_host_name_specified_in_file"), SOME_NUMBER("some_number"), ; private final String propertyName; private Config(String propertyName) { this.propertyName = propertyName; } public String getPropertyName() { return propertyName; } public InjectConfig annotation() { // Create an implementation of InjectConfig for ease of binding. return new InjectConfig() { @Override public Class<? extends Annotation> annotationType() { return InjectConfig.class; } @Override public Config value() { return Config.this; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } else if (!(obj instanceof InjectConfig)) { return false; } return value() == ((InjectConfig) obj).value(); } /** @see Annotation#hashCode */ @Override public int hashCode() { return (127 * "value".hashCode()) ^ value().hashCode(); } }; } @Retention(RetentionPolicy.RUNTIME) @BindingAnnotation public static @interface InjectConfig { Config value(); } } 

Now you can iterate and bind each in a loop:

 public class YourModule extend AbstractModule { @Override public void configure() { // You can get a Provider in a Module as long as you // don't call get() before the injector exists. Provider<Settings> settingsProvider = binder().getProvider(Settings.class); for (Config config : Config.values()) { String propertyName = config.getPropertyName(); // Guice TypeConverter will convert Strings to the right type. bind(String.class).annotatedWith(config.annotation()).toProvider( new GetValueFromSettingsProvider(settingsProvider, propertyName)); } } } 

And enter only what you need directly:

 /** Your constructor */ YourClass(@InjectConfig(DB_USER) String user, @InjectConfig(SOME_NUMBER) int number) { } 

I did not have the opportunity to verify this, but as far as I know, it should work. Given your specific usage settings, you may need to massage GetValueFromSettingsProvider , which you are writing, or write an override method getConfigValueFromSettings in the listing. Remember, however, that somehow you still need to store the tuple (key name, property name in the file, property type), and it seems that Enum is the best way to manage this programmatically.

+2
source

Browse the Tadeon , especially its functionality.

with the following .properties file:

 foo=foo list=1, 2, 3 

and component:

 public class PropertyInjectedComponent { private final String foo; private final List<String> list; @Inject public PropertyInjectedComponent( @Named("foo") String foo, @Named("list") List<String> list) { this.foo = foo; this.list = list; } ... } 

you can simply configure the graphics module to check properties for named values:

 Injector injector = Guice.createInjector(new AbstractModule() { protected void configure() { GuiceConfigurations.bindProperties(binder(), new File("src/test/data"), "conf1.properties"); } }); 

here are test files.

you can either replace the Settings class with apache Configuration , JDK Properties , or just Map<String, String> or explore the Tadeon source to create a similar solution for your Settings class.

0
source

All Articles