diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolver.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolver.java index fbb3b2eed58..82f0dea17f1 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolver.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolver.java @@ -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 { - ResourcePatternResolver getResourcePatternResolver(ApplicationContext applicationContext, + 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,21 +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> protocolResolvers; + + ApplicationContextResourceLoader(Supplier> protocolResolvers) { + this.protocolResolvers = protocolResolvers; + } + + @Override + public Collection getProtocolResolvers() { + return this.protocolResolvers.get(); } } @@ -251,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> protocolResolvers, + WebApplicationContext applicationContext) { + super(protocolResolvers); this.applicationContext = applicationContext; } diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolverTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolverTests.java index 964c22b7f96..a71e8ff87f9 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolverTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolverTests.java @@ -127,6 +127,18 @@ class ClassLoaderFilesResourcePatternResolverTests { verify(resolver).resolve(eq("foo:some-file.txt"), any(ResourceLoader.class)); } + @Test + 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 void customResourceLoaderIsUsedInWebApplication() { GenericWebApplicationContext context = new GenericWebApplicationContext(new MockServletContext()); @@ -149,6 +161,18 @@ class ClassLoaderFilesResourcePatternResolverTests { verify(resolver).resolve(eq("foo:some-file.txt"), any(ResourceLoader.class)); } + @Test + 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);