diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Profile.java b/spring-context/src/main/java/org/springframework/context/annotation/Profile.java index 35ab5d75818..d6d0e0e01a6 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Profile.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Profile.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import java.lang.annotation.Target; import org.springframework.core.env.AbstractEnvironment; import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Profiles; /** * Indicates that a component is eligible for registration when one or more @@ -47,9 +48,14 @@ import org.springframework.core.env.ConfigurableEnvironment; * *

If a {@code @Configuration} class is marked with {@code @Profile}, all of the * {@code @Bean} methods and {@link Import @Import} annotations associated with that class - * will be bypassed unless one or more of the specified profiles are active. This is - * analogous to the behavior in Spring XML: if the {@code profile} attribute of the - * {@code beans} element is supplied e.g., {@code }, the + * will be bypassed unless one or more of the specified profiles are active. A profile + * string may contains a simple profile name (for example {@code "p1"}) or a profile + * expression. A profile expression allows for more complicated profile logic to be + * expressed, for example {@code "p1 & p2"}. See {@link Profiles#of(String...)} for more + * details about supported formats. + * + *

This is analogous to the behavior in Spring XML: if the {@code profile} attribute of + * the {@code beans} element is supplied e.g., {@code }, the * {@code beans} element will not be parsed unless at least profile 'p1' or 'p2' has been * activated. Likewise, if a {@code @Component} or {@code @Configuration} class is marked * with {@code @Profile({"p1", "p2"})}, that class will not be registered or processed unless diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ProfileCondition.java b/spring-context/src/main/java/org/springframework/context/annotation/ProfileCondition.java index 36f9be46cd1..2f830706cbd 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ProfileCondition.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ProfileCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.context.annotation; +import org.springframework.core.env.Profiles; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.util.MultiValueMap; @@ -35,7 +36,7 @@ class ProfileCondition implements Condition { MultiValueMap attrs = metadata.getAllAnnotationAttributes(Profile.class.getName()); if (attrs != null) { for (Object value : attrs.get("value")) { - if (context.getEnvironment().acceptsProfiles((String[]) value)) { + if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) { return true; } } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java index 0ff18c2e420..9f73d776a95 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,6 +54,7 @@ import org.springframework.context.annotation.componentscan.simple.SimpleCompone import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; @@ -193,7 +194,7 @@ public class ComponentScanAnnotationIntegrationTests { @Test public void withAwareTypeFilter() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanWithAwareTypeFilter.class); - assertTrue(ctx.getEnvironment().acceptsProfiles("the-filter-ran")); + assertTrue(ctx.getEnvironment().acceptsProfiles(Profiles.of("the-filter-ran"))); } @Test diff --git a/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java b/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java index 94d82e2db75..137f2b5d0ac 100644 --- a/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java +++ b/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java @@ -322,6 +322,23 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { } } + @Override + @Deprecated + public boolean acceptsProfiles(String... profiles) { + Assert.notEmpty(profiles, "Must specify at least one profile"); + for (String profile : profiles) { + if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') { + if (!isProfileActive(profile.substring(1))) { + return true; + } + } + else if (isProfileActive(profile)) { + return true; + } + } + return false; + } + @Override public boolean acceptsProfiles(Profiles profiles) { Assert.notNull(profiles, "Profiles must not be null"); diff --git a/spring-core/src/main/java/org/springframework/core/env/Environment.java b/spring-core/src/main/java/org/springframework/core/env/Environment.java index d335aa9db58..34afc12ef49 100644 --- a/spring-core/src/main/java/org/springframework/core/env/Environment.java +++ b/spring-core/src/main/java/org/springframework/core/env/Environment.java @@ -97,18 +97,22 @@ public interface Environment extends PropertyResolver { /** * Return whether one or more of the given profiles is active or, in the case of no * explicit active profiles, whether one or more of the given profiles is included in - * the set of default profiles. Profiles can simple indicators ('{@code p1}', - * {@code !p1}) or more complex boolean expressions. See {@link Profiles#of(String...)} - * for syntax details. + * the set of default profiles. If a profile begins with '!' the logic is inverted, + * i.e. the method will return true if the given profile is not active. + * For example,

env.acceptsProfiles("p1", "!p2")
will + * return {@code true} if profile 'p1' is active or 'p2' is not active. + * @throws IllegalArgumentException if called with zero arguments + * or if any profile is {@code null}, empty or whitespace-only * @see #getActiveProfiles * @see #getDefaultProfiles + * @see #acceptsProfiles(Profiles) + * @deprecated as of 5.1 in favor of {@link #acceptsProfiles(Profiles)} */ - default boolean acceptsProfiles(String... profiles) { - return acceptsProfiles(Profiles.of(profiles)); - } + @Deprecated + boolean acceptsProfiles(String... profiles); /** - * Returns whether the active profiles match the given {@link Profiles} set. + * Return whether the active profiles match the given {@link Profiles} predicate. */ boolean acceptsProfiles(Profiles profiles); diff --git a/spring-core/src/main/java/org/springframework/core/env/Profiles.java b/spring-core/src/main/java/org/springframework/core/env/Profiles.java index dd09d7b5049..3da61100408 100644 --- a/spring-core/src/main/java/org/springframework/core/env/Profiles.java +++ b/spring-core/src/main/java/org/springframework/core/env/Profiles.java @@ -16,30 +16,32 @@ package org.springframework.core.env; +import java.util.function.Predicate; + /** - * A set of profiles that may be {@link Environment#acceptsProfiles(Profiles) accepted} by + * Profile predicate that may be {@link Environment#acceptsProfiles(Profiles) accepted} by * an {@link Environment}. *

* May be implemented directly or, more usually, created using the {@link #of(String...) * of(...)} factory method. * * @author Phillip Webb - * @since 5.0 - * @see #of(String...) + * @since 5.1 */ @FunctionalInterface public interface Profiles { /** - * Test if this profile set matches against given active profiles. + * Test if this profile predicate matches against given active profiles. + * @param activeProfiles test whether a given profile is currently active */ - boolean matches(ActiveProfiles activeProfiles); + boolean matches(Predicate activeProfiles); /** * Return a new {@link Profiles} instance that checks for matches against the given * profile strings. The returned instance will - * {@link Profiles#matches(ActiveProfiles) matches} if any one of the given profile - * strings match. + * {@link Profiles#matches(Predicate)} match} if any one of the given profile strings + * match. *

* A profile string may contains a simple profile name (for example * {@code "production"}) or a profile expression. A profile expression allows for more @@ -59,18 +61,8 @@ public interface Profiles { * @param profiles the profiles to include * @return a new {@link Profiles} instance */ - public static Profiles of(String... profiles) { + static Profiles of(String... profiles) { return ProfilesParser.parse(profiles); } - /** - * The current set of active profiles. - */ - interface ActiveProfiles { - - /** - * Tests if given profile is currently active. - */ - boolean contains(String profile); - } } diff --git a/spring-core/src/main/java/org/springframework/core/env/ProfilesParser.java b/spring-core/src/main/java/org/springframework/core/env/ProfilesParser.java index bb1bd857f9c..1a5d83697fe 100644 --- a/spring-core/src/main/java/org/springframework/core/env/ProfilesParser.java +++ b/spring-core/src/main/java/org/springframework/core/env/ProfilesParser.java @@ -19,10 +19,10 @@ package org.springframework.core.env; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.StringTokenizer; import java.util.function.Predicate; -import org.springframework.core.env.Profiles.ActiveProfiles; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -30,7 +30,6 @@ import org.springframework.util.StringUtils; * Internal parser used by {@link Profiles#of}. * * @author Phillip Webb - * @since 5.0 */ class ProfilesParser { @@ -55,7 +54,7 @@ class ProfilesParser { Operator operator = null; while (tokens.hasMoreTokens()) { String token = tokens.nextToken().trim(); - if(token.isEmpty()) { + if (token.isEmpty()) { continue; } switch (token) { @@ -86,7 +85,8 @@ class ProfilesParser { return merge(expression, elements, operator); } - private static Profiles merge(String expression, List elements, Operator operator) { + private static Profiles merge(String expression, List elements, + Operator operator) { assertWellFormed(expression, !elements.isEmpty()); if (elements.size() == 1) { return elements.get(0); @@ -115,16 +115,17 @@ class ProfilesParser { } private static Profiles equals(String profile) { - return (activeProfile) -> activeProfile.contains(profile); + return (activeProfile) -> activeProfile.test(profile); } - private static Predicate isMatch(ActiveProfiles activeProfile) { + private static Predicate isMatch(Predicate activeProfile) { return (profiles) -> profiles.matches(activeProfile); } enum Operator { - AND, OR - }; + AND, + OR + } private static class ParsedProfiles implements Profiles { @@ -138,7 +139,7 @@ class ProfilesParser { } @Override - public boolean matches(ActiveProfiles activeProfiles) { + public boolean matches(Predicate activeProfiles) { for (Profiles candidate : this.parsed) { if (candidate.matches(activeProfiles)) { return true; @@ -149,7 +150,9 @@ class ProfilesParser { @Override public String toString() { - return StringUtils.arrayToCommaDelimitedString(this.expressions); + return StringUtils.arrayToDelimitedString(this.expressions, " or "); } + } + } diff --git a/spring-core/src/test/java/org/springframework/core/env/CustomEnvironmentTests.java b/spring-core/src/test/java/org/springframework/core/env/CustomEnvironmentTests.java index 6ac982a0b18..e7081b1e72c 100644 --- a/spring-core/src/test/java/org/springframework/core/env/CustomEnvironmentTests.java +++ b/spring-core/src/test/java/org/springframework/core/env/CustomEnvironmentTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,7 @@ public class CustomEnvironmentTests { @Test public void control() { Environment env = new AbstractEnvironment() { }; - assertThat(env.acceptsProfiles(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME), is(true)); + assertThat(env.acceptsProfiles(defaultProfile()), is(true)); } @Test @@ -51,7 +51,7 @@ public class CustomEnvironmentTests { } Environment env = new CustomEnvironment(); - assertThat(env.acceptsProfiles(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME), is(false)); + assertThat(env.acceptsProfiles(defaultProfile()), is(false)); } @Test @@ -64,8 +64,8 @@ public class CustomEnvironmentTests { } Environment env = new CustomEnvironment(); - assertThat(env.acceptsProfiles(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME), is(false)); - assertThat(env.acceptsProfiles("rd1"), is(true)); + assertThat(env.acceptsProfiles(defaultProfile()), is(false)); + assertThat(env.acceptsProfiles(Profiles.of("rd1")), is(true)); } @Test @@ -79,28 +79,32 @@ public class CustomEnvironmentTests { } ConfigurableEnvironment env = new CustomEnvironment(); - assertThat(env.acceptsProfiles(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME), is(false)); - assertThat(env.acceptsProfiles("rd1", "rd2"), is(true)); + assertThat(env.acceptsProfiles(defaultProfile()), is(false)); + assertThat(env.acceptsProfiles(Profiles.of("rd1 | rd2")), is(true)); // finally, issue additional assertions to cover all combinations of calling these // methods, however unlikely. env.setDefaultProfiles("d1"); - assertThat(env.acceptsProfiles("rd1", "rd2"), is(false)); - assertThat(env.acceptsProfiles("d1"), is(true)); + assertThat(env.acceptsProfiles(Profiles.of("rd1 | rd2")), is(false)); + assertThat(env.acceptsProfiles(Profiles.of("d1")), is(true)); env.setActiveProfiles("a1", "a2"); - assertThat(env.acceptsProfiles("d1"), is(false)); - assertThat(env.acceptsProfiles("a1", "a2"), is(true)); + assertThat(env.acceptsProfiles(Profiles.of("d1")), is(false)); + assertThat(env.acceptsProfiles(Profiles.of("a1 | a2")), is(true)); env.setActiveProfiles(); - assertThat(env.acceptsProfiles("d1"), is(true)); - assertThat(env.acceptsProfiles("a1", "a2"), is(false)); + assertThat(env.acceptsProfiles(Profiles.of("d1")), is(true)); + assertThat(env.acceptsProfiles(Profiles.of("a1 | a2")), is(false)); env.setDefaultProfiles(); - assertThat(env.acceptsProfiles(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME), is(false)); - assertThat(env.acceptsProfiles("rd1", "rd2"), is(false)); - assertThat(env.acceptsProfiles("d1"), is(false)); - assertThat(env.acceptsProfiles("a1", "a2"), is(false)); + assertThat(env.acceptsProfiles(defaultProfile()), is(false)); + assertThat(env.acceptsProfiles(Profiles.of("rd1 | rd2")), is(false)); + assertThat(env.acceptsProfiles(Profiles.of("d1")), is(false)); + assertThat(env.acceptsProfiles(Profiles.of("a1 | a2")), is(false)); + } + + private Profiles defaultProfile() { + return Profiles.of(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME); } diff --git a/spring-core/src/test/java/org/springframework/core/env/DummyEnvironment.java b/spring-core/src/test/java/org/springframework/core/env/DummyEnvironment.java index aa267476235..e37212f88f8 100644 --- a/spring-core/src/test/java/org/springframework/core/env/DummyEnvironment.java +++ b/spring-core/src/test/java/org/springframework/core/env/DummyEnvironment.java @@ -73,8 +73,14 @@ public class DummyEnvironment implements Environment { return null; } + @Override + public boolean acceptsProfiles(String... profiles) { + return false; + } + @Override public boolean acceptsProfiles(Profiles profiles) { return false; } + } diff --git a/spring-core/src/test/java/org/springframework/core/env/ProfilesTests.java b/spring-core/src/test/java/org/springframework/core/env/ProfilesTests.java index 13c7d659dc1..dd0f541f7b4 100644 --- a/spring-core/src/test/java/org/springframework/core/env/ProfilesTests.java +++ b/spring-core/src/test/java/org/springframework/core/env/ProfilesTests.java @@ -21,12 +21,13 @@ import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; +import java.util.function.Predicate; import java.util.function.Supplier; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.springframework.core.env.Profiles.ActiveProfiles; + import org.springframework.util.StringUtils; import static org.junit.Assert.*; @@ -35,6 +36,7 @@ import static org.junit.Assert.*; * Tests for {@link Profiles}. * * @author Phillip Webb + * @author Stephane Nicoll */ public class ProfilesTests { @@ -56,10 +58,10 @@ public class ProfilesTests { } @Test - public void ofNullElement() throws Exception { + public void ofNullElement() { this.thrown.expect(IllegalArgumentException.class); this.thrown.expectMessage("must contain text"); - Profiles.of((String)null); + Profiles.of((String) null); } @Test @@ -103,22 +105,32 @@ public class ProfilesTests { } @Test - public void ofSingleExpression() throws Exception { + public void ofSingleExpression() { Profiles profiles = Profiles.of("(spring)"); assertTrue(profiles.matches(new MockActiveProfiles("spring"))); assertFalse(profiles.matches(new MockActiveProfiles("framework"))); } @Test - public void ofSingleInvertedExpression() throws Exception { + public void ofSingleInvertedExpression() { Profiles profiles = Profiles.of("(!spring)"); assertFalse(profiles.matches(new MockActiveProfiles("spring"))); assertTrue(profiles.matches(new MockActiveProfiles("framework"))); } @Test - public void ofOrExpression() throws Exception { + public void ofOrExpression() { Profiles profiles = Profiles.of("(spring | framework)"); + assertOrExpression(profiles); + } + + @Test + public void ofOrExpressionWithoutSpace() { + Profiles profiles = Profiles.of("(spring|framework)"); + assertOrExpression(profiles); + } + + private void assertOrExpression(Profiles profiles) { assertTrue(profiles.matches(new MockActiveProfiles("spring"))); assertTrue(profiles.matches(new MockActiveProfiles("framework"))); assertTrue(profiles.matches(new MockActiveProfiles("spring", "framework"))); @@ -126,17 +138,24 @@ public class ProfilesTests { } @Test - public void ofAndExpression() throws Exception { + public void ofAndExpression() { Profiles profiles = Profiles.of("(spring & framework)"); - assertFalse(profiles.matches(new MockActiveProfiles("spring"))); - assertFalse(profiles.matches(new MockActiveProfiles("framework"))); - assertTrue(profiles.matches(new MockActiveProfiles("spring", "framework"))); - assertFalse(profiles.matches(new MockActiveProfiles("java"))); + assertAndExpression(profiles); } @Test - public void ofAndExpressionWithoutBraces() throws Exception { + public void ofAndExpressionWithoutSpace() { + Profiles profiles = Profiles.of("spring&framework)"); + assertAndExpression(profiles); + } + + @Test + public void ofAndExpressionWithoutBraces() { Profiles profiles = Profiles.of("spring & framework"); + assertAndExpression(profiles); + } + + private void assertAndExpression(Profiles profiles) { assertFalse(profiles.matches(new MockActiveProfiles("spring"))); assertFalse(profiles.matches(new MockActiveProfiles("framework"))); assertTrue(profiles.matches(new MockActiveProfiles("spring", "framework"))); @@ -144,8 +163,18 @@ public class ProfilesTests { } @Test - public void ofNotAndExpression() throws Exception { + public void ofNotAndExpression() { Profiles profiles = Profiles.of("!(spring & framework)"); + assertOfNotAndExpression(profiles); + } + + @Test + public void ofNotAndExpressionWithoutSpace() { + Profiles profiles = Profiles.of("!(spring&framework)"); + assertOfNotAndExpression(profiles); + } + + private void assertOfNotAndExpression(Profiles profiles) { assertTrue(profiles.matches(new MockActiveProfiles("spring"))); assertTrue(profiles.matches(new MockActiveProfiles("framework"))); assertFalse(profiles.matches(new MockActiveProfiles("spring", "framework"))); @@ -153,8 +182,18 @@ public class ProfilesTests { } @Test - public void ofNotOrExpression() throws Exception { + public void ofNotOrExpression() { Profiles profiles = Profiles.of("!(spring | framework)"); + assertOfNotOrExpression(profiles); + } + + @Test + public void ofNotOrExpressionWithoutSpace() { + Profiles profiles = Profiles.of("!(spring|framework)"); + assertOfNotOrExpression(profiles); + } + + private void assertOfNotOrExpression(Profiles profiles) { assertFalse(profiles.matches(new MockActiveProfiles("spring"))); assertFalse(profiles.matches(new MockActiveProfiles("framework"))); assertFalse(profiles.matches(new MockActiveProfiles("spring", "framework"))); @@ -162,8 +201,18 @@ public class ProfilesTests { } @Test - public void ofComplex() throws Exception { + public void ofComplexExpression() { Profiles profiles = Profiles.of("(spring & framework) | (spring & java)"); + assertComplexExpression(profiles); + } + + @Test + public void ofComplexExpressionWithoutSpace() { + Profiles profiles = Profiles.of("(spring&framework)|(spring&java)"); + assertComplexExpression(profiles); + } + + private void assertComplexExpression(Profiles profiles) { assertFalse(profiles.matches(new MockActiveProfiles("spring"))); assertTrue(profiles.matches(new MockActiveProfiles("spring", "framework"))); assertTrue(profiles.matches(new MockActiveProfiles("spring", "java"))); @@ -171,12 +220,18 @@ public class ProfilesTests { } @Test - public void malformedExpressions() throws Exception { + public void malformedExpressions() { assertMalformed(() -> Profiles.of("(")); assertMalformed(() -> Profiles.of(")")); assertMalformed(() -> Profiles.of("a & b | c")); } + @Test + public void sensibleToString() { + assertEquals("spring & framework or java | kotlin", + Profiles.of("spring & framework", "java | kotlin").toString()); + } + private void assertMalformed(Supplier supplier) { try { supplier.get(); @@ -187,7 +242,7 @@ public class ProfilesTests { } } - private static class MockActiveProfiles implements ActiveProfiles { + private static class MockActiveProfiles implements Predicate { private Set activeProfiles; @@ -210,13 +265,14 @@ public class ProfilesTests { @Override - public boolean contains(String profile) { + public boolean test(String profile) { if (!StringUtils.hasText(profile) || profile.charAt(0) == '!') { throw new IllegalArgumentException("Invalid profile [" + profile + "]"); } return (this.activeProfiles.contains(profile) || (this.activeProfiles.isEmpty() && this.defaultProfiles.contains(profile))); } + } } diff --git a/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java b/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java index 0f0266b8f0a..e705793ae81 100644 --- a/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java +++ b/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java @@ -326,12 +326,12 @@ public class StandardEnvironmentTests { } @Test - public void acceptsProfiles_withProfileExpression() throws Exception { - assertThat(environment.acceptsProfiles("p1 & p2"), is(false)); + public void acceptsProfiles_withProfileExpression() { + assertThat(environment.acceptsProfiles(Profiles.of("p1 & p2")), is(false)); environment.addActiveProfile("p1"); - assertThat(environment.acceptsProfiles("p1 & p2"), is(false)); + assertThat(environment.acceptsProfiles(Profiles.of("p1 & p2")), is(false)); environment.addActiveProfile("p2"); - assertThat(environment.acceptsProfiles("p1 & p2"), is(true)); + assertThat(environment.acceptsProfiles(Profiles.of("p1 & p2")), is(true)); } @Test diff --git a/src/docs/asciidoc/core/core-beans.adoc b/src/docs/asciidoc/core/core-beans.adoc index c267701545e..17f6ce91212 100644 --- a/src/docs/asciidoc/core/core-beans.adoc +++ b/src/docs/asciidoc/core/core-beans.adoc @@ -7664,6 +7664,22 @@ straight JNDI `InitialContext` usage shown above, but not the `JndiObjectFactory variant which would force you to declare the return type as the `FactoryBean` type. ==== +The profile string may contains a simple profile name (for example `production`) or a +profile expression. A profile expression allows for more complicated profile logic to be +expressed, for example `production & us-east`. The following operators are supported in +profile expressions: + +* `!` - A logical not of the profile +* `&` - A logical and of the profiles +* `|` - A logical or of the profiles + +[NOTE] +==== +The `&` and `|` operators may not be mixed without using parentheses. For example +`production & us-east | eu-central` is not a valid expression, it must be expressed as +`production & (us-east | eu-central)`. +==== + `@Profile` can be used as a <> for the purpose of creating a custom _composed annotation_. The following example defines a custom `@Production` annotation that can be used as a drop-in replacement for @@ -7804,6 +7820,35 @@ The `spring-bean.xsd` has been constrained to allow such elements only as the last ones in the file. This should help provide flexibility without incurring clutter in the XML files. +[NOTE] +==== +The XML counterpart does not support profile expressions described above. It is possible +however to negate a profile using the `!` operator. It is also possible to apply a logical +and by nesting the profiles: + +[source,xml,indent=0] +[subs="verbatim,quotes"] +---- + + + + + + + + + + +---- + +In the example above, the `dataSource` bean will be exposed if both the `production` and +`us-east` profiles are active. +==== + [[beans-definition-profiles-enable]] ==== Activating a profile diff --git a/src/test/java/org/springframework/core/env/EnvironmentSystemIntegrationTests.java b/src/test/java/org/springframework/core/env/EnvironmentSystemIntegrationTests.java index 5abd7693bde..1d4ad4ec807 100644 --- a/src/test/java/org/springframework/core/env/EnvironmentSystemIntegrationTests.java +++ b/src/test/java/org/springframework/core/env/EnvironmentSystemIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -305,6 +305,37 @@ public class EnvironmentSystemIntegrationTests { assertThat("should not have transitive bean", ctx.containsBean(TRANSITIVE_BEAN_NAME), is(false)); } + @Test + public void annotationConfigApplicationContext_withProfileExpressionMatchOr() { + testProfileExpression(true, "p3"); + } + + @Test + public void annotationConfigApplicationContext_withProfileExpressionMatchAnd() { + testProfileExpression(true, "p1", "p2"); + } + + @Test + public void annotationConfigApplicationContext_withProfileExpressionNoMatchAnd() { + testProfileExpression(false, "p1"); + } + + @Test + public void annotationConfigApplicationContext_withProfileExpressionNoMatchNone() { + testProfileExpression(false, "p4"); + } + + private void testProfileExpression(boolean expected, String... activeProfiles) { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + StandardEnvironment environment = new StandardEnvironment(); + environment.setActiveProfiles(activeProfiles); + ctx.setEnvironment(environment); + ctx.register(ProfileExpressionConfig.class); + ctx.refresh(); + assertThat("wrong presence of expression bean", + ctx.containsBean("expressionBean"), is(expected)); + } + @Test public void webApplicationContext() { GenericWebApplicationContext ctx = new GenericWebApplicationContext(newBeanFactoryWithEnvironmentAwareBean()); @@ -644,6 +675,15 @@ public class EnvironmentSystemIntegrationTests { } } + @Profile("(p1 & p2) | p3") + @Configuration + static class ProfileExpressionConfig { + @Bean + public Object expressionBean() { + return new Object(); + } + } + /** * Constants used both locally and in scan* sub-packages