diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolver.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolver.java
index 51f24e31314..64e1ea1b4f2 100644
--- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolver.java
+++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolver.java
@@ -29,32 +29,54 @@ import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile.Kin
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFileURLStreamHandler;
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.core.io.AbstractResource;
+import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.UrlResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.AntPathMatcher;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.PathMatcher;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.support.ServletContextResource;
+import org.springframework.web.context.support.ServletContextResourcePatternResolver;
/**
* A {@code ResourcePatternResolver} that considers {@link ClassLoaderFiles} when
* resolving resources.
*
* @author Andy Wilkinson
+ * @author Phillip Webb
*/
final class ClassLoaderFilesResourcePatternResolver implements ResourcePatternResolver {
private static final String[] LOCATION_PATTERN_PREFIXES = { CLASSPATH_ALL_URL_PREFIX,
CLASSPATH_URL_PREFIX };
- private final ResourcePatternResolver delegate = new PathMatchingResourcePatternResolver();
+ private static final String WEB_CONTEXT_CLASS = "org.springframework.web.context."
+ + "WebApplicationContext";
- private final AntPathMatcher antPathMatcher = new AntPathMatcher();
+ private final ResourcePatternResolver delegate;
+
+ private final PathMatcher antPathMatcher = new AntPathMatcher();
private final ClassLoaderFiles classLoaderFiles;
- ClassLoaderFilesResourcePatternResolver(ClassLoaderFiles classLoaderFiles) {
+ ClassLoaderFilesResourcePatternResolver(ApplicationContext applicationContext,
+ ClassLoaderFiles classLoaderFiles) {
this.classLoaderFiles = classLoaderFiles;
+ this.delegate = getResourcePatternResolverFactory()
+ .getResourcePatternResolver(applicationContext);
+ }
+
+ private ResourcePatternResolverFactory getResourcePatternResolverFactory() {
+ if (ClassUtils.isPresent(WEB_CONTEXT_CLASS, null)) {
+ return new WebResourcePatternResolverFactory();
+ }
+ return new ResourcePatternResolverFactory();
}
@Override
@@ -137,7 +159,7 @@ final class ClassLoaderFilesResourcePatternResolver implements ResourcePatternRe
* A {@link Resource} that represents a {@link ClassLoaderFile} that has been
* {@link Kind#DELETED deleted}.
*/
- private final class DeletedClassLoaderFileResource extends AbstractResource {
+ static final class DeletedClassLoaderFileResource extends AbstractResource {
private final String name;
@@ -162,4 +184,60 @@ final class ClassLoaderFilesResourcePatternResolver implements ResourcePatternRe
}
+ /**
+ * Factory used to create the {@link ResourcePatternResolver} delegate.
+ */
+ private static class ResourcePatternResolverFactory {
+
+ public ResourcePatternResolver getResourcePatternResolver(
+ ApplicationContext applicationContext) {
+ return new PathMatchingResourcePatternResolver();
+ }
+
+ }
+
+ /**
+ * {@link ResourcePatternResolverFactory} to be used when the classloader can access
+ * {@link WebApplicationContext}.
+ */
+ private static class WebResourcePatternResolverFactory
+ extends ResourcePatternResolverFactory {
+
+ @Override
+ public ResourcePatternResolver getResourcePatternResolver(
+ ApplicationContext applicationContext) {
+ if (applicationContext instanceof WebApplicationContext) {
+ return new ServletContextResourcePatternResolver(
+ new WebApplicationContextResourceLoader(
+ (WebApplicationContext) applicationContext));
+ }
+ return super.getResourcePatternResolver(applicationContext);
+ }
+
+ }
+
+ /**
+ * {@link ResourceLoader} that optionally supports {@link ServletContextResource
+ * ServletContextResources}.
+ */
+ private static class WebApplicationContextResourceLoader
+ extends DefaultResourceLoader {
+
+ private final WebApplicationContext applicationContext;
+
+ WebApplicationContextResourceLoader(WebApplicationContext applicationContext) {
+ this.applicationContext = applicationContext;
+ }
+
+ @Override
+ protected Resource getResourceByPath(String path) {
+ if (this.applicationContext.getServletContext() != null) {
+ return new ServletContextResource(
+ this.applicationContext.getServletContext(), path);
+ }
+ return super.getResourceByPath(path);
+ }
+
+ }
+
}
diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java
index c66d9bb8faa..04306f2761d 100644
--- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java
+++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java
@@ -51,6 +51,7 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
@@ -426,8 +427,9 @@ public class Restarter {
}
private void prepare(GenericApplicationContext applicationContext) {
- applicationContext.setResourceLoader(
- new ClassLoaderFilesResourcePatternResolver(this.classLoaderFiles));
+ ResourceLoader resourceLoader = new ClassLoaderFilesResourcePatternResolver(
+ applicationContext, this.classLoaderFiles);
+ applicationContext.setResourceLoader(resourceLoader);
}
private LeakSafeThread getLeakSafeThread() {
diff --git a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolverTests.java b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolverTests.java
new file mode 100644
index 00000000000..c709ef822b4
--- /dev/null
+++ b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolverTests.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2012-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.devtools.restart;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import org.springframework.boot.devtools.restart.ClassLoaderFilesResourcePatternResolver.DeletedClassLoaderFileResource;
+import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile;
+import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile.Kind;
+import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.mock.web.MockServletContext;
+import org.springframework.util.FileCopyUtils;
+import org.springframework.web.context.support.GenericWebApplicationContext;
+import org.springframework.web.context.support.ServletContextResource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link ClassLoaderFilesResourcePatternResolver}.
+ *
+ * @author Phillip Webb
+ */
+public class ClassLoaderFilesResourcePatternResolverTests {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ private ClassLoaderFiles files;
+
+ private ClassLoaderFilesResourcePatternResolver resolver;
+
+ @Before
+ public void setup() {
+ this.files = new ClassLoaderFiles();
+ this.resolver = new ClassLoaderFilesResourcePatternResolver(
+ new GenericApplicationContext(), this.files);
+ }
+
+ @Test
+ public void getClassLoaderShouldReturnClassLoader() throws Exception {
+ assertThat(this.resolver.getClassLoader()).isNotNull();
+ }
+
+ @Test
+ public void getResourceShouldReturnResource() throws Exception {
+ Resource resource = this.resolver.getResource("index.html");
+ assertThat(resource).isNotNull().isInstanceOf(ClassPathResource.class);
+ }
+
+ @Test
+ public void getResourceWhenHasServeletContextShouldReturnServletResource()
+ throws Exception {
+ GenericWebApplicationContext context = new GenericWebApplicationContext(
+ new MockServletContext());
+ this.resolver = new ClassLoaderFilesResourcePatternResolver(context, this.files);
+ Resource resource = this.resolver.getResource("index.html");
+ assertThat(resource).isNotNull().isInstanceOf(ServletContextResource.class);
+ }
+
+ @Test
+ public void getResourceWhenDeletedShouldReturnDeletedResource() throws Exception {
+ File folder = this.temp.newFolder();
+ File file = createFile(folder, "name.class");
+ this.files.addFile(folder.getName(), "name.class",
+ new ClassLoaderFile(Kind.DELETED, null));
+ Resource resource = this.resolver.getResource("file:" + file.getAbsolutePath());
+ assertThat(resource).isNotNull()
+ .isInstanceOf(DeletedClassLoaderFileResource.class);
+ }
+
+ @Test
+ public void getResourcesShouldReturnResources() throws Exception {
+ File folder = this.temp.newFolder();
+ createFile(folder, "name.class");
+ Resource[] resources = this.resolver
+ .getResources("file:" + folder.getAbsolutePath() + "/**");
+ assertThat(resources).isNotEmpty();
+ }
+
+ @Test
+ public void getResourcesWhenDeletedShouldFilterDeleted() throws Exception {
+ File folder = this.temp.newFolder();
+ createFile(folder, "name.class");
+ this.files.addFile(folder.getName(), "name.class",
+ new ClassLoaderFile(Kind.DELETED, null));
+ Resource[] resources = this.resolver
+ .getResources("file:" + folder.getAbsolutePath() + "/**");
+ assertThat(resources).isEmpty();
+ }
+
+ private File createFile(File folder, String name) throws IOException {
+ File file = new File(folder, name);
+ FileCopyUtils.copy("test".getBytes(), file);
+ return file;
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-war/pom.xml b/spring-boot-samples/spring-boot-sample-war/pom.xml
index fee8d09f598..7a44a9cdfb2 100644
--- a/spring-boot-samples/spring-boot-sample-war/pom.xml
+++ b/spring-boot-samples/spring-boot-sample-war/pom.xml
@@ -42,6 +42,13 @@
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ provided
+
+
org.springframework.boot
spring-boot-starter-test