Add support for profile groups

Add support for profile groups so that users can combine a number of
fine-grained profiles into a single logical group.

Closes gh-22522
This commit is contained in:
Madhura Bhave 2020-07-21 23:57:04 -07:00 committed by Phillip Webb
parent 8c6c4fa9fa
commit eee260fc03
3 changed files with 101 additions and 1 deletions

View File

@ -1711,6 +1711,27 @@ For example, when an application with the following properties is run by using t
[[boot-features-profiles-groups]]
=== Profile Groups
Occasionally the profiles that you define and use in your application are too fine-grained and become cumbersome to use.
For example, you might have `proddb` and `prodmq` profiles that you use to enable database and messaging features independently.
To help with this, Spring Boot lets you define profile groups.
A profile group allows you to define a logical name for a related group of profiles.
For example, we can create a `production` group that consists of our `proddb` and `prodmq` profiles.
[source,yaml,indent=0]
----
spring.profiles.group.production:
- proddb
- prodmq
----
Our application can now be started using `--spring.profiles.active=production` to active the `production`, `proddb` and `prodmq` profiles in one hit.
[[boot-features-programmatically-setting-profiles]]
=== Programmatically Setting Profiles
You can programmatically set active profiles by calling `SpringApplication.setAdditionalProfiles(...)` before your application runs.

View File

@ -28,11 +28,15 @@ import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
@ -45,10 +49,15 @@ import org.springframework.util.StringUtils;
*/
public class Profiles implements Iterable<String> {
private static final Bindable<MultiValueMap<String, String>> STRING_STRINGS_MAP = Bindable
.of(ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class));
private static final Set<String> UNSET_ACTIVE = Collections.emptySet();
private static final Set<String> UNSET_DEFAULT = Collections.singleton("default");
private final MultiValueMap<String, String> groups;
private final List<String> activeProfiles;
private final List<String> defaultProfiles;
@ -63,6 +72,7 @@ public class Profiles implements Iterable<String> {
* @param additionalProfiles and additional active profiles
*/
Profiles(Environment environment, Binder binder, Collection<String> additionalProfiles) {
this.groups = binder.bind("spring.profiles.group", STRING_STRINGS_MAP).orElseGet(LinkedMultiValueMap::new);
this.activeProfiles = asUniqueItemList(get(environment, binder, environment::getActiveProfiles,
AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, UNSET_ACTIVE), additionalProfiles);
this.defaultProfiles = asUniqueItemList(get(environment, binder, environment::getDefaultProfiles,
@ -94,7 +104,9 @@ public class Profiles implements Iterable<String> {
asReversedList((!activeProfiles.isEmpty()) ? activeProfiles : defaultProfiles).forEach(stack::push);
Set<String> acceptedProfiles = new LinkedHashSet<>();
while (!stack.isEmpty()) {
acceptedProfiles.add(stack.pop());
String current = stack.pop();
acceptedProfiles.add(current);
asReversedList(this.groups.get(current)).forEach(stack::push);
}
return asUniqueItemList(StringUtils.toStringArray(acceptedProfiles));
}

View File

@ -262,6 +262,17 @@ class ProfilesTests {
assertThat(profiles.isAccepted("x")).isFalse();
}
@Test
void iteratorWithProfileGroups() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("spring.profiles.active", "a,b,c");
environment.setProperty("spring.profiles.group.a", "e,f");
environment.setProperty("spring.profiles.group.e", "x,y");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles).containsExactly("a", "e", "x", "y", "f", "b", "c");
}
@Test
void iteratorWithProfileGroupsAndNoActive() {
MockEnvironment environment = new MockEnvironment();
@ -272,4 +283,60 @@ class ProfilesTests {
assertThat(profiles).containsExactly("default");
}
@Test
void iteratorWithProfileGroupsForDefault() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("spring.profiles.group.default", "e,f");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles).containsExactly("default", "e", "f");
}
@Test
void getAcceptedWithProfileGroups() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("spring.profiles.active", "a,b,c");
environment.setProperty("spring.profiles.group.a", "e,f");
environment.setProperty("spring.profiles.group.e", "x,y");
environment.setDefaultProfiles("g", "h", "i");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getAccepted()).containsExactly("a", "e", "x", "y", "f", "b", "c");
}
@Test
void getAcceptedWhenNoActiveAndDefaultWithGroups() {
MockEnvironment environment = new MockEnvironment();
environment.setDefaultProfiles("d", "e", "f");
environment.setProperty("spring.profiles.group.e", "x,y");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getAccepted()).containsExactly("d", "e", "x", "y", "f");
}
@Test
void isAcceptedWithGroupsReturnsTrue() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("spring.profiles.active", "a,b,c");
environment.setProperty("spring.profiles.group.a", "e,f");
environment.setProperty("spring.profiles.group.e", "x,y");
environment.setDefaultProfiles("g", "h", "i");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.isAccepted("a")).isTrue();
assertThat(profiles.isAccepted("e")).isTrue();
assertThat(profiles.isAccepted("g")).isFalse();
}
@Test
void isAcceptedWhenNoActiveAndDefaultWithGroupsContainsProfileReturnsTrue() {
MockEnvironment environment = new MockEnvironment();
environment.setDefaultProfiles("d", "e", "f");
environment.setProperty("spring.profiles.group.e", "x,y");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.isAccepted("d")).isTrue();
assertThat(profiles.isAccepted("x")).isTrue();
}
}