From 9434cb0e22ad2a5be335d6fad77414bd730cb74a Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 2 Sep 2019 15:46:44 +0200 Subject: [PATCH] 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 --- .../spring-boot-dependencies/pom.xml | 2 +- ...assLoaderFilesResourcePatternResolver.java | 68 ++++++++++--------- ...aderFilesResourcePatternResolverTests.java | 24 +++++++ 3 files changed, 60 insertions(+), 34 deletions(-) diff --git a/spring-boot-project/spring-boot-dependencies/pom.xml b/spring-boot-project/spring-boot-dependencies/pom.xml index 4247b8a93a..29791145aa 100644 --- a/spring-boot-project/spring-boot-dependencies/pom.xml +++ b/spring-boot-project/spring-boot-dependencies/pom.xml @@ -165,7 +165,7 @@ 1.23 7.7.2 - 5.1.9.RELEASE + 5.1.10.BUILD-SNAPSHOT 2.1.8.RELEASE 4.1.2.RELEASE 2.0.6.RELEASE 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 d915ac01eb..5cceb7bc60 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 { - 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> protocolResolvers; + + ApplicationContextResourceLoader(Supplier> protocolResolvers) { + this.protocolResolvers = protocolResolvers; + } + + @Override + public Collection 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> 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 7cdfcedc5f..a2a9156330 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 @@ -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);