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 gh-17214
pull/18464/head
Stephane Nicoll 5 years ago
parent a642421661
commit 9434cb0e22

@ -165,7 +165,7 @@
<snakeyaml.version>1.23</snakeyaml.version>
<solr.version>7.7.2</solr.version>
<!-- deprecated in favor of "spring-framework.version" -->
<spring.version>5.1.9.RELEASE</spring.version>
<spring.version>5.1.10.BUILD-SNAPSHOT</spring.version>
<spring-amqp.version>2.1.8.RELEASE</spring-amqp.version>
<spring-batch.version>4.1.2.RELEASE</spring-batch.version>
<spring-cloud-connectors.version>2.0.6.RELEASE</spring-cloud-connectors.version>

@ -22,8 +22,10 @@ import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
import java.util.function.Supplier;
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile;
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile.Kind;
@ -31,6 +33,7 @@ import org.springframework.boot.devtools.restart.classloader.ClassLoaderFileURLS
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles;
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles.SourceFolder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.io.AbstractResource;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ProtocolResolver;
@ -67,7 +70,8 @@ final class ClassLoaderFilesResourcePatternResolver implements ResourcePatternRe
private final ClassLoaderFiles classLoaderFiles;
ClassLoaderFilesResourcePatternResolver(ApplicationContext applicationContext, ClassLoaderFiles classLoaderFiles) {
ClassLoaderFilesResourcePatternResolver(AbstractApplicationContext applicationContext,
ClassLoaderFiles classLoaderFiles) {
this.classLoaderFiles = classLoaderFiles;
this.patternResolverDelegate = getResourcePatternResolverFactory()
.getResourcePatternResolver(applicationContext, retrieveResourceLoader(applicationContext));
@ -195,28 +199,11 @@ final class ClassLoaderFilesResourcePatternResolver implements ResourcePatternRe
*/
private static class ResourcePatternResolverFactory {
public ResourcePatternResolver getResourcePatternResolver(ApplicationContext applicationContext,
public ResourcePatternResolver getResourcePatternResolver(AbstractApplicationContext applicationContext,
ResourceLoader resourceLoader) {
if (resourceLoader == null) {
resourceLoader = new DefaultResourceLoader();
copyProtocolResolvers(applicationContext, resourceLoader);
}
return new PathMatchingResourcePatternResolver(resourceLoader);
}
protected final void copyProtocolResolvers(ApplicationContext applicationContext,
ResourceLoader resourceLoader) {
if (applicationContext instanceof DefaultResourceLoader
&& resourceLoader instanceof DefaultResourceLoader) {
copyProtocolResolvers((DefaultResourceLoader) applicationContext,
(DefaultResourceLoader) resourceLoader);
}
}
protected final void copyProtocolResolvers(DefaultResourceLoader source, DefaultResourceLoader destination) {
for (ProtocolResolver resolver : source.getProtocolResolvers()) {
destination.addProtocolResolver(resolver);
}
ResourceLoader targetResourceLoader = (resourceLoader != null) ? resourceLoader
: new ApplicationContextResourceLoader(applicationContext::getProtocolResolvers);
return new PathMatchingResourcePatternResolver(targetResourceLoader);
}
}
@ -228,22 +215,35 @@ final class ClassLoaderFilesResourcePatternResolver implements ResourcePatternRe
private static class WebResourcePatternResolverFactory extends ResourcePatternResolverFactory {
@Override
public ResourcePatternResolver getResourcePatternResolver(ApplicationContext applicationContext,
public ResourcePatternResolver getResourcePatternResolver(AbstractApplicationContext applicationContext,
ResourceLoader resourceLoader) {
if (applicationContext instanceof WebApplicationContext) {
return getResourcePatternResolver((WebApplicationContext) applicationContext, resourceLoader);
return getServletContextResourcePatternResolver(applicationContext, resourceLoader);
}
return super.getResourcePatternResolver(applicationContext, resourceLoader);
}
private ResourcePatternResolver getResourcePatternResolver(WebApplicationContext applicationContext,
ResourceLoader resourceLoader) {
if (resourceLoader == null) {
resourceLoader = new WebApplicationContextResourceLoader(applicationContext);
copyProtocolResolvers(applicationContext, resourceLoader);
}
return new ServletContextResourcePatternResolver(resourceLoader);
private ResourcePatternResolver getServletContextResourcePatternResolver(
AbstractApplicationContext applicationContext, ResourceLoader resourceLoader) {
ResourceLoader targetResourceLoader = (resourceLoader != null) ? resourceLoader
: new WebApplicationContextResourceLoader(applicationContext::getProtocolResolvers,
(WebApplicationContext) applicationContext);
return new ServletContextResourcePatternResolver(targetResourceLoader);
}
}
private static class ApplicationContextResourceLoader extends DefaultResourceLoader {
private final Supplier<Collection<ProtocolResolver>> protocolResolvers;
ApplicationContextResourceLoader(Supplier<Collection<ProtocolResolver>> protocolResolvers) {
this.protocolResolvers = protocolResolvers;
}
@Override
public Collection<ProtocolResolver> getProtocolResolvers() {
return this.protocolResolvers.get();
}
}
@ -252,11 +252,13 @@ final class ClassLoaderFilesResourcePatternResolver implements ResourcePatternRe
* {@link ResourceLoader} that optionally supports {@link ServletContextResource
* ServletContextResources}.
*/
private static class WebApplicationContextResourceLoader extends DefaultResourceLoader {
private static class WebApplicationContextResourceLoader extends ApplicationContextResourceLoader {
private final WebApplicationContext applicationContext;
WebApplicationContextResourceLoader(WebApplicationContext applicationContext) {
WebApplicationContextResourceLoader(Supplier<Collection<ProtocolResolver>> protocolResolvers,
WebApplicationContext applicationContext) {
super(protocolResolvers);
this.applicationContext = applicationContext;
}

@ -134,6 +134,18 @@ public class ClassLoaderFilesResourcePatternResolverTests {
verify(resolver).resolve(eq("foo:some-file.txt"), any(ResourceLoader.class));
}
@Test
public void customProtocolResolverRegisteredAfterCreationIsUsedInNonWebApplication() {
GenericApplicationContext context = new GenericApplicationContext();
Resource resource = mock(Resource.class);
this.resolver = new ClassLoaderFilesResourcePatternResolver(context, this.files);
ProtocolResolver resolver = mockProtocolResolver("foo:some-file.txt", resource);
context.addProtocolResolver(resolver);
Resource actual = this.resolver.getResource("foo:some-file.txt");
assertThat(actual).isSameAs(resource);
verify(resolver).resolve(eq("foo:some-file.txt"), any(ResourceLoader.class));
}
@Test
public void customResourceLoaderIsUsedInWebApplication() {
GenericWebApplicationContext context = new GenericWebApplicationContext(new MockServletContext());
@ -156,6 +168,18 @@ public class ClassLoaderFilesResourcePatternResolverTests {
verify(resolver).resolve(eq("foo:some-file.txt"), any(ResourceLoader.class));
}
@Test
public void customProtocolResolverRegisteredAfterCreationIsUsedInWebApplication() {
GenericWebApplicationContext context = new GenericWebApplicationContext(new MockServletContext());
Resource resource = mock(Resource.class);
this.resolver = new ClassLoaderFilesResourcePatternResolver(context, this.files);
ProtocolResolver resolver = mockProtocolResolver("foo:some-file.txt", resource);
context.addProtocolResolver(resolver);
Resource actual = this.resolver.getResource("foo:some-file.txt");
assertThat(actual).isSameAs(resource);
verify(resolver).resolve(eq("foo:some-file.txt"), any(ResourceLoader.class));
}
private ProtocolResolver mockProtocolResolver(String path, Resource resource) {
ProtocolResolver resolver = mock(ProtocolResolver.class);
given(resolver.resolve(eq(path), any(ResourceLoader.class))).willReturn(resource);

Loading…
Cancel
Save