Skip to content

Commit 9434cb0

Browse files
committed
Keep a live reference of protocol resolvers rather than copying them
This commit makes sure that any subsequent call on addProtocolResolver on the context will impact the ResourceLoader implementation that DevTools sets on the context. This makes sure that any custom ProtocolResolver that is set later in the lifecycle is taken into account. Closes spring-projectsgh-17214
1 parent a642421 commit 9434cb0

File tree

3 files changed

+60
-34
lines changed

3 files changed

+60
-34
lines changed

spring-boot-project/spring-boot-dependencies/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@
165165
<snakeyaml.version>1.23</snakeyaml.version>
166166
<solr.version>7.7.2</solr.version>
167167
<!-- deprecated in favor of "spring-framework.version" -->
168-
<spring.version>5.1.9.RELEASE</spring.version>
168+
<spring.version>5.1.10.BUILD-SNAPSHOT</spring.version>
169169
<spring-amqp.version>2.1.8.RELEASE</spring-amqp.version>
170170
<spring-batch.version>4.1.2.RELEASE</spring-batch.version>
171171
<spring-cloud-connectors.version>2.0.6.RELEASE</spring-cloud-connectors.version>

spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolver.java

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,18 @@
2222
import java.net.MalformedURLException;
2323
import java.net.URL;
2424
import java.util.ArrayList;
25+
import java.util.Collection;
2526
import java.util.List;
2627
import java.util.Map.Entry;
28+
import java.util.function.Supplier;
2729

2830
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile;
2931
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile.Kind;
3032
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFileURLStreamHandler;
3133
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles;
3234
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles.SourceFolder;
3335
import org.springframework.context.ApplicationContext;
36+
import org.springframework.context.support.AbstractApplicationContext;
3437
import org.springframework.core.io.AbstractResource;
3538
import org.springframework.core.io.DefaultResourceLoader;
3639
import org.springframework.core.io.ProtocolResolver;
@@ -67,7 +70,8 @@ final class ClassLoaderFilesResourcePatternResolver implements ResourcePatternRe
6770

6871
private final ClassLoaderFiles classLoaderFiles;
6972

70-
ClassLoaderFilesResourcePatternResolver(ApplicationContext applicationContext, ClassLoaderFiles classLoaderFiles) {
73+
ClassLoaderFilesResourcePatternResolver(AbstractApplicationContext applicationContext,
74+
ClassLoaderFiles classLoaderFiles) {
7175
this.classLoaderFiles = classLoaderFiles;
7276
this.patternResolverDelegate = getResourcePatternResolverFactory()
7377
.getResourcePatternResolver(applicationContext, retrieveResourceLoader(applicationContext));
@@ -195,28 +199,11 @@ public InputStream getInputStream() throws IOException {
195199
*/
196200
private static class ResourcePatternResolverFactory {
197201

198-
public ResourcePatternResolver getResourcePatternResolver(ApplicationContext applicationContext,
202+
public ResourcePatternResolver getResourcePatternResolver(AbstractApplicationContext applicationContext,
199203
ResourceLoader resourceLoader) {
200-
if (resourceLoader == null) {
201-
resourceLoader = new DefaultResourceLoader();
202-
copyProtocolResolvers(applicationContext, resourceLoader);
203-
}
204-
return new PathMatchingResourcePatternResolver(resourceLoader);
205-
}
206-
207-
protected final void copyProtocolResolvers(ApplicationContext applicationContext,
208-
ResourceLoader resourceLoader) {
209-
if (applicationContext instanceof DefaultResourceLoader
210-
&& resourceLoader instanceof DefaultResourceLoader) {
211-
copyProtocolResolvers((DefaultResourceLoader) applicationContext,
212-
(DefaultResourceLoader) resourceLoader);
213-
}
214-
}
215-
216-
protected final void copyProtocolResolvers(DefaultResourceLoader source, DefaultResourceLoader destination) {
217-
for (ProtocolResolver resolver : source.getProtocolResolvers()) {
218-
destination.addProtocolResolver(resolver);
219-
}
204+
ResourceLoader targetResourceLoader = (resourceLoader != null) ? resourceLoader
205+
: new ApplicationContextResourceLoader(applicationContext::getProtocolResolvers);
206+
return new PathMatchingResourcePatternResolver(targetResourceLoader);
220207
}
221208

222209
}
@@ -228,22 +215,35 @@ protected final void copyProtocolResolvers(DefaultResourceLoader source, Default
228215
private static class WebResourcePatternResolverFactory extends ResourcePatternResolverFactory {
229216

230217
@Override
231-
public ResourcePatternResolver getResourcePatternResolver(ApplicationContext applicationContext,
218+
public ResourcePatternResolver getResourcePatternResolver(AbstractApplicationContext applicationContext,
232219
ResourceLoader resourceLoader) {
233220
if (applicationContext instanceof WebApplicationContext) {
234-
return getResourcePatternResolver((WebApplicationContext) applicationContext, resourceLoader);
221+
return getServletContextResourcePatternResolver(applicationContext, resourceLoader);
235222
}
236223
return super.getResourcePatternResolver(applicationContext, resourceLoader);
237224
}
238225

239-
private ResourcePatternResolver getResourcePatternResolver(WebApplicationContext applicationContext,
240-
ResourceLoader resourceLoader) {
241-
if (resourceLoader == null) {
242-
resourceLoader = new WebApplicationContextResourceLoader(applicationContext);
243-
copyProtocolResolvers(applicationContext, resourceLoader);
244-
}
245-
return new ServletContextResourcePatternResolver(resourceLoader);
226+
private ResourcePatternResolver getServletContextResourcePatternResolver(
227+
AbstractApplicationContext applicationContext, ResourceLoader resourceLoader) {
228+
ResourceLoader targetResourceLoader = (resourceLoader != null) ? resourceLoader
229+
: new WebApplicationContextResourceLoader(applicationContext::getProtocolResolvers,
230+
(WebApplicationContext) applicationContext);
231+
return new ServletContextResourcePatternResolver(targetResourceLoader);
232+
}
233+
234+
}
235+
236+
private static class ApplicationContextResourceLoader extends DefaultResourceLoader {
246237

238+
private final Supplier<Collection<ProtocolResolver>> protocolResolvers;
239+
240+
ApplicationContextResourceLoader(Supplier<Collection<ProtocolResolver>> protocolResolvers) {
241+
this.protocolResolvers = protocolResolvers;
242+
}
243+
244+
@Override
245+
public Collection<ProtocolResolver> getProtocolResolvers() {
246+
return this.protocolResolvers.get();
247247
}
248248

249249
}
@@ -252,11 +252,13 @@ private ResourcePatternResolver getResourcePatternResolver(WebApplicationContext
252252
* {@link ResourceLoader} that optionally supports {@link ServletContextResource
253253
* ServletContextResources}.
254254
*/
255-
private static class WebApplicationContextResourceLoader extends DefaultResourceLoader {
255+
private static class WebApplicationContextResourceLoader extends ApplicationContextResourceLoader {
256256

257257
private final WebApplicationContext applicationContext;
258258

259-
WebApplicationContextResourceLoader(WebApplicationContext applicationContext) {
259+
WebApplicationContextResourceLoader(Supplier<Collection<ProtocolResolver>> protocolResolvers,
260+
WebApplicationContext applicationContext) {
261+
super(protocolResolvers);
260262
this.applicationContext = applicationContext;
261263
}
262264

spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolverTests.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,18 @@ public void customProtocolResolverIsUsedInNonWebApplication() {
134134
verify(resolver).resolve(eq("foo:some-file.txt"), any(ResourceLoader.class));
135135
}
136136

137+
@Test
138+
public void customProtocolResolverRegisteredAfterCreationIsUsedInNonWebApplication() {
139+
GenericApplicationContext context = new GenericApplicationContext();
140+
Resource resource = mock(Resource.class);
141+
this.resolver = new ClassLoaderFilesResourcePatternResolver(context, this.files);
142+
ProtocolResolver resolver = mockProtocolResolver("foo:some-file.txt", resource);
143+
context.addProtocolResolver(resolver);
144+
Resource actual = this.resolver.getResource("foo:some-file.txt");
145+
assertThat(actual).isSameAs(resource);
146+
verify(resolver).resolve(eq("foo:some-file.txt"), any(ResourceLoader.class));
147+
}
148+
137149
@Test
138150
public void customResourceLoaderIsUsedInWebApplication() {
139151
GenericWebApplicationContext context = new GenericWebApplicationContext(new MockServletContext());
@@ -156,6 +168,18 @@ public void customProtocolResolverIsUsedInWebApplication() {
156168
verify(resolver).resolve(eq("foo:some-file.txt"), any(ResourceLoader.class));
157169
}
158170

171+
@Test
172+
public void customProtocolResolverRegisteredAfterCreationIsUsedInWebApplication() {
173+
GenericWebApplicationContext context = new GenericWebApplicationContext(new MockServletContext());
174+
Resource resource = mock(Resource.class);
175+
this.resolver = new ClassLoaderFilesResourcePatternResolver(context, this.files);
176+
ProtocolResolver resolver = mockProtocolResolver("foo:some-file.txt", resource);
177+
context.addProtocolResolver(resolver);
178+
Resource actual = this.resolver.getResource("foo:some-file.txt");
179+
assertThat(actual).isSameAs(resource);
180+
verify(resolver).resolve(eq("foo:some-file.txt"), any(ResourceLoader.class));
181+
}
182+
159183
private ProtocolResolver mockProtocolResolver(String path, Resource resource) {
160184
ProtocolResolver resolver = mock(ProtocolResolver.class);
161185
given(resolver.resolve(eq(path), any(ResourceLoader.class))).willReturn(resource);

0 commit comments

Comments
 (0)