diff --git a/module/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolver.java b/module/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolver.java index 7541b0947c4..27cc715ce83 100644 --- a/module/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolver.java +++ b/module/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolver.java @@ -33,6 +33,7 @@ import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile; import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile.Kind; import org.springframework.boot.devtools.restart.classloader.ClassLoaderFileURLStreamHandler; import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles; +import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles.SourceDirectory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.core.io.AbstractResource; @@ -58,6 +59,7 @@ import org.springframework.web.context.support.ServletContextResourcePatternReso * @author Andy Wilkinson * @author Phillip Webb * @author Stephane Nicoll + * @author DongHoon Lee */ final class ClassLoaderFilesResourcePatternResolver implements ResourcePatternResolver { @@ -146,22 +148,35 @@ final class ClassLoaderFilesResourcePatternResolver implements ResourcePatternRe } private boolean isDeleted(Resource resource) { - for (Entry entry : this.classLoaderFiles.getFileEntries()) { - try { - String name = entry.getKey(); - ClassLoaderFile file = entry.getValue(); - if (file.getKind() == Kind.DELETED && resource.exists() - && resource.getURI().toString().endsWith(name)) { + String uri = null; + for (SourceDirectory sourceDirectory : this.classLoaderFiles.getSourceDirectories()) { + for (Entry entry : sourceDirectory.getFilesEntrySet()) { + if (entry.getValue().getKind() != Kind.DELETED) { + continue; + } + if (uri == null) { + if (!resource.exists()) { + return false; + } + uri = getUri(resource); + } + if (uri.endsWith(entry.getKey())) { return true; } } - catch (IOException ex) { - throw new IllegalStateException("Failed to retrieve URI from '" + resource + "'", ex); - } } return false; } + private String getUri(Resource resource) { + try { + return resource.getURI().toString(); + } + catch (IOException ex) { + throw new IllegalStateException("Failed to retrieve URI from '" + resource + "'", ex); + } + } + /** * A {@link Resource} that represents a {@link ClassLoaderFile} that has been * {@link Kind#DELETED deleted}. diff --git a/module/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolverTests.java b/module/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolverTests.java index 998b73c59bd..68b9ba6f6a3 100644 --- a/module/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolverTests.java +++ b/module/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolverTests.java @@ -18,6 +18,7 @@ package org.springframework.boot.devtools.restart; import java.io.File; import java.io.IOException; +import java.net.URI; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -43,6 +44,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; /** * Tests for {@link ClassLoaderFilesResourcePatternResolver}. @@ -50,6 +52,7 @@ import static org.mockito.Mockito.mock; * @author Phillip Webb * @author Andy Wilkinson * @author Stephane Nicoll + * @author DongHoon Lee */ class ClassLoaderFilesResourcePatternResolverTests { @@ -90,6 +93,41 @@ class ClassLoaderFilesResourcePatternResolverTests { assertThat(resource).isInstanceOf(DeletedClassLoaderFileResource.class); } + @Test + void getResourceWhenDeletedInAnotherSourceDirectoryShouldReturnDeletedResource(@TempDir File directory) + throws Exception { + File file = createFile(directory, "name.class"); + this.files.addFile("one", "other.class", new ClassLoaderFile(Kind.ADDED, new byte[0])); + this.files.addFile("two", "name.class", new ClassLoaderFile(Kind.DELETED, null)); + Resource resource = this.resolver.getResource("file:" + file.getAbsolutePath()); + assertThat(resource).isInstanceOf(DeletedClassLoaderFileResource.class); + } + + @Test + void getResourceWhenManyFilesAreDeletedShouldCheckResourceOnlyOnce() throws Exception { + Resource resource = mock(Resource.class); + given(resource.exists()).willReturn(true); + given(resource.getURI()).willReturn(URI.create("file:/app/classes/three.class")); + this.resolver = createResolverResolving("foo:some-file.txt", resource); + this.files.addFile("one", "one.class", new ClassLoaderFile(Kind.DELETED, null)); + this.files.addFile("one", "two.class", new ClassLoaderFile(Kind.DELETED, null)); + this.files.addFile("two", "three.class", new ClassLoaderFile(Kind.DELETED, null)); + assertThat(this.resolver.getResource("foo:some-file.txt")).isInstanceOf(DeletedClassLoaderFileResource.class); + then(resource).should().exists(); + then(resource).should().getURI(); + } + + @Test + void getResourceWhenNoFileIsDeletedShouldNotCheckResource() throws Exception { + Resource resource = mock(Resource.class); + this.resolver = createResolverResolving("foo:some-file.txt", resource); + this.files.addFile("one", "one.class", new ClassLoaderFile(Kind.ADDED, new byte[0])); + this.files.addFile("two", "two.class", new ClassLoaderFile(Kind.MODIFIED, new byte[0])); + assertThat(this.resolver.getResource("foo:some-file.txt")).isSameAs(resource); + then(resource).should(never()).exists(); + then(resource).should(never()).getURI(); + } + @Test void getResourcesShouldReturnResources(@TempDir File directory) throws Exception { File file = createFile(directory, "name.class"); @@ -173,6 +211,12 @@ class ClassLoaderFilesResourcePatternResolverTests { then(resolver).should().resolve(eq("foo:some-file.txt"), any(ResourceLoader.class)); } + private ClassLoaderFilesResourcePatternResolver createResolverResolving(String location, Resource resource) { + GenericApplicationContext context = new GenericApplicationContext(); + context.addProtocolResolver(mockProtocolResolver(location, resource)); + return new ClassLoaderFilesResourcePatternResolver(context, this.files); + } + private ProtocolResolver mockProtocolResolver(String path, Resource resource) { ProtocolResolver resolver = mock(ProtocolResolver.class); given(resolver.resolve(eq(path), any(ResourceLoader.class))).willReturn(resource);