Environment
Environment is the application's configuration as code sees it: every property from every source, and the active profiles.
Why
Most beans never need it. @Value and @ConfigurationProperties read properties for them, and @Profile switches beans on profiles. Take the Environment when:
- the key is only known at run time (
shop.banner.$language); - a decision in code depends on the active profiles;
- text from elsewhere contains
${...}placeholders to fill.
The environment is a bean. A constructor parameter of type Environment receives it; EnvironmentAware works too. Placeholders in values are resolved and relaxed names are found.
Environment is read-only. ConfigurableEnvironment is the side that changes it: property sources, profiles, conversion. It is for startup code, such as an EnvironmentPostProcessor, and for tests.
Example
import 'package:spring_core_dart/spring_core_dart.dart';
@Service()
class FeatureFlags {
FeatureFlags(this.env);
final Environment env;
// Converted to the requested type by the ConversionService.
bool get betaOn => env.getPropertyAs<bool>('shop.beta', defaultValue: false)!;
int get maxItems => env.getPropertyAs<int>('shop.max-basket-size') ?? 10;
Duration? get timeout => env.getPropertyAs<Duration>('shop.checkout-timeout');
// The key depends on the language, so @Value cannot name it.
String banner(String language) =>
env.getProperty('shop.banner.$language') ??
env.resolvePlaceholders(r'Welcome to ${shop.title}');
// Throws MissingPropertyException when no source has the key.
String get title => env.getRequiredProperty('shop.title');
// Tells "set to empty" from "not set".
bool get hasMotto => env.containsProperty('shop.motto');
bool get showDebugInfo => env.matchesProfiles(['dev & !test']);
}
Reading the configuration without a context, in a command-line tool or a test:
Future<void> main(List<String> args) async {
final environment = StandardEnvironment()
..propertySources.addFirst(SimpleCommandLinePropertySource(args));
// Reads application.* and the profile files, as SpringApplication does.
await ConfigDataEnvironment(environment).processAndApply();
final port = environment.getPropertyAs<int>('server.port');
}
A test can replace the OS environment variables:
final environment = StandardEnvironment(
systemEnvironment: {'SPRING_PROFILES_ACTIVE': 'test'},
);
environment.activeProfiles; // ['test']
Members
Environment (and PropertyResolver, which it implements):
| Member | Meaning |
|---|---|
getProperty(key, {defaultValue}) |
The value as text, placeholders resolved, or defaultValue (null unless given). |
getPropertyAs<T>(key, {defaultValue}) |
The value converted to T: int, bool, Duration, List<String>, enums... defaultValue also applies when the value is blank. Throws a ConversionException when it cannot be converted. |
getRequiredProperty(key) |
The value as text. Throws MissingPropertyException when no source has it. |
getRequiredPropertyAs<T>(key) |
The value converted to T. Throws when it is missing. |
containsProperty(key) |
Whether some source has the key, even with an empty value. |
resolvePlaceholders(text) |
Replaces ${...} in text. An unresolvable placeholder without a default stays as it is. |
resolveRequiredPlaceholders(text) |
The same, but throws PlaceholderResolutionException for an unresolvable placeholder. |
activeProfiles |
The active profiles, such as ['prod', 'eu']. Empty when none is active. |
defaultProfiles |
The profiles used while none is active: ['default'] unless spring.profiles.default says otherwise. |
matchesProfiles(expressions) |
Whether any profile expression holds: ['dev & (eu | us)']. |
acceptsProfiles(profiles) |
The same with a Profiles built once: Profiles.of(['prod', 'staging']). |
ConfigurableEnvironment adds:
| Member | Meaning |
|---|---|
propertySources |
The ordered MutablePropertySources. For each key, the first source that has it wins. See Property sources. |
setActiveProfiles(profiles) |
Replaces the active profiles. spring.profiles.active is then no longer read. |
addActiveProfile(profile) |
Adds a profile and keeps the others, including those from spring.profiles.active. |
setDefaultProfiles(profiles) |
Replaces ['default']. |
systemEnvironment |
The environment variables of the process, read-only. Empty on the web. |
merge(parent) |
Takes over a parent's sources (below its own) and profiles. Used for child contexts. |
conversionService |
The service used by getPropertyAs and by binding. |
setRequiredProperties(keys), validateRequiredProperties() |
Fail early, listing every missing key. |
placeholderPrefix, placeholderSuffix, valueSeparator, escapeCharacter |
The placeholder syntax: ${, }, : and \ by default. |
ignoreUnresolvableNestedPlaceholders |
Whether an unresolvable placeholder inside a value stays as text instead of throwing. |
StandardEnvironment is the implementation used everywhere. It starts with one property source, the environment variables. SpringApplication adds the command line, --dart-define values, the config files and the defaults.
Changing it at startup
Beans should not change the environment: other beans may already have read it. To add a source or a profile, use an EnvironmentPostProcessor. It runs while the environment is prepared, before any bean exists.
class RegionProfile implements EnvironmentPostProcessor {
@override
void postProcessEnvironment(
ConfigurableEnvironment environment,
SpringApplication application,
) {
// Machines in the EU data centre run with the `eu` profile.
if (environment.getProperty('REGION') == 'eu') {
environment.addActiveProfile('eu');
}
}
}
final app = SpringApplication(
[appBeans],
environmentPostProcessors: [RegionProfile()],
);
A post-processor whose order is smaller than EnvironmentPostProcessor.configDataOrder (implement Ordered) runs before the config files are read, so its profiles and sources decide which files are loaded. The others run after.
In Spring (Java)
@Service
class FeatureFlags {
private final Environment env;
FeatureFlags(Environment env) { this.env = env; }
boolean betaOn() { return env.getProperty("shop.beta", Boolean.class, false); }
boolean showDebugInfo() { return env.matchesProfiles("dev & !test"); }
}
Differences that matter:
- Typed access is
getPropertyAs<T>(key)instead ofgetProperty(key, Class). Dart has no overloading. matchesProfilestakes a list of expressions, not varargs.- There are no JVM system properties.
- Post-processors are passed to
SpringApplication(environmentPostProcessors: [...]), not listed inMETA-INF/spring.factories.