diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java index 3ea39a829fab..4e0857b9ae43 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java @@ -22,6 +22,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import java.util.stream.Stream; import org.apache.commons.logging.Log; @@ -56,6 +57,7 @@ * * @author Phillip Webb * @author Madhura Bhave + * @author Zhengsheng Xia */ class ConfigDataEnvironment { @@ -68,6 +70,11 @@ class ConfigDataEnvironment { * Property used to provide additional locations to import. */ static final String ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location"; + + /** + * The "config intergration location" property name. + */ + public static final String INTERGRATION_LOCATION_PROPERTY = "spring.config.intergration-location"; /** * Property used to provide additional locations to import. @@ -200,12 +207,23 @@ private List getInitialImportContributors(Bind bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS)); addInitialImportContributors(initialContributors, bindLocations(binder, LOCATION_PROPERTY, DEFAULT_SEARCH_LOCATIONS)); + addInitialImportContributors(initialContributors, + bindLocations(binder, INTERGRATION_LOCATION_PROPERTY, EMPTY_LOCATIONS, true)); return initialContributors; } - private ConfigDataLocation[] bindLocations(Binder binder, String propertyName, ConfigDataLocation[] other) { - return binder.bind(propertyName, CONFIG_DATA_LOCATION_ARRAY).orElse(other); + private ConfigDataLocation[] bindLocations(Binder binder, String propertyName, ConfigDataLocation[] other, + boolean allowClasspathAll) { + ConfigDataLocation[] result = binder.bind(propertyName, CONFIG_DATA_LOCATION_ARRAY).orElse(other); + if (allowClasspathAll && other != result) { + Stream.of(result).forEach(configDataLocation -> configDataLocation.setAllowClasspathAll(allowClasspathAll)); + } + return result; } + + private ConfigDataLocation[] bindLocations(Binder binder, String propertyName, ConfigDataLocation[] other) { + return bindLocations(binder, propertyName, other, false); + } private void addInitialImportContributors(List initialContributors, ConfigDataLocation[] locations) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocation.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocation.java index 98c941ba69cf..78eb472894af 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocation.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocation.java @@ -31,6 +31,7 @@ * prefixed with {@code optional:}. * * @author Phillip Webb + * @author Zhengsheng Xia * @since 2.4.0 */ public final class ConfigDataLocation implements OriginProvider { @@ -45,6 +46,8 @@ public final class ConfigDataLocation implements OriginProvider { private final String value; private final Origin origin; + + private boolean allowClasspathAll; private ConfigDataLocation(boolean optional, String value, Origin origin) { this.value = value; @@ -78,6 +81,25 @@ public String getValue() { public boolean hasPrefix(String prefix) { return this.value.startsWith(prefix); } + + + /** + * Getter method for property allowClasspathAll. + * + * @return property value of allowClasspathAll + */ + public boolean isAllowClasspathAll() { + return allowClasspathAll; + } + + /** + * Setter method for property allowClasspathAll. + * + * @param allowClasspathAll value to be assigned to property allowClasspathAll + */ + public void setAllowClasspathAll(boolean allowClasspathAll) { + this.allowClasspathAll = allowClasspathAll; + } /** * Return {@link #getValue()} with the specified prefix removed. If the location does diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java index e583445b9063..2c24a445840a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java @@ -70,6 +70,7 @@ import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.util.Assert; @@ -111,6 +112,7 @@ * @author EddĂș MelĂ©ndez * @author Madhura Bhave * @author Scott Frederick + * @author Zhengsheng Xia * @since 1.0.0 * @deprecated since 2.4.0 in favor of {@link ConfigDataEnvironmentPostProcessor} */ @@ -161,6 +163,11 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, * The "config additional location" property name. */ public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location"; + + /** + * The "config intergration location" property name. + */ + public static final String CONFIG_INTERGRATION_LOCATION_PROPERTY = "spring.config.intergration-location"; /** * The default order for the processor. @@ -588,7 +595,11 @@ private boolean isPatternLocation(String location) { } private Resource[] getResourcesFromPatternLocationReference(String locationReference) throws IOException { - String directoryPath = locationReference.substring(0, locationReference.indexOf("*/")); + int wildcardlastIdx = locationReference.indexOf("*/"); + String directoryPath = locationReference.substring(0, wildcardlastIdx); + if (locationReference.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX)) { + return getResourcesFromClasspathAllPatternLocation(locationReference, locationReference.length()- wildcardlastIdx); + } Resource resource = this.resourceLoader.getResource(directoryPath); File[] files = resource.getFile().listFiles(File::isDirectory); if (files != null) { @@ -600,6 +611,27 @@ private Resource[] getResourcesFromPatternLocationReference(String locationRefer } return EMPTY_RESOURCES; } + + private Resource[] getResourcesFromClasspathAllPatternLocation(String location,int propNameLen) throws IOException { + PathMatchingResourcePatternResolver pathMatchingResolver =new PathMatchingResourcePatternResolver(); + Resource[] resources = pathMatchingResolver.getResources(location); + if (resources != null) { + Arrays.sort(resources,Comparator.comparing(resource -> { + String filename; + try { + filename = resource.getFile().getAbsolutePath(); + } catch (IOException e) { + filename = resource.getFilename(); + } + int idx = filename.lastIndexOf(File.separator, filename.length() - propNameLen); + if (idx>-1) { + filename= filename.substring(idx); + } + return filename; + })); + } + return resources; + } private void addIncludedProfiles(Set includeProfiles) { LinkedList existingProfiles = new LinkedList<>(this.profiles); @@ -688,19 +720,21 @@ private Set getSearchLocations() { locations.addAll( asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS)); } + locations.addAll(getSearchLocations(CONFIG_INTERGRATION_LOCATION_PROPERTY,true)); return locations; } - private Set getSearchLocations(String propertyName) { + private Set getSearchLocations(String propertyName, boolean allowClasspathAll) { Set locations = new LinkedHashSet<>(); if (this.environment.containsProperty(propertyName)) { for (String path : asResolvedSet(this.environment.getProperty(propertyName), null)) { if (!path.contains("$")) { path = StringUtils.cleanPath(path); - Assert.state(!path.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX), + boolean hasClasspathAll=path.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX); + Assert.state(allowClasspathAll || !hasClasspathAll, "Classpath wildcard patterns cannot be used as a search location"); - validateWildcardLocation(path); - if (!ResourceUtils.isUrl(path)) { + validateWildcardLocation(path, allowClasspathAll); + if (!hasClasspathAll && !ResourceUtils.isUrl(path) ) { path = ResourceUtils.FILE_URL_PREFIX + path; } } @@ -710,9 +744,17 @@ private Set getSearchLocations(String propertyName) { return locations; } - private void validateWildcardLocation(String path) { + private Set getSearchLocations(String propertyName) { + return getSearchLocations(propertyName, false); + } + //xia + + //xia + private void validateWildcardLocation(String path, boolean allowClasspathAll) { if (path.contains("*")) { - Assert.state(StringUtils.countOccurrencesOf(path, "*") == 1, + int wildCardCount = StringUtils.countOccurrencesOf(path, "*"); + boolean rightState =allowClasspathAll ? wildCardCount == 2: wildCardCount == 1; + Assert.state(rightState, () -> "Search location '" + path + "' cannot contain multiple wildcards"); String directoryPath = path.substring(0, path.lastIndexOf("/") + 1); Assert.state(directoryPath.endsWith("*/"), () -> "Search location '" + path + "' must end with '*/'"); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/LocationResourceLoader.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/LocationResourceLoader.java index b3d232cce171..4f46d58b049c 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/LocationResourceLoader.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/LocationResourceLoader.java @@ -18,6 +18,7 @@ import java.io.File; import java.io.FilenameFilter; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -26,6 +27,7 @@ import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.util.Assert; import org.springframework.util.ResourceUtils; @@ -37,6 +39,7 @@ * * @author Phillip Webb * @author Madhura Bhave + * @author Zhengsheng Xia */ class LocationResourceLoader { @@ -88,12 +91,18 @@ private void validateNonPattern(String location) { * Get a multiple resources from a location pattern. * @param location the location pattern * @param type the type of resource to return + * @param allowClasspathAll the boolean value of allow param location contains 'classpath*:' * @return the resources * @see #isPattern(String) */ - Resource[] getResources(String location, ResourceType type) { - validatePattern(location, type); - String directoryPath = location.substring(0, location.indexOf("*/")); + Resource[] getResources(String location, ResourceType type ,boolean allowClasspathAll) { + boolean startsWithClasspathAll = validatePatternAndCheckClasspathAll(location, type, allowClasspathAll); + int wildcardlastIdx = location.indexOf("*/"); + String directoryPath = location.substring(0, wildcardlastIdx); + if (startsWithClasspathAll) { + return getResourcesFromClasspathAllPatternLocation(location, location.length()- wildcardlastIdx); + } + String fileName = location.substring(location.lastIndexOf("/") + 1); Resource directoryResource = getResource(directoryPath); if (!directoryResource.exists()) { @@ -119,17 +128,68 @@ Resource[] getResources(String location, ResourceType type) { } return resources.toArray(EMPTY_RESOURCES); } - - private void validatePattern(String location, ResourceType type) { + + + /** + * Get a multiple resources from a location pattern. + * @param location the location pattern + * @param type the type of resource to return + * @return the resources + * @see #isPattern(String) + */ + Resource[] getResources(String location, ResourceType type) { + return getResources(location, type, false); + } + + /** + * Get a multiple resources from a location pattern. + * @param location the location pattern + * @param allowClasspathAll the boolean value of allow param location contains 'classpath*:' + * @return the resources + * @see #isPattern(String) + */ + Resource[] getResources(String location, boolean allowClasspathAll) { + ResourceType resourceType =location.endsWith("/")? ResourceType.DIRECTORY : ResourceType.FILE; + return getResources(location,resourceType,allowClasspathAll); + } + + private Resource[] getResourcesFromClasspathAllPatternLocation(String location,int propNameLen) { + PathMatchingResourcePatternResolver pathMatchingResolver =new PathMatchingResourcePatternResolver(); + Resource[] resources; + try { + resources = pathMatchingResolver.getResources(location); + } catch (IOException e) { + throw new IllegalStateException(String.format("get location: '%s' error",location),e); + } + if (resources != null) { + Arrays.sort(resources, Comparator.comparing(resource -> { + String filename; + FileSystemResource fileResource =(FileSystemResource)resource; + filename=fileResource.getFile().getAbsolutePath(); + int idx = filename.lastIndexOf(File.separator, filename.length() - propNameLen); + if (idx>-1) { + filename= filename.substring(idx); + } + return filename; + })); + } + return resources; + } + + private boolean validatePatternAndCheckClasspathAll(String location, ResourceType type,boolean allowClasspathAll) { Assert.state(isPattern(location), () -> String.format("Location '%s' must be a pattern", location)); - Assert.state(!location.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX), + boolean startsWithClasspathAll = location.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX); + Assert.state((allowClasspathAll || !startsWithClasspathAll), () -> String.format("Location '%s' cannot use classpath wildcards", location)); - Assert.state(StringUtils.countOccurrencesOf(location, "*") == 1, + int countOfWilldcard = StringUtils.countOccurrencesOf(location, "*"); + Assert.state((startsWithClasspathAll? (countOfWilldcard ==2) : countOfWilldcard == 1), () -> String.format("Location '%s' cannot contain multiple wildcards", location)); - String directoryPath = (type != ResourceType.DIRECTORY) ? location.substring(0, location.lastIndexOf("/") + 1) - : location; - Assert.state(directoryPath.endsWith("*/"), () -> String.format("Location '%s' must end with '*/'", location)); + if (type == ResourceType.DIRECTORY) { + Assert.state(location.endsWith("*/"), () -> String.format("Location '%s' must end with '*/'", location)); + } + return startsWithClasspathAll; } + private File getDirectory(String patternLocation, Resource resource) { try { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java index f21970c39153..c87c51bda4b8 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java @@ -29,7 +29,6 @@ import org.apache.commons.logging.Log; -import org.springframework.boot.context.config.LocationResourceLoader.ResourceType; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.env.PropertySourceLoader; import org.springframework.core.Ordered; @@ -37,6 +36,7 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.log.LogMessage; import org.springframework.util.Assert; @@ -48,6 +48,7 @@ * @author Madhura Bhave * @author Phillip Webb * @author Scott Frederick + * @author Zhengsheng Xia * @since 2.4.0 */ public class StandardConfigDataLocationResolver @@ -244,6 +245,9 @@ private Collection resolveEmptyDirectories( Set empty = new LinkedHashSet<>(); for (StandardConfigDataReference reference : references) { if (reference.isMandatoryDirectory()) { + if (reference.getDirectory().startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX)) { + continue; + } Resource resource = this.resourceLoader.getResource(reference.getDirectory()); if (resource instanceof ClassPathResource) { continue; @@ -274,7 +278,8 @@ private List resolveNonPattern(StandardConfigDataRef private List resolvePattern(StandardConfigDataReference reference) { List resolved = new ArrayList<>(); - for (Resource resource : this.resourceLoader.getResources(reference.getResourceLocation(), ResourceType.FILE)) { + for (Resource resource : this.resourceLoader.getResources(reference.getResourceLocation(), + reference.getConfigDataLocation().isAllowClasspathAll())) { if (!resource.exists() && reference.isSkippable()) { logSkippingResource(reference); } diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 99ecbf0fe077..e46e4e29826c 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -291,6 +291,12 @@ "sourceType": "org.springframework.boot.context.config.ConfigFileApplicationListener", "description": "Config file locations used in addition to the defaults." }, + { + "name": "spring.config.intergration-location", + "type": "java.lang.String", + "sourceType": "org.springframework.boot.context.config.ConfigFileApplicationListener", + "description": "lowest config file locations ,when intergrate dynamic jars, the value can be such as 'classpath*:/sink/*/'" + }, { "name": "spring.config.import", "type": "java.util.List", diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java index bba8dd5d12f3..f68cc4c1a072 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java @@ -72,6 +72,7 @@ * * @author Madhura Bhave * @author Phillip Webb + * @author Zhengsheng Xia */ class ConfigDataEnvironmentPostProcessorIntegrationTests { @@ -502,6 +503,24 @@ void runWhenAdditionalLocationAndLocationLoadsWithAdditionalTakingPrecedenceOver assertThat(context.getEnvironment().getProperty("foo")).isEqualTo("bar"); assertThat(context.getEnvironment().getProperty("value")).isNull(); } + + @Test + void runWhenIntergrationLocationAndLocationLoadsWithClasspathAllFileName() { + ConfigurableApplicationContext context = this.application.run( + "--"+ConfigDataEnvironment.INTERGRATION_LOCATION_PROPERTY+"=classpath*:/wildcardconfig/*/testproperties.properties"); + assertThat(context.getEnvironment().getProperty("first.property")).isEqualTo("apple"); + assertThat(context.getEnvironment().getProperty("second.property")).isEqualTo("ball"); + assertThat(context.getEnvironment().getProperty("value")).isEqualTo("1234"); + } + + @Test + void runWhenIntergrationLocationAndLocationLoadsWithClasspathAllDirectory() { + ConfigurableApplicationContext context = this.application.run( + "--"+ConfigDataEnvironment.INTERGRATION_LOCATION_PROPERTY+"=classpath*:/wildcardconfig/*/"); + assertThat(context.getEnvironment().getProperty("first.property")).isEqualTo("green"); + assertThat(context.getEnvironment().getProperty("second.property")).isEqualTo("red"); + assertThat(context.getEnvironment().getProperty("value")).isEqualTo("1234"); + } @Test void runWhenPropertiesFromCustomPropertySourceLoaderShouldLoadFromCustomSource() { diff --git a/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/0-empty/application.properties b/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/0-empty/application.properties new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/0-empty/testproperties.properties b/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/0-empty/testproperties.properties new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/1-first/application.properties b/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/1-first/application.properties new file mode 100644 index 000000000000..939fe34c17ec --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/1-first/application.properties @@ -0,0 +1 @@ +first.property=green \ No newline at end of file diff --git a/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/1-first/testproperties.properties b/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/1-first/testproperties.properties new file mode 100644 index 000000000000..1c97e4c41267 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/1-first/testproperties.properties @@ -0,0 +1 @@ +first.property=apple \ No newline at end of file diff --git a/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/2-second/application.properties b/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/2-second/application.properties new file mode 100644 index 000000000000..c957db09f88b --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/2-second/application.properties @@ -0,0 +1 @@ +second.property=red \ No newline at end of file diff --git a/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/2-second/testproperties.properties b/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/2-second/testproperties.properties new file mode 100644 index 000000000000..a568eac54e05 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/2-second/testproperties.properties @@ -0,0 +1 @@ +second.property=ball \ No newline at end of file diff --git a/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/nested/3-third/application.properties b/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/nested/3-third/application.properties new file mode 100644 index 000000000000..d00e7f0a62f7 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/nested/3-third/application.properties @@ -0,0 +1 @@ +third.property=shouldnotbefound diff --git a/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/nested/3-third/testproperties.properties b/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/nested/3-third/testproperties.properties new file mode 100644 index 000000000000..d00e7f0a62f7 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/wildcardconfig/nested/3-third/testproperties.properties @@ -0,0 +1 @@ +third.property=shouldnotbefound