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 4d827d9810ee..da8aa7392574 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 @@ -123,15 +123,13 @@ public Resource[] getResources(String locationPattern) throws IOException { private List getAdditionalResources(String locationPattern) throws MalformedURLException { List additionalResources = new ArrayList<>(); String trimmedLocationPattern = trimLocationPattern(locationPattern); - for (SourceDirectory sourceDirectory : this.classLoaderFiles.getSourceDirectories()) { - for (Entry entry : sourceDirectory.getFilesEntrySet()) { - String name = entry.getKey(); - ClassLoaderFile file = entry.getValue(); - if (file.getKind() != Kind.DELETED && this.antPathMatcher.match(trimmedLocationPattern, name)) { - URL url = new URL("reloaded", null, -1, "/" + name, new ClassLoaderFileURLStreamHandler(file)); - UrlResource resource = new UrlResource(url); - additionalResources.add(resource); - } + for (Entry entry : this.classLoaderFiles.getFileEntries()) { + String name = entry.getKey(); + ClassLoaderFile file = entry.getValue(); + if (file.getKind() != Kind.DELETED && this.antPathMatcher.match(trimmedLocationPattern, name)) { + URL url = new URL("reloaded", null, -1, "/" + name, new ClassLoaderFileURLStreamHandler(file)); + UrlResource resource = new UrlResource(url); + additionalResources.add(resource); } } return additionalResources; @@ -147,20 +145,18 @@ private String trimLocationPattern(String pattern) { } private boolean isDeleted(Resource resource) { - for (SourceDirectory sourceDirectory : this.classLoaderFiles.getSourceDirectories()) { - for (Entry entry : sourceDirectory.getFilesEntrySet()) { - try { - String name = entry.getKey(); - ClassLoaderFile file = entry.getValue(); - if (file.getKind() == Kind.DELETED && resource.exists() - && resource.getURI().toString().endsWith(name)) { - return true; - } - } - catch (IOException ex) { - throw new IllegalStateException("Failed to retrieve URI from '" + resource + "'", ex); + 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)) { + return true; } } + catch (IOException ex) { + throw new IllegalStateException("Failed to retrieve URI from '" + resource + "'", ex); + } } return false; } diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/classloader/ClassLoaderFiles.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/classloader/ClassLoaderFiles.java index 1def6fd05b1f..8b4ee7e5c5d3 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/classloader/ClassLoaderFiles.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/classloader/ClassLoaderFiles.java @@ -43,11 +43,18 @@ public class ClassLoaderFiles implements ClassLoaderFileRepository, Serializable private final Map sourceDirectories; + /** + * A flattened map of all files from all source directories for fast, O(1) lookups. + * The key is the file's relative path, and the value is the ClassLoaderFile. + */ + private final Map filesByName; + /** * Create a new {@link ClassLoaderFiles} instance. */ public ClassLoaderFiles() { this.sourceDirectories = new LinkedHashMap<>(); + this.filesByName = new LinkedHashMap<>(); } /** @@ -57,6 +64,7 @@ public ClassLoaderFiles() { public ClassLoaderFiles(ClassLoaderFiles classLoaderFiles) { Assert.notNull(classLoaderFiles, "'classLoaderFiles' must not be null"); this.sourceDirectories = new LinkedHashMap<>(classLoaderFiles.sourceDirectories); + this.filesByName = new LinkedHashMap<>(classLoaderFiles.filesByName); } /** @@ -94,12 +102,14 @@ public void addFile(String sourceDirectory, String name, ClassLoaderFile file) { Assert.notNull(file, "'file' must not be null"); removeAll(name); getOrCreateSourceDirectory(sourceDirectory).add(name, file); + this.filesByName.put(name, file); } private void removeAll(String name) { for (SourceDirectory sourceDirectory : this.sourceDirectories.values()) { sourceDirectory.remove(name); } + this.filesByName.remove(name); } /** @@ -125,22 +135,21 @@ public Collection getSourceDirectories() { * @return the size of the collection */ public int size() { - int size = 0; - for (SourceDirectory sourceDirectory : this.sourceDirectories.values()) { - size += sourceDirectory.getFiles().size(); - } - return size; + return this.filesByName.size(); } @Override public ClassLoaderFile getFile(String name) { - for (SourceDirectory sourceDirectory : this.sourceDirectories.values()) { - ClassLoaderFile file = sourceDirectory.get(name); - if (file != null) { - return file; - } - } - return null; + return this.filesByName.get(name); + } + + /** + * Returns a set of all file entries across all source directories for efficient + * iteration. + * @return a set of all file entries + */ + public Set> getFileEntries() { + return Collections.unmodifiableSet(this.filesByName.entrySet()); } /**