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
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user