Skip to content

Commit e6b8400

Browse files
committed
Support ResourceLoader delegation from ApplicationResourceLoader
Update `ApplicationResourceLoader` to support delegation to another `ResourceLoader`. The update allows customer resource loaders to be used when loading SSL resources. Closes gh-42835
1 parent 65fcf34 commit e6b8400

File tree

19 files changed

+435
-39
lines changed

19 files changed

+435
-39
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfiguration.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import org.springframework.context.annotation.Configuration;
6161
import org.springframework.core.Ordered;
6262
import org.springframework.core.io.Resource;
63+
import org.springframework.core.io.ResourceLoader;
6364
import org.springframework.util.Assert;
6465
import org.springframework.util.StringUtils;
6566

@@ -81,9 +82,12 @@
8182
@EnableConfigurationProperties(CouchbaseProperties.class)
8283
public class CouchbaseAutoConfiguration {
8384

85+
private final ResourceLoader resourceLoader;
86+
8487
private final CouchbaseProperties properties;
8588

86-
CouchbaseAutoConfiguration(CouchbaseProperties properties) {
89+
CouchbaseAutoConfiguration(ResourceLoader resourceLoader, CouchbaseProperties properties) {
90+
this.resourceLoader = ApplicationResourceLoader.get(resourceLoader);
8791
this.properties = properties;
8892
}
8993

@@ -117,7 +121,7 @@ public Authenticator couchbaseAuthenticator(CouchbaseConnectionDetails connectio
117121
}
118122
Jks jks = this.properties.getAuthentication().getJks();
119123
if (jks.getLocation() != null) {
120-
Resource resource = new ApplicationResourceLoader().getResource(jks.getLocation());
124+
Resource resource = this.resourceLoader.getResource(jks.getLocation());
121125
String keystorePassword = jks.getPassword();
122126
try (InputStream inputStream = resource.getInputStream()) {
123127
KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/PropertiesSslBundle.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public SslManagerBundle getManagers() {
9999
* @return an {@link SslBundle} instance
100100
*/
101101
public static SslBundle get(PemSslBundleProperties properties) {
102-
return get(properties, new ApplicationResourceLoader());
102+
return get(properties, ApplicationResourceLoader.get());
103103
}
104104

105105
/**
@@ -143,7 +143,7 @@ private static PemSslStoreDetails asPemSslStoreDetails(PemSslBundleProperties.St
143143
* @return an {@link SslBundle} instance
144144
*/
145145
public static SslBundle get(JksSslBundleProperties properties) {
146-
return get(properties, new ApplicationResourceLoader());
146+
return get(properties, ApplicationResourceLoader.get());
147147
}
148148

149149
/**

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslAutoConfiguration.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@
3838
@EnableConfigurationProperties(SslProperties.class)
3939
public class SslAutoConfiguration {
4040

41-
private final ApplicationResourceLoader resourceLoader;
41+
private final ResourceLoader resourceLoader;
4242

4343
private final SslProperties sslProperties;
4444

4545
SslAutoConfiguration(ResourceLoader resourceLoader, SslProperties sslProperties) {
46-
this.resourceLoader = new ApplicationResourceLoader(resourceLoader.getClassLoader());
46+
this.resourceLoader = ApplicationResourceLoader.get(resourceLoader);
4747
this.sslProperties = sslProperties;
4848
}
4949

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/BundleContentPropertyTests.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ void hasValueWhenHasEmptyValueReturnsFalse() {
7878
@Test
7979
void toWatchPathWhenNotPathThrowsException() {
8080
BundleContentProperty property = new BundleContentProperty("name", PEM_TEXT);
81-
assertThatIllegalStateException().isThrownBy(() -> property.toWatchPath(new ApplicationResourceLoader()))
81+
assertThatIllegalStateException().isThrownBy(() -> property.toWatchPath(ApplicationResourceLoader.get()))
8282
.withMessage("Unable to convert value of property 'name' to a path");
8383
}
8484

@@ -87,15 +87,15 @@ void toWatchPathWhenPathReturnsPath() throws URISyntaxException {
8787
URL resource = getClass().getResource("keystore.jks");
8888
Path file = Path.of(resource.toURI()).toAbsolutePath();
8989
BundleContentProperty property = new BundleContentProperty("name", file.toString());
90-
assertThat(property.toWatchPath(new ApplicationResourceLoader())).isEqualTo(file);
90+
assertThat(property.toWatchPath(ApplicationResourceLoader.get())).isEqualTo(file);
9191
}
9292

9393
@Test
9494
void toWatchPathUsesResourceLoader() throws URISyntaxException {
9595
URL resource = getClass().getResource("keystore.jks");
9696
Path file = Path.of(resource.toURI()).toAbsolutePath();
9797
BundleContentProperty property = new BundleContentProperty("name", file.toString());
98-
ResourceLoader resourceLoader = spy(new ApplicationResourceLoader());
98+
ResourceLoader resourceLoader = spy(ApplicationResourceLoader.get());
9999
assertThat(property.toWatchPath(resourceLoader)).isEqualTo(file);
100100
then(resourceLoader).should(atLeastOnce()).getResource(file.toString());
101101
}
@@ -104,7 +104,7 @@ void toWatchPathUsesResourceLoader() throws URISyntaxException {
104104
void shouldThrowBundleContentNotWatchableExceptionIfContentIsNotWatchable() {
105105
BundleContentProperty property = new BundleContentProperty("name", "https://example.com/");
106106
assertThatExceptionOfType(BundleContentNotWatchableException.class)
107-
.isThrownBy(() -> property.toWatchPath(new ApplicationResourceLoader()))
107+
.isThrownBy(() -> property.toWatchPath(ApplicationResourceLoader.get()))
108108
.withMessageContaining("Only 'file:' resources are watchable");
109109
}
110110

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/StringToFileConverter.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ class StringToFileConverter implements Converter<String, File> {
3434

3535
@Override
3636
public File convert(String source) {
37-
Resource resource = new ApplicationResourceLoader().getResource(source);
38-
return getFile(resource);
37+
return getFile(ApplicationResourceLoader.get().getResource(source));
3938
}
4039

4140
private File getFile(Resource resource) {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/io/ApplicationResourceLoader.java

Lines changed: 149 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,38 @@
1616

1717
package org.springframework.boot.io;
1818

19+
import java.util.List;
20+
1921
import org.springframework.core.io.ContextResource;
2022
import org.springframework.core.io.DefaultResourceLoader;
2123
import org.springframework.core.io.FileSystemResource;
2224
import org.springframework.core.io.ProtocolResolver;
2325
import org.springframework.core.io.Resource;
26+
import org.springframework.core.io.ResourceLoader;
2427
import org.springframework.core.io.support.SpringFactoriesLoader;
28+
import org.springframework.util.Assert;
29+
import org.springframework.util.StringUtils;
2530

2631
/**
27-
* A {@link DefaultResourceLoader} with any {@link ProtocolResolver ProtocolResolvers}
28-
* registered in a {@code spring.factories} file applied to it. Plain paths without a
29-
* qualifier will resolve to file system resources. This is different from
32+
* Class can be used to obtain {@link ResourceLoader ResourceLoaders} supporting
33+
* additional {@link ProtocolResolver ProtocolResolvers} registered in
34+
* {@code spring.factories}.
35+
* <p>
36+
* When not delegating to an existing resource loader, plain paths without a qualifier
37+
* will resolve to file system resources. This is different from
3038
* {@code DefaultResourceLoader}, which resolves unqualified paths to classpath resources.
3139
*
3240
* @author Scott Frederick
41+
* @author Phillip Webb
3342
* @since 3.3.0
3443
*/
3544
public class ApplicationResourceLoader extends DefaultResourceLoader {
3645

3746
/**
3847
* Create a new {@code ApplicationResourceLoader}.
48+
* @deprecated since 3.4.0 for removal in 3.6.0 in favor of {@link #get()}
3949
*/
50+
@Deprecated(since = "3.4.0", forRemoval = true)
4051
public ApplicationResourceLoader() {
4152
this(null);
4253
}
@@ -46,7 +57,9 @@ public ApplicationResourceLoader() {
4657
* @param classLoader the {@link ClassLoader} to load class path resources with, or
4758
* {@code null} for using the thread context class loader at the time of actual
4859
* resource access
60+
* @deprecated since 3.4.0 for removal in 3.6.0 in favor of {@link #get(ClassLoader)}
4961
*/
62+
@Deprecated(since = "3.4.0", forRemoval = true)
5063
public ApplicationResourceLoader(ClassLoader classLoader) {
5164
super(classLoader);
5265
SpringFactoriesLoader loader = SpringFactoriesLoader.forDefaultResourceLocation(classLoader);
@@ -55,12 +68,107 @@ public ApplicationResourceLoader(ClassLoader classLoader) {
5568

5669
@Override
5770
protected Resource getResourceByPath(String path) {
58-
return new FileSystemContextResource(path);
71+
return new ApplicationResource(path);
72+
}
73+
74+
/**
75+
* Return a {@link ResourceLoader} supporting additional {@link ProtocolResolver
76+
* ProtocolResolvers} registered in {@code spring.factories}. The factories file will
77+
* be resolved using the default class loader at the time this call is made. Resources
78+
* will be resolved using the default class loader at the time they are resolved.
79+
* @return a {@link ResourceLoader} instance
80+
* @since 3.4.0
81+
*/
82+
public static ResourceLoader get() {
83+
return get((ClassLoader) null);
84+
}
85+
86+
/**
87+
* Return a {@link ResourceLoader} supporting additional {@link ProtocolResolver
88+
* ProtocolResolvers} registered in {@code spring.factories}. The factories files and
89+
* resources will be resolved using the specified class loader.
90+
* @param classLoader the class loader to use or {@code null} to use the default class
91+
* loader
92+
* @return a {@link ResourceLoader} instance
93+
* @since 3.4.0
94+
*/
95+
public static ResourceLoader get(ClassLoader classLoader) {
96+
return get(classLoader, SpringFactoriesLoader.forDefaultResourceLocation(classLoader));
97+
}
98+
99+
/**
100+
* Return a {@link ResourceLoader} supporting additional {@link ProtocolResolver
101+
* ProtocolResolvers} registered in {@code spring.factories}.
102+
* @param classLoader the class loader to use or {@code null} to use the default class
103+
* loader
104+
* @param springFactoriesLoader the {@link SpringFactoriesLoader} used to load
105+
* {@link ProtocolResolver ProtocolResolvers}
106+
* @return a {@link ResourceLoader} instance
107+
* @since 3.4.0
108+
*/
109+
public static ResourceLoader get(ClassLoader classLoader, SpringFactoriesLoader springFactoriesLoader) {
110+
return get(ApplicationFileSystemResourceLoader.get(classLoader), springFactoriesLoader);
59111
}
60112

61-
private static class FileSystemContextResource extends FileSystemResource implements ContextResource {
113+
/**
114+
* Return a {@link ResourceLoader} delegating to the given resource loader and
115+
* supporting additional {@link ProtocolResolver ProtocolResolvers} registered in
116+
* {@code spring.factories}. The factories file will be resolved using the default
117+
* class loader at the time this call is made.
118+
* @param resourceLoader the delegate resource loader
119+
* @return a {@link ResourceLoader} instance
120+
* @since 3.4.0
121+
*/
122+
public static ResourceLoader get(ResourceLoader resourceLoader) {
123+
Assert.notNull(resourceLoader, "'resourceLoader' must not be null");
124+
return get(resourceLoader, SpringFactoriesLoader.forDefaultResourceLocation(resourceLoader.getClassLoader()));
125+
}
126+
127+
/**
128+
* Return a {@link ResourceLoader} delegating to the given resource loader and
129+
* supporting additional {@link ProtocolResolver ProtocolResolvers} registered in
130+
* {@code spring.factories}.
131+
* @param resourceLoader the delegate resource loader
132+
* @param springFactoriesLoader the {@link SpringFactoriesLoader} used to load
133+
* {@link ProtocolResolver ProtocolResolvers}
134+
* @return a {@link ResourceLoader} instance
135+
* @since 3.4.0
136+
*/
137+
public static ResourceLoader get(ResourceLoader resourceLoader, SpringFactoriesLoader springFactoriesLoader) {
138+
Assert.notNull(resourceLoader, "'resourceLoader' must not be null");
139+
Assert.notNull(springFactoriesLoader, "'springFactoriesLoader' must not be null");
140+
return new ProtocolResolvingResourceLoader(resourceLoader, springFactoriesLoader.load(ProtocolResolver.class));
141+
}
142+
143+
/**
144+
* Internal {@link ResourceLoader} used to load {@link ApplicationResource}.
145+
*/
146+
private static final class ApplicationFileSystemResourceLoader extends DefaultResourceLoader {
147+
148+
private static final ResourceLoader shared = new ApplicationFileSystemResourceLoader(null);
149+
150+
private ApplicationFileSystemResourceLoader(ClassLoader classLoader) {
151+
super(classLoader);
152+
}
62153

63-
FileSystemContextResource(String path) {
154+
@Override
155+
protected Resource getResourceByPath(String path) {
156+
return new ApplicationResource(path);
157+
}
158+
159+
static ResourceLoader get(ClassLoader classLoader) {
160+
return (classLoader != null) ? new ApplicationFileSystemResourceLoader(classLoader)
161+
: ApplicationFileSystemResourceLoader.shared;
162+
}
163+
164+
}
165+
166+
/**
167+
* An application {@link Resource}.
168+
*/
169+
private static final class ApplicationResource extends FileSystemResource implements ContextResource {
170+
171+
ApplicationResource(String path) {
64172
super(path);
65173
}
66174

@@ -71,4 +179,39 @@ public String getPathWithinContext() {
71179

72180
}
73181

182+
/**
183+
* {@link ResourceLoader} decorator that adds support for additional
184+
* {@link ProtocolResolver ProtocolResolvers}.
185+
*/
186+
private static class ProtocolResolvingResourceLoader implements ResourceLoader {
187+
188+
private final ResourceLoader resourceLoader;
189+
190+
private final List<ProtocolResolver> protocolResolvers;
191+
192+
ProtocolResolvingResourceLoader(ResourceLoader resourceLoader, List<ProtocolResolver> protocolResolvers) {
193+
this.resourceLoader = resourceLoader;
194+
this.protocolResolvers = protocolResolvers;
195+
}
196+
197+
@Override
198+
public Resource getResource(String location) {
199+
if (StringUtils.hasLength(location)) {
200+
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
201+
Resource resource = protocolResolver.resolve(location, this);
202+
if (resource != null) {
203+
return resource;
204+
}
205+
}
206+
}
207+
return this.resourceLoader.getResource(location);
208+
}
209+
210+
@Override
211+
public ClassLoader getClassLoader() {
212+
return this.resourceLoader.getClassLoader();
213+
}
214+
215+
}
216+
74217
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/io/ProtocolResolverApplicationContextInitializer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
import org.springframework.core.io.support.SpringFactoriesLoader;
2525

2626
/**
27-
* {@link ApplicationContextInitializer} that adds all {@link ProtocolResolver}s
28-
* registered in a {@code spring.factories} file.
27+
* {@link ApplicationContextInitializer} that adds all {@link ProtocolResolver
28+
* ProtocolResolvers} registered in a {@code spring.factories} file.
2929
*
3030
* @author Scott Frederick
3131
*/

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/java/JavaLoggingSystem.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ protected void loadConfiguration(LoggingInitializationContext initializationCont
103103
protected void loadConfiguration(String location, LogFile logFile) {
104104
Assert.notNull(location, "Location must not be null");
105105
try {
106-
Resource resource = new ApplicationResourceLoader().getResource(location);
106+
Resource resource = ApplicationResourceLoader.get().getResource(location);
107107
String configuration = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream()));
108108
if (logFile != null) {
109109
configuration = configuration.replace("${LOG_FILE}", StringUtils.cleanPath(logFile.toString()));

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ protected void loadConfiguration(String location, LogFile logFile, List<String>
275275
}
276276

277277
private Configuration load(String location, LoggerContext context) throws IOException {
278-
Resource resource = new ApplicationResourceLoader().getResource(location);
278+
Resource resource = ApplicationResourceLoader.get().getResource(location);
279279
ConfigurationSource source = getConfigurationSource(resource);
280280
return ConfigurationFactory.getInstance().getConfiguration(context, source);
281281
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ protected void loadConfiguration(LoggingInitializationContext initializationCont
258258
applySystemProperties(initializationContext.getEnvironment(), logFile);
259259
}
260260
try {
261-
Resource resource = new ApplicationResourceLoader().getResource(location);
261+
Resource resource = ApplicationResourceLoader.get().getResource(location);
262262
configureByResourceUrl(initializationContext, loggerContext, resource.getURL());
263263
}
264264
catch (Exception ex) {

0 commit comments

Comments
 (0)