DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Comprehensive Guide to Property-Based Testing in Go: Principles and Implementation
  • How Does Video Annotation Augment Computer Vision?
  • Role of Data Annotation Services in AI-Powered Manufacturing
  • Annotating Data at Scale in Real Time

Trending

  • Kubeflow: Driving Scalable and Intelligent Machine Learning Systems
  • Contextual AI Integration for Agile Product Teams
  • Scaling DevOps With NGINX Caching: Reducing Latency and Backend Load
  • Power BI Embedded Analytics — Part 2: Power BI Embedded Overview
  1. DZone
  2. Coding
  3. Languages
  4. Flexible configuration with Guice

Flexible configuration with Guice

By 
Cedric Beust user avatar
Cedric Beust
·
Jul. 16, 13 · Interview
Likes (0)
Comment
Save
Tweet
Share
17.5K Views

Join the DZone community and get the full member experience.

Join For Free

There are quite a few configuration libraries available in Java, such as this one available from Apache Commons, and they usually follow a very similar pattern: they parse a variety of configuration files and in the end, give you a Property or Map like structure where you can query your values:

Double double = config.getDouble("number");
Integer integer = config.getInteger("number");

I have always been unsatisfied with this approach for a couple of reasons:

  • A lot of boiler plate to retrieve these parameters.
  • Having to share the whole configuration object even if I only need one parameter from it.
  • It’s very easy to misspell a property and received incorrect values.

A while ago, I was reading the Guice documentation and I came across a paragraph that made me realize that maybe, we could do better. Here is the relevant excerpt:

Guice supports binding annotations that have attribute values. In the rare case that you need such an annotation:

Create the annotation @interface.
Create a class that implements the annotation interface. Follow the guidelines for equals() and hashCode() specified in the Annotation Javadoc. Pass an instance of this to the annotatedWith() binding clause.

I thought that using this technique might be exactly what I needed to create a smarter configuration framework, even though I had different plans than using this trick with the annotatedWith method, as suggested by this paragraph. The relevance of this snippet will become clear later, so let’s start with the goals.

Objectives

I want to:

  • Be able to inject individual configuration values anyhere in my code base and I want this to be type safe. No @Named or other string-based lookup.
  • Have a canonical list of all the properties available to the application, with their full type, default value, documentation and leaving the door open for improvements (e.g. is this option mandatory or optional, detecting when some properties are not used anywhere, deprecation, aliasing, etc…).

I don’t care much about the front end: how these properties get gathered is not relevant to this framework, they can come from XML, JSON, the network, a database, and they can have arbitrarily complex resolution and overriding rules, let’s save this for a future post. The input of this framework is a Map of properties and I take it from there.

By the time we’re done, we will be able to do something like this:

# Some property file
host=foo.com
port=1234

Using these configuration values in your code:

public class A {
@Inject
@Prop(Property.HOST)
private String host;
@Inject
@Prop(Property.PORT)
private Integer port;
// ...
}

Implementation

The definition of the Prop annotation is trivial:

@Retention(RUNTIME)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@BindingAnnotation
public @interface Prop {
Property value();
}

Property is an enum that captures all the information necessary for all your properties. In our case:

public enum Property {
HOST("host",
"The host name",
new TypeLiteral<String>() {},
"foo.com"),
PORT("port",
"The port",
new TypeLiteral<Integer>() {},
1234);
}

This enum contains the string name of the property, a description, its default value and its type. Note that this type is a TypeLiteral, so we can even offer properties that have generic types that would otherwise be erased, a trick that comes in handy to inject caches or other generic collections. Obviously, you can have additional parameters as you see fit (e.g. “boolean deprecated“).

The next step is to tie all the properties that we parsed as input — we’ll use a Map called "allProps"— into our module so that Guice knows how to inject them.

In order to do this, we iterate all these properties and bind them to their own provider. Because we are using typed names, note the use of Key.get from the Guice API, which lets us specifically target each property with a specific annotation:


for (Property prop : Property.values()) {
Object value = PropertyConverters.getValue(prop.getType(), prop, allProps.asMap());
binder.bind(Key.get(prop.getType(), new PropImpl(prop)))
.toProvider(new PropertyProvider(prop, value));
}

There are three classes in this piece of code that I haven’t explained yet. The first one isPropertyConverters, which simply reads the string version of the property and converts it to a Java type. The second one is PropertyProvider, a trivial Guice provider:

public class PropertyProvider<t> implements Provider<t> {
private final T value;
private final Property property;
public PropertyProvider(Property property, T value) {
this.property = property;
this.value = value;
}
@Override
public T get() {
return value;
}
}

PropImpl is more tricky and also the one thing that has always prevented me from implementing such a framework, until I came across this obscure tidbit of the Guice documentation quoted above. In order to understand the necessity of its existence, we need to understand how Guice’s Key.get() works. Guice uses this class to translate a type into a unique key that it can use to inject the correct value. The important part here is to notice that not only does this method work with both Class andTypeLiteral (which we are using), but it can also be given a specific annotation. This annotation can be @Named, which I’m not a big fan of because it’s a string, so susceptible to typos, or a real annotation, which is what we want. However, annotations are special beasts in Java and you can’t get an instance of them just like that.

This is where the trick mentioned at the top of this article comes into play: Java actually allows you to implement an annotation with a regular class. The implementation turns out to be fairly trivial, the difficulty was realizing that this was possible at all.

Now that we have all this in place, let’s back track and dissect how the magic happens:

@Inject
@Prop(Property.HOST)
private String host;

When Guice encounters this injection point, it looks into its binders and it finds multiple bindings forStrings. However, because they have all been bound with a Key, the key is actually a pair: (String, a Prop). In this case, it will look up the pair String, Property.HOST and it will find a provider there. This provider was instantiated with the value found in the property file, so it knows what value to return.

Generalizing

Once I had the basic logic in place, I wondered if I could turn this mini framework into a library so that others could use it. The only missing piece would be to allow the specification of a more general Propannotation. In the example above, this annotation has a value of type Property, which is specific to my application:


@Retention(RUNTIME)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@BindingAnnotation
public @interface Prop {
Property value();
}

In order to make this more general, I need to make this attribute return an enum instead of my own:

@Retention(RUNTIME)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@BindingAnnotation
public @interface Prop {
Enum value();
}

Unfortunately, this is not legal Java, because according to the JLS section 8.9, Enum and its generic variants are not enum types, something that Josh Bloch confirmed, to my consternation.

Therefore, this cannot be turned into a library, so if you are interested in using it in your project, you will have to copy the source and make a few modifications to adjust it to your needs, starting by havingProp#value have the type of the enum that captures your configuration.

You can find a small proof of concept here, which I hope you’ll find useful.

Note: this is a copy of the article I posted on our work blog.

Property (programming) Annotation

Published at DZone with permission of Cedric Beust, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Comprehensive Guide to Property-Based Testing in Go: Principles and Implementation
  • How Does Video Annotation Augment Computer Vision?
  • Role of Data Annotation Services in AI-Powered Manufacturing
  • Annotating Data at Scale in Real Time

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!