Support not (!) operator for profile selection

The following syntax is now supported

  <beans profile="p1,!p2">

  @Profile("p1", "!p2")

indicating that the <beans> element or annotated component should
be processed only if profile 'p1' is active or profile 'p2' is not
active.

Issue: SPR-8728
This commit is contained in:
Chris Beams 2012-05-27 08:10:40 +03:00
parent e72c49f4cf
commit bcd44f3798
6 changed files with 115 additions and 47 deletions

View File

@ -82,19 +82,28 @@
<xsd:attribute name="profile" use="optional" type="xsd:string"> <xsd:attribute name="profile" use="optional" type="xsd:string">
<xsd:annotation> <xsd:annotation>
<xsd:documentation><![CDATA[ <xsd:documentation><![CDATA[
The set of profiles for which this <beans> element may be parsed. Multiple profiles can be The set of profiles for which this <beans> element should be parsed. Multiple profiles
separated by any number of spaces, commas, or semi-colons (or indeed any mixture of the three). can be separated by spaces, commas, or semi-colons.
If one or more of the specified profiles are active at time of parsing, the <beans> element
will be parsed, and all of its <bean> elements registered, &lt;import&gt; elements followed, If one or more of the specified profiles are active at time of parsing, the <beans>
etc. If none of the specified profiles are active at time of parsing, then the entire element element will be parsed, and all of its <bean> elements registered, &lt;import&gt;
and its contents will be ignored. elements followed, etc. If none of the specified profiles are active at time of
parsing, then the entire element and its contents will be ignored.
If a profile is prefixed with the NOT operator '!', e.g.
<beans profile="p1,!p2">
indicates that the <beans> element should be parsed if profile "p1" is active or
if profile "p2" is not active.
Profiles are activated in one of two ways: Profiles are activated in one of two ways:
Programmatic: Programmatic:
ConfigurableEnvironment#setActiveProfiles(String...) ConfigurableEnvironment#setActiveProfiles(String...)
ConfigurableEnvironment#setDefaultProfiles(String...) ConfigurableEnvironment#setDefaultProfiles(String...)
Properties (typically through -D system properties, environment variables, or servlet context init params): Properties (typically through -D system properties, environment variables, or
servlet context init params):
spring.profiles.active=p1,p2 spring.profiles.active=p1,p2
spring.profiles.default=p1,p2 spring.profiles.default=p1,p2
]]></xsd:documentation> ]]></xsd:documentation>

View File

@ -82,19 +82,28 @@
<xsd:attribute name="profile" use="optional" type="xsd:string"> <xsd:attribute name="profile" use="optional" type="xsd:string">
<xsd:annotation> <xsd:annotation>
<xsd:documentation><![CDATA[ <xsd:documentation><![CDATA[
The set of profiles for which this <beans> element may be parsed. Multiple profiles can be The set of profiles for which this <beans> element should be parsed. Multiple profiles
separated by any number of spaces, commas, or semi-colons (or indeed any mixture of the three). can be separated by spaces, commas, or semi-colons.
If one or more of the specified profiles are active at time of parsing, the <beans> element
will be parsed, and all of its <bean> elements registered, &lt;import&gt; elements followed, If one or more of the specified profiles are active at time of parsing, the <beans>
etc. If none of the specified profiles are active at time of parsing, then the entire element element will be parsed, and all of its <bean> elements registered, &lt;import&gt;
and its contents will be ignored. elements followed, etc. If none of the specified profiles are active at time of
parsing, then the entire element and its contents will be ignored.
If a profile is prefixed with the NOT operator '!', e.g.
<beans profile="p1,!p2">
indicates that the <beans> element should be parsed if profile "p1" is active or
if profile "p2" is not active.
Profiles are activated in one of two ways: Profiles are activated in one of two ways:
Programmatic: Programmatic:
ConfigurableEnvironment#setActiveProfiles(String...) ConfigurableEnvironment#setActiveProfiles(String...)
ConfigurableEnvironment#setDefaultProfiles(String...) ConfigurableEnvironment#setDefaultProfiles(String...)
Properties (typically through -D system properties, environment variables, or servlet context init params): Properties (typically through -D system properties, environment variables, or
servlet context init params):
spring.profiles.active=p1,p2 spring.profiles.active=p1,p2
spring.profiles.default=p1,p2 spring.profiles.default=p1,p2
]]></xsd:documentation> ]]></xsd:documentation>

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2011 the original author or authors. * Copyright 2002-2012 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -25,14 +25,15 @@ import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
/** /**
* Indicates that a component is eligible for registration when one or more {@linkplain #value * Indicates that a component is eligible for registration when one or more {@linkplain
* specified profiles} are active. * #value specified profiles} are active.
* *
* <p>A <em>profile</em> is a named logical grouping that may be activated programatically via * <p>A <em>profile</em> is a named logical grouping that may be activated
* {@link ConfigurableEnvironment#setActiveProfiles} or declaratively through setting the * programmatically via {@link ConfigurableEnvironment#setActiveProfiles} or declaratively
* {@link AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME spring.profiles.active} property, * through setting the {@link AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME
* usually through JVM system properties, as an environment variable, or for web applications * spring.profiles.active} property, usually through JVM system properties, as an
* as a Servlet context parameter in {@code web.xml}. * environment variable, or for web applications as a Servlet context parameter in
* {@code web.xml}.
* *
* <p>The {@code @Profile} annotation may be used in any of the following ways: * <p>The {@code @Profile} annotation may be used in any of the following ways:
* <ul> * <ul>
@ -42,19 +43,26 @@ import org.springframework.core.env.ConfigurableEnvironment;
* </ul> * </ul>
* *
* <p>If a {@code @Configuration} class is marked with {@code @Profile}, all of the * <p>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 * {@code @Bean} methods and {@link Import @Import} annotations associated with that class
* be bypassed unless one or more the specified profiles are active. This is very similar to * will be bypassed unless one or more the specified profiles are active. This is very
* the behavior in Spring XML: if the {@code profile} attribute of the {@code beans} element is * similar to the behavior in Spring XML: if the {@code profile} attribute of the
* supplied e.g., {@code <beans profile="p1,p2">}, the {@code beans} element will not be parsed unless * {@code beans} element is supplied e.g., {@code <beans profile="p1,p2">}, the
* profiles 'p1' and/or 'p2' have been activated. Likewise, if a {@code @Component} or * {@code beans} element will not be parsed unless profiles 'p1' and/or 'p2' have been
* {@code @Configuration} class is marked with <code>@Profile({"p1", "p2"})</code>, that class will * activated. Likewise, if a {@code @Component} or {@code @Configuration} class is marked
* not be registered/processed unless profiles 'p1' and/or 'p2' have been activated. * with {@code @Profile({"p1", "p2"})}, that class will not be registered/processed unless
* profiles 'p1' and/or 'p2' have been activated.
* *
* <p>If the {@code @Profile} annotation is omitted, registration will occur, regardless of which, * <p>If a given profile is prefixed with the NOT operator ({@code !}), the annotated
* if any, profiles are active. * will be registered if the profile is <em>not</em> active. e.g., for
* {@code @Profile({"p1", "!p2"})}, registration will occur if profile 'p1' is active or
* if profile 'p2' is not active.
* *
* <p>When defining Spring beans via XML, the {@code "profile"} attribute of the {@code <beans>} * <p>If the {@code @Profile} annotation is omitted, registration will occur, regardless
* element may be used. See the documentation in {@code spring-beans-3.1.xsd} for details. * of which (if any) profiles are active.
*
* <p>When defining Spring beans via XML, the {@code "profile"} attribute of the
* {@code <beans>} element may be used. See the documentation in
* {@code spring-beans} XSD (version 3.1 or greater) for details.
* *
* @author Chris Beams * @author Chris Beams
* @since 3.1 * @since 3.1

View File

@ -31,6 +31,7 @@ import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import static java.lang.String.*; import static java.lang.String.*;
import static org.springframework.util.StringUtils.*; import static org.springframework.util.StringUtils.*;
/** /**
@ -300,31 +301,43 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
public boolean acceptsProfiles(String... profiles) { public boolean acceptsProfiles(String... profiles) {
Assert.notEmpty(profiles, "Must specify at least one profile"); Assert.notEmpty(profiles, "Must specify at least one profile");
boolean activeProfileFound = false;
Set<String> activeProfiles = this.doGetActiveProfiles();
Set<String> defaultProfiles = this.doGetDefaultProfiles();
for (String profile : profiles) { for (String profile : profiles) {
if (profile != null && profile.length() > 0 && profile.charAt(0) == '!') {
return !this.isProfileActive(profile.substring(1));
}
if (this.isProfileActive(profile)) {
return true;
}
}
return false;
}
/**
* Return whether the given profile is active, or if active profiles are empty
* whether the profile should be active by default.
* @throws IllegalArgumentException per {@link #validateProfile(String)}
* @since 3.2
*/
protected boolean isProfileActive(String profile) {
this.validateProfile(profile); this.validateProfile(profile);
if (activeProfiles.contains(profile) return this.doGetActiveProfiles().contains(profile)
|| (activeProfiles.isEmpty() && defaultProfiles.contains(profile))) { || (this.doGetActiveProfiles().isEmpty() && this.doGetDefaultProfiles().contains(profile));
activeProfileFound = true;
break;
}
}
return activeProfileFound;
} }
/** /**
* Validate the given profile, called internally prior to adding to the set of * Validate the given profile, called internally prior to adding to the set of
* active or default profiles. * active or default profiles.
* <p>Subclasses may override to impose further restrictions on profile syntax. * <p>Subclasses may override to impose further restrictions on profile syntax.
* @throws IllegalArgumentException if the profile is null, empty or whitespace-only * @throws IllegalArgumentException if the profile is null, empty, whitespace-only or
* begins with the profile NOT operator (!).
* @see #acceptsProfiles * @see #acceptsProfiles
* @see #addActiveProfile * @see #addActiveProfile
* @see #setDefaultProfiles * @see #setDefaultProfiles
*/ */
protected void validateProfile(String profile) { protected void validateProfile(String profile) {
Assert.hasText(profile, "Invalid profile [" + profile + "]: must contain text"); Assert.hasText(profile, "Invalid profile [" + profile + "]: must contain text");
Assert.isTrue(profile.charAt(0) != '!',
"Invalid profile [" + profile + "]: must not begin with the ! operator");
} }
public MutablePropertySources getPropertySources() { public MutablePropertySources getPropertySources() {

View File

@ -100,7 +100,10 @@ public interface Environment extends PropertyResolver {
/** /**
* Return whether one or more of the given profiles is active or, in the case of no * 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 * explicit active profiles, whether one or more of the given profiles is included in
* the set of default profiles * 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 <em>not</em> active. For
* example, {@code env.acceptsProfiles("p1", "!p2")} will return true if profile 'p1'
* is active or 'p2' is not active.
* @throws IllegalArgumentException if called with zero arguments * @throws IllegalArgumentException if called with zero arguments
* @throws IllegalArgumentException if any profile is null, empty or whitespace-only * @throws IllegalArgumentException if any profile is null, empty or whitespace-only
* @see #getActiveProfiles * @see #getActiveProfiles

View File

@ -17,8 +17,10 @@
package org.springframework.core.env; package org.springframework.core.env;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.security.AccessControlException; import java.security.AccessControlException;
import java.security.Permission; import java.security.Permission;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
@ -147,6 +149,11 @@ public class StandardEnvironmentTests {
environment.setActiveProfiles(""); environment.setActiveProfiles("");
} }
@Test(expected=IllegalArgumentException.class)
public void setActiveProfiles_withNotOperator() {
environment.setActiveProfiles("p1", "!p2");
}
@Test(expected=IllegalArgumentException.class) @Test(expected=IllegalArgumentException.class)
public void setDefaultProfiles_withNullProfileArray() { public void setDefaultProfiles_withNullProfileArray() {
environment.setDefaultProfiles((String[])null); environment.setDefaultProfiles((String[])null);
@ -162,6 +169,11 @@ public class StandardEnvironmentTests {
environment.setDefaultProfiles(""); environment.setDefaultProfiles("");
} }
@Test(expected=IllegalArgumentException.class)
public void setDefaultProfiles_withNotOperator() {
environment.setDefaultProfiles("d1", "!d2");
}
@Test @Test
public void addActiveProfile() { public void addActiveProfile() {
assertThat(environment.getActiveProfiles().length, is(0)); assertThat(environment.getActiveProfiles().length, is(0));
@ -284,6 +296,20 @@ public class StandardEnvironmentTests {
assertThat(environment.acceptsProfiles("p1"), is(true)); assertThat(environment.acceptsProfiles("p1"), is(true));
} }
@Test
public void acceptsProfiles_withNotOperator() {
assertThat(environment.acceptsProfiles("p1"), is(false));
assertThat(environment.acceptsProfiles("!p1"), is(true));
environment.addActiveProfile("p1");
assertThat(environment.acceptsProfiles("p1"), is(true));
assertThat(environment.acceptsProfiles("!p1"), is(false));
}
@Test(expected=IllegalArgumentException.class)
public void acceptsProfiles_withInvalidNotOperator() {
environment.acceptsProfiles("p1", "!");
}
@Test @Test
public void environmentSubclass_withCustomProfileValidation() { public void environmentSubclass_withCustomProfileValidation() {
ConfigurableEnvironment env = new AbstractEnvironment() { ConfigurableEnvironment env = new AbstractEnvironment() {