Skip to content

Commit c4a4b63

Browse files
committed
Merge pull request #46289 from move-hoon
* pr/46289: Polish "Optimize DevTools resource lookup performance" Optimize DevTools resource lookup performance Closes gh-46289
2 parents 4761e13 + 2c4d162 commit c4a4b63

File tree

2 files changed

+40
-34
lines changed

2 files changed

+40
-34
lines changed

module/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ClassLoaderFilesResourcePatternResolver.java

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile.Kind;
3434
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFileURLStreamHandler;
3535
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles;
36-
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles.SourceDirectory;
3736
import org.springframework.context.ApplicationContext;
3837
import org.springframework.context.support.AbstractApplicationContext;
3938
import org.springframework.core.io.AbstractResource;
@@ -125,15 +124,13 @@ public Resource[] getResources(String locationPattern) throws IOException {
125124
private List<Resource> getAdditionalResources(String locationPattern) throws MalformedURLException {
126125
List<Resource> additionalResources = new ArrayList<>();
127126
String trimmedLocationPattern = trimLocationPattern(locationPattern);
128-
for (SourceDirectory sourceDirectory : this.classLoaderFiles.getSourceDirectories()) {
129-
for (Entry<String, ClassLoaderFile> entry : sourceDirectory.getFilesEntrySet()) {
130-
String name = entry.getKey();
131-
ClassLoaderFile file = entry.getValue();
132-
if (file.getKind() != Kind.DELETED && this.antPathMatcher.match(trimmedLocationPattern, name)) {
133-
URL url = new URL("reloaded", null, -1, "/" + name, new ClassLoaderFileURLStreamHandler(file));
134-
UrlResource resource = new UrlResource(url);
135-
additionalResources.add(resource);
136-
}
127+
for (Entry<String, ClassLoaderFile> entry : this.classLoaderFiles.getFileEntries()) {
128+
String name = entry.getKey();
129+
ClassLoaderFile file = entry.getValue();
130+
if (file.getKind() != Kind.DELETED && this.antPathMatcher.match(trimmedLocationPattern, name)) {
131+
URL url = new URL("reloaded", null, -1, "/" + name, new ClassLoaderFileURLStreamHandler(file));
132+
UrlResource resource = new UrlResource(url);
133+
additionalResources.add(resource);
137134
}
138135
}
139136
return additionalResources;
@@ -149,20 +146,18 @@ private String trimLocationPattern(String pattern) {
149146
}
150147

151148
private boolean isDeleted(Resource resource) {
152-
for (SourceDirectory sourceDirectory : this.classLoaderFiles.getSourceDirectories()) {
153-
for (Entry<String, ClassLoaderFile> entry : sourceDirectory.getFilesEntrySet()) {
154-
try {
155-
String name = entry.getKey();
156-
ClassLoaderFile file = entry.getValue();
157-
if (file.getKind() == Kind.DELETED && resource.exists()
158-
&& resource.getURI().toString().endsWith(name)) {
159-
return true;
160-
}
161-
}
162-
catch (IOException ex) {
163-
throw new IllegalStateException("Failed to retrieve URI from '" + resource + "'", ex);
149+
for (Entry<String, ClassLoaderFile> entry : this.classLoaderFiles.getFileEntries()) {
150+
try {
151+
String name = entry.getKey();
152+
ClassLoaderFile file = entry.getValue();
153+
if (file.getKind() == Kind.DELETED && resource.exists()
154+
&& resource.getURI().toString().endsWith(name)) {
155+
return true;
164156
}
165157
}
158+
catch (IOException ex) {
159+
throw new IllegalStateException("Failed to retrieve URI from '" + resource + "'", ex);
160+
}
166161
}
167162
return false;
168163
}

module/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/classloader/ClassLoaderFiles.java

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,23 @@
4242
*/
4343
public class ClassLoaderFiles implements ClassLoaderFileRepository, Serializable {
4444

45+
@Serial
4546
private static final long serialVersionUID = 1;
4647

4748
private final Map<String, SourceDirectory> sourceDirectories;
4849

50+
/**
51+
* A flattened map of all files from all source directories for fast, O(1) lookups.
52+
* The key is the file's relative path, and the value is the ClassLoaderFile.
53+
*/
54+
private final Map<String, ClassLoaderFile> filesByName;
55+
4956
/**
5057
* Create a new {@link ClassLoaderFiles} instance.
5158
*/
5259
public ClassLoaderFiles() {
5360
this.sourceDirectories = new LinkedHashMap<>();
61+
this.filesByName = new LinkedHashMap<>();
5462
}
5563

5664
/**
@@ -60,6 +68,7 @@ public ClassLoaderFiles() {
6068
public ClassLoaderFiles(ClassLoaderFiles classLoaderFiles) {
6169
Assert.notNull(classLoaderFiles, "'classLoaderFiles' must not be null");
6270
this.sourceDirectories = new LinkedHashMap<>(classLoaderFiles.sourceDirectories);
71+
this.filesByName = new LinkedHashMap<>(classLoaderFiles.filesByName);
6372
}
6473

6574
/**
@@ -97,12 +106,14 @@ public void addFile(String sourceDirectory, String name, ClassLoaderFile file) {
97106
Assert.notNull(file, "'file' must not be null");
98107
removeAll(name);
99108
getOrCreateSourceDirectory(sourceDirectory).add(name, file);
109+
this.filesByName.put(name, file);
100110
}
101111

102112
private void removeAll(String name) {
103113
for (SourceDirectory sourceDirectory : this.sourceDirectories.values()) {
104114
sourceDirectory.remove(name);
105115
}
116+
this.filesByName.remove(name);
106117
}
107118

108119
/**
@@ -128,22 +139,22 @@ public Collection<SourceDirectory> getSourceDirectories() {
128139
* @return the size of the collection
129140
*/
130141
public int size() {
131-
int size = 0;
132-
for (SourceDirectory sourceDirectory : this.sourceDirectories.values()) {
133-
size += sourceDirectory.getFiles().size();
134-
}
135-
return size;
142+
return this.filesByName.size();
136143
}
137144

138145
@Override
139146
public @Nullable ClassLoaderFile getFile(String name) {
140-
for (SourceDirectory sourceDirectory : this.sourceDirectories.values()) {
141-
ClassLoaderFile file = sourceDirectory.get(name);
142-
if (file != null) {
143-
return file;
144-
}
145-
}
146-
return null;
147+
return this.filesByName.get(name);
148+
}
149+
150+
/**
151+
* Returns a set of all file entries across all source directories for efficient
152+
* iteration.
153+
* @return a set of all file entries
154+
* @since 4.0.0
155+
*/
156+
public Set<Entry<String, ClassLoaderFile>> getFileEntries() {
157+
return Collections.unmodifiableSet(this.filesByName.entrySet());
147158
}
148159

149160
/**

0 commit comments

Comments
 (0)