Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Google Guice Properties Management

I would like to create a proper properties management strategy in a java webapp that relays on google guice as a DI framework.

I would like to have a mechanism answering the following 3 requirements:

  • I would like to be able to inject properties using guice (@Named)
  • I would like to be able to access properties in a static way
  • The mechanism should support prioritization of properties, meaning that a property can be wrapped in the deployed war with a certain value but it can also be redundant in the target system level or local file system (of the target machine I deploy on), in such a case the value in the war will be overridden by the value that exists in the target machine.

I believe this is a standard requirement. Now, using guice standard binder I can easily get the first requirement but not the other two. To get the other two I created my own class that does the following:

  • Wraps and exposes the binding methods of guice (those that binds properties) For example:

public static void bindString(AnnotatedBindingBuilder<String> binder, String property, String defaultValue) { binder.annotatedWith(Names.named(property)).toInstance(getProperty(property, defaultValue)); }

Where the getProperty method knows how to handle my properties (get the value from the war or system level) and exposes the properties statically as well.

So basically as long as I'm using this utility that I created for properties bindings I'm good, it covers all my requirements but once I use the standard guice bindings I'm losing the second and third requirement.

Is there a way to override guice bindings and get all those 3 requirements?

Once I had the same challange in a spring based app and was pretty easy. I implemented ApplicationContextInitializer with the following method:

@Override public void initialize(ConfigurableWebApplicationContext ctx) { PropertySource<Map<String, Object>> localProps = null; try { localProps = new ResourcePropertySource(new ClassPathResource(LOCAL_PROPERTIES_FILE_NAME)); } catch (IOException e) { LOG.fatal("Could not load local properties from classpath " + LOCAL_PROPERTIES_FILE_NAME); return; } LOG.info("Loaded configuration from classpath local file " + LOCAL_PROPERTIES_FILE_NAME); ctx.getEnvironment().getPropertySources().addFirst(localProps); }

so this gave me a way to add local properties with highest priority to my Environment. In case of overlap with war properties the local ones had higher priority. In addition I exposed my Environment statically so I has static access to my properties (for services that are not managed by the container, legacy mostly).

How can I achieve this with guice?

like image 893
forhas Avatar asked Nov 03 '15 10:11

forhas


People also ask

Why we use Google Guice?

Beyond Dependency Injection, the benefits of using Google Guice is: Guice has a very clean implementation of constructor Injection. As you can see from the example you just add @Inject annotation constructor. Guice also has setter Injection using the same annotation.

Is dagger better than Guice?

To my knowledge, Dagger does generate code, while Guice and Spring rely on runtime processing, thus Dagger works faster, but requires more work on programmer side. Because of performance edge it's good for mobile (Android) development.

What are modules in Guice?

The Module is the core building block for an Injector which is Guice's object-graph builder. First step is to create an injector and then we can use the injector to get the objects.


1 Answers

Unfortunately, I don't think that you are going to find anything that gives you a truly clean and satisfying implementation. Especially, I don't think that you will find anything that gives you exactly what you want without implementing at least portions of it yourself.

If I had those needs, I would make sure that my injector is created in a central InjectorFactory. If you require a large number of parameters from outside to create your injector, I would simply create it once at the very beginning of my application and then cache the injector into a static final field. This would make it available to a static method. I would bind my "fall-back" property loading to an explicit provider. That way, instead of using the standard Names.bindProperties(...) method, I would bind it directly to a Provider. This provider then implements the logic that is necessary to perform the fallback or to merge multiple property files. Having the injector cached to a static field means that I can call a static method to access properties from a global-context outside of my injected classes.

Using your own provider seems initially unpleasant, but can provide some additional benefits. For starters, you can implement your fallback strategy exactly how you want. Additionally, you can add additional behaviors such as auto-reloading your property files, etc (not shown in my code sample).

public class InjectorFactory {
    private static Injector injector = null;
    public static synchronized Injector getOrCreateInjector() {
        if(injector == null) {
            injector = Guice.createInjector(new AbstractModule() {
                @Override
                protected void configure() {
                    Properties properties1 = createProperties("file1.properties");
                    Properties properties2 = createProperties("file2.properties");
                    Set<Object> propertyNames = new HashSet<Object>();
                    propertyNames.addAll(properties1.keySet());
                    propertyNames.addAll(properties2.keySet());

                    for (Object object : propertyNames) {
                        String propertyName = (String) object;
                        bind(String.class).annotatedWith(Names.named(propertyName)).toProvider(new StringProvider(properties1, properties2, propertyName));
                    }
                }

                private Properties createProperties(String propertyFileName) {
                    try {
                        InputStream stream = InjectorFactory.class.getResourceAsStream(propertyFileName);
                        try {
                            Properties properties = new Properties();
                            properties.load(stream);
                            return properties;
                        } finally {
                            stream.close();
                        }

                    } catch (IOException exception) {
                        throw new RuntimeException("Could not load properties file");
                    }
                }
            });
        }
        return injector;
    }

    public static String getProperty(String propertyName) {
        return getOrCreateInjector().getInstance(Key.get(String.class, Names.named(propertyName)));
    }

}

Given the above code and file1.properties:

property1=Property1Value
property2=Property2Value

And file.properties:

property2=IncorrectProperty2Value
property3=Property3Value

with the provider

public class StringProvider implements Provider<String> {
    private Properties properties1;
    private Properties properties2;
    private String propertyName;
    public StringProvider(Properties properties1, Properties properties2,
            String propertyName) {
        this.properties1 = properties1;
        this.properties2 = properties2;
        this.propertyName = propertyName;
    }
    public String get() {
        if(properties1.containsKey(propertyName)) {
            return properties1.getProperty(propertyName);
        }
        return properties2.getProperty(propertyName);
    }
}

The following usage:

public class InjectorFactoryTest {
    public static void main(String ... parameters) {
        System.out.println(InjectorFactory.getProperty("property1"));
        System.out.println(InjectorFactory.getProperty("property2"));
        System.out.println(InjectorFactory.getProperty("property3"));
    }
}

Outputs:

Property1Value
Property2Value
Property3Value
like image 103
Nathan Avatar answered Oct 02 '22 02:10

Nathan