Skip to content

Commit d57f7f8

Browse files
committed
Sort handler matches in ResourceUrlProvider
Prior to this change, the `ResourceUrlProvider.getForLookupPath` method would try to match handlers using the keySet order in the handlerMappings Map. In case of several matches, the handler used for the return value could vary, since the registration order in the handlerMappings can't be guaranteed in the configuration. This commit now collects all matching handlers and sort them using a `PatternComparator`, in order to try each handler from the most specific mapping to the least. Issue: SPR-12647
1 parent 4550b4a commit d57f7f8

File tree

2 files changed

+53
-19
lines changed

2 files changed

+53
-19
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlProvider.java

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.Collections;
21+
import java.util.Comparator;
2122
import java.util.HashMap;
2223
import java.util.List;
2324
import java.util.Map;
@@ -191,32 +192,41 @@ private int getLookupPathIndex(HttpServletRequest request) {
191192
* public use.
192193
* <p>It is expected that the given path is what Spring MVC would use for
193194
* request mapping purposes, i.e. excluding context and servlet path portions.
195+
* <p>If several handler mappings match, the handler used will be the one
196+
* configured with the most specific pattern.
194197
* @param lookupPath the lookup path to check
195198
* @return the resolved public URL path, or {@code null} if unresolved
196199
*/
197200
public final String getForLookupPath(String lookupPath) {
198201
if (logger.isTraceEnabled()) {
199202
logger.trace("Getting resource URL for lookupPath=" + lookupPath);
200203
}
204+
List<String> matchingPatterns = new ArrayList<String>();
201205
for (String pattern : this.handlerMap.keySet()) {
202-
if (!getPathMatcher().match(pattern, lookupPath)) {
203-
continue;
206+
if (getPathMatcher().match(pattern, lookupPath)) {
207+
matchingPatterns.add(pattern);
204208
}
205-
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(pattern, lookupPath);
206-
String pathMapping = lookupPath.substring(0, lookupPath.indexOf(pathWithinMapping));
207-
if (logger.isTraceEnabled()) {
208-
logger.trace("Invoking ResourceResolverChain for URL pattern=\"" + pattern + "\"");
209-
}
210-
ResourceHttpRequestHandler handler = this.handlerMap.get(pattern);
211-
ResourceResolverChain chain = new DefaultResourceResolverChain(handler.getResourceResolvers());
212-
String resolved = chain.resolveUrlPath(pathWithinMapping, handler.getLocations());
213-
if (resolved == null) {
214-
continue;
215-
}
216-
if (logger.isTraceEnabled()) {
217-
logger.trace("Resolved public resource URL path=\"" + resolved + "\"");
209+
}
210+
if (!matchingPatterns.isEmpty()) {
211+
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(lookupPath);
212+
Collections.sort(matchingPatterns, patternComparator);
213+
for(String pattern : matchingPatterns) {
214+
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(pattern, lookupPath);
215+
String pathMapping = lookupPath.substring(0, lookupPath.indexOf(pathWithinMapping));
216+
if (logger.isTraceEnabled()) {
217+
logger.trace("Invoking ResourceResolverChain for URL pattern=\"" + pattern + "\"");
218+
}
219+
ResourceHttpRequestHandler handler = this.handlerMap.get(pattern);
220+
ResourceResolverChain chain = new DefaultResourceResolverChain(handler.getResourceResolvers());
221+
String resolved = chain.resolveUrlPath(pathWithinMapping, handler.getLocations());
222+
if (resolved == null) {
223+
continue;
224+
}
225+
if (logger.isTraceEnabled()) {
226+
logger.trace("Resolved public resource URL path=\"" + resolved + "\"");
227+
}
228+
return pathMapping + resolved;
218229
}
219-
return pathMapping + resolved;
220230
}
221231
logger.debug("No matching resource mapping");
222232
return null;

spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlProviderTests.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
*/
4545
public class ResourceUrlProviderTests {
4646

47+
private List<Resource> locations;
48+
4749
private ResourceUrlProvider translator;
4850

4951
private ResourceHttpRequestHandler handler;
@@ -53,9 +55,9 @@ public class ResourceUrlProviderTests {
5355

5456
@Before
5557
public void setUp() {
56-
List<Resource> locations = new ArrayList<Resource>();
57-
locations.add(new ClassPathResource("test/", getClass()));
58-
locations.add(new ClassPathResource("testalternatepath/", getClass()));
58+
this.locations = new ArrayList<Resource>();
59+
this.locations.add(new ClassPathResource("test/", getClass()));
60+
this.locations.add(new ClassPathResource("testalternatepath/", getClass()));
5961

6062
this.handler = new ResourceHttpRequestHandler();
6163
this.handler.setLocations(locations);
@@ -94,6 +96,28 @@ private void initTranslator() {
9496
this.translator.setHandlerMap(this.handlerMap);
9597
}
9698

99+
// SPR-12647
100+
@Test
101+
public void bestPatternMatch() throws Exception {
102+
ResourceHttpRequestHandler otherHandler = new ResourceHttpRequestHandler();
103+
otherHandler.setLocations(this.locations);
104+
Map<String, VersionStrategy> versionStrategyMap = new HashMap<>();
105+
versionStrategyMap.put("/**", new ContentVersionStrategy());
106+
VersionResourceResolver versionResolver = new VersionResourceResolver();
107+
versionResolver.setStrategyMap(versionStrategyMap);
108+
109+
List<ResourceResolver> resolvers = new ArrayList<ResourceResolver>();
110+
resolvers.add(versionResolver);
111+
resolvers.add(new PathResourceResolver());
112+
otherHandler.setResourceResolvers(resolvers);
113+
114+
this.handlerMap.put("/resources/*.css", otherHandler);
115+
initTranslator();
116+
117+
String url = this.translator.getForLookupPath("/resources/foo.css");
118+
assertEquals("/resources/foo-e36d2e05253c6c7085a91522ce43a0b4.css", url);
119+
}
120+
97121
// SPR-12592
98122
@Test
99123
public void initializeOnce() throws Exception {

0 commit comments

Comments
 (0)