Envy provides easy, uniform access to environment variables and system properties, which helps you build twelve-factor apps.
Say you need to access the environment variables API_KEY
and API_SECRET
in your program.
First, you define an interface with getter methods named after the parameters you want to bring in:
interface MyConfig {
String getApiKey();
String getApiSecret();
}
Envy can then instantiate your configuration interface like this:
import com.statemachinesystems.envy.Envy;
...
MyConfig config = Envy.configure(MyConfig.class);
Now, calling config.getApiKey()
will return the value of the API_KEY
environment variable, and
config.getApiSecret()
will return the value of API_SECRET
.
This also works for JVM system properties, using a lower-case dotted naming convention,
which would be api.key
and api.secret
in the above example. When an environment variable and a
system property are both defined with equivalent names, the system property takes precedence.
Also, you don't have to use bean-style method names - the following version would work in exactly the same way:
interface MyConfig {
String apiKey();
String apiSecret();
}
Envy's in the Maven Central repo, so just add the latest version as a dependency in your Maven/SBT/Gradle/whatever build.
Envy treats all parameters as mandatory, but you can provide a default value using the @Default
annotation:
import com.statemachinesystems.envy.Default;
interface ServerConfig {
@Default("80")
int getHttpPort();
}
Sometimes a parameter type has no meaningful default value, or you need to test for its absence.
Envy supports Java 8's Optional
, Scala's Option
and Guava's Optional
types.
interface FooConfig {
Optional<URL> getUrl();
}
You can force Envy to allow null values using the @Nullable
annotation:
import com.statemachinesystems.envy.Nullable;
interface FooConfig {
@Nullable
URL nullableUrl();
}
By default, Envy provides a toString()
method that includes all configured values.
To mask out sensitive values such as passwords, use the @Sensitive
annotation:
import com.statemachinesystems.envy.Sensitive;
interface Credentials {
String username();
@Sensitive String password();
}
Long and/or awkward names can be overridden using the @Name
annotation:
import com.statemachinesystems.envy.Name;
interface BarConfig {
@Name("com.foo.extremely.long.property.name.for.thing")
String getThing();
}
To apply a prefix to all names in a configuration interface, use the @Prefix
annotation:
import com.statemachinesystems.envy.Prefix;
@Prefix("baz.config")
interface BazConfig {
/**
* Configured by BAZ_CONFIG_HTTP_PORT
*/
int getHttpPort();
}
Interface inheritance is supported, so you can factor out common parameters in more complex configurations.
Annotations on methods (@Name
, @Nullable
and @Default
) are inherited,
but can be overridden (or removed entirely) by redeclaring the method in a sub-interface. @Prefix
annotations
are not inherited.
Configuration interfaces can be nested, which allows reuse of repeated structures. Here's an example using both inheritance and nesting:
interface Credentials {
String username();
@Sensitive String password();
}
interface ConnectionConfig extends Credentials {
java.net.InetSocketAddress address();
}
interface AppConfig {
/**
* Configured by DATABASE_ADDRESS, DATABASE_USERNAME and DATABASE_PASSWORD
*/
ConnectionConfig database();
/**
* Configured by MESSAGE_BROKER_ADDRESS, MESSAGE_BROKER_USERNAME and MESSAGE_BROKER_PASSWORD
*/
ConnectionConfig messageBroker();
}
Envy will do the following type conversions for you:
- Strings (no conversion needed)
- Numbers (
int
/Integer
,long
/Long
,byte
/Byte
,short
/Short
,float
/Float
,double
/Double
,java.math.BigDecimal
,java.math.BigInteger
) - Booleans (true/false, yes/no, y/n, on/off)
- Characters (
char
/Character
) - Enums
- Arrays, comma-separated
- Anything with a constructor that takes a single
String
argument java.io.File
java.lang.Class
java.net.URL
,java.net.URI
java.net.InetAddress
,Inet4Address
,Inet6Address
,InetSocketAddress
java.util.regex.Pattern
java.util.UUID
java.time.Duration
e.g. "100ms", "30 seconds", "1000" (defaults to millis)java.time.Period
e.g. "1d", "2 weeks", "3 months", "10" (defaults to days)
To parse a custom type, implement the ValueParser
interface:
import com.statemachinesystems.envy.ValueParser;
public class MyCustomTypeParser implements ValueParser<MyCustomType> {
@Override
public MyCustomType parseValue(String value) {
...
}
@Override
public Class<MyCustomType> getValueClass() {
return MyCustomType.class;
}
}
Then, when instantiating your configuration, pass along an instance of your parser like this:
MyConfig config = Envy.configure(MyConfig.class, new MyCustomTypeParser());
© 2014-2018 State Machine Systems Ltd. Apache Licence, Version 2.0