A PetClinic user noticed this (https://github.com/spring-projects/spring-petclinic/issues/1732). For me it's a 2x throughput degradation on the home page of PetClinic, for the user that reported the issue it was 4x. The problem seems to be that ResourceUrlEncodingFilter has a CachingResourceResolver in it that works great for actual classpath resources. Conversely, links that are not static resources are cache misses, and they have to be checked every time in case they are, after all, on the classpath (e.g. links to other pages in the app).
Comment From: maio
Hello,
we also encountered this issue in our backoffice app which uses webjars-locator-lite, and displays tables with few hundreds of rows where each has multiple actions for which links get generated in Thymeleaf using <a href="@{/an/app/path/..}"> syntax. I would expect zero performance impact as these links have nothing to do with webjars, but instead we see a lot of CPU time being spent in LiteWebJarsResourceResolver.
To be precise time is being spent in org.springframework.web.servlet.resource.LiteWebJarsResourceResolver#resolveUrlPathInternal
99% of this 7 plus seconds were spent in following methods (stacktrace):
org.springframework.web.servlet.resource.LiteWebJarsResourceResolver#resolveUrlPathInternal
org.springframework.web.servlet.resource.DefaultResourceResolverChain#resolveUrlPath
org.springframework.web.servlet.resource.AbstractResourceResolver#resolveUrlPath
org.springframework.web.servlet.resource.PathResourceResolver#resolveUrlPathInternal
org.springframework.web.servlet.resource.PathResourceResolver#getResource(java.lang.String, jakarta.servlet.http.HttpServletRequest, java.util.List<? extends org.springframework.core.io.Resource>)
BTW we are including webjars using HTML like this:
<link rel="stylesheet" th:href="@{/webjars/bootstrap/dist/css/bootstrap.css}">
So I would expect locator to do it's magic just for URLs which start with /webjars, but it seems to affect/process all the URLs which is problematic for any app that generates links to many unique URLs (e.g. link to transaction/user/whatever detail which contains ID).
Comment From: dsyer
The problem isn't the webjars locator per se though, it's a side effect of Spring Boot enabling a bunch of stuff including that. You might be able to workaround the issue by setting spring.web.resources.chain.cache=false.
Comment From: bclozel
I had another look at this and I think there are a few ways to solve this problem.
Runtime behavior
First, I have created a sample application that links to a webjar file and to a static file located under src/main/resources/static/test.txt; those are made available with the following Thymeleaf template:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Home</title>
<script th:src="@{/webjars/jquery/jquery.js}"></script>
</head>
<body>
<a th:href="@{/home}">home</a>
<a th:href="@{/blog}">blog</a>
<a th:href="@{/users/brian}">user information</a>
<a th:href="@{/test.txt}">link to text file</a>
</body>
</html>
At runtime, the ResourceUrlEncodingFilter uses the ResourceUrlProvider to locate existing static resources and possibly rewrite their public link in the rendered HTML templates.
Here, the main performance problem comes from the fact that links to application pages are considered in this process. While "@{/webjars/jquery/jquery.js}" and @{/test.txt} are resolved once and then cached, all the other links (@{/home}, @{/blog}, @{/users/brian}) are being processed over and over. Some resource resolvers can be quite slow because they perform multiple I/O operations (like the LiteWebJarsResourceResolver).
Possible workarounds
One way to solve this problem is to add somewhere in the chain, possibly in the Servlet Filter directly, an LruCache that would store all paths that resolved no static resource. This would mostly solve the performance issue, but at the cost of memory usage and complexity. Applications can generate a large number of dynamic URLs (/users/brian, /users/dave...) so the cache hit rate could be low depending on the pages and the type of traffic.
We could also add some heuristics that would skip the resource resolution entirely. For example, not considering URLs if the path does not contain a file extension. This would probably be flawed. We could have a predicate to customize it but not something that could be driven with configuration properties. So the overall situation would be even more complex.
Revisiting static path patterns
I have noticed that the false positives here do not happen because of the webjar resolver registration by itself, but because in Spring Boot applications the following default applies: spring.mvc.static-path-pattern=/**. This means all URLs are considered for resource handling. This is not a problem in general, as during the handling phase the resource handler is considered last after the controllers. But this is a real problem when rewriting URLs in templates.
I have changed my sample application with the following:
spring.mvc.static-path-pattern=/static/**
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Home</title>
<script th:src="@{/webjars/jquery/jquery.js}"></script>
</head>
<body>
<a th:href="@{/home}">home</a>
<a th:href="@{/static/test.txt}">link to text file</a>
</body>
</html>
Here, the application URLs are discarded very early in org.springframework.web.servlet.resource.ResourceUrlProvider#getForLookupPath and I think the performance problem is gone entirely. This means static resources must have at least one path prefix (like /static) to avoid this problem.
This has an important impact, but we could consider changing the default in Spring Boot to reflect that. What do you think?