Issue
I have a "typical" (close to starter project from initializer) Spring Boot 2.4 application with Spring MVC and I want to add caching to static files. For this, I provide my own @Configuration
-class which implements WebMvcConfigurer
. But whatever I try, I always just receive 404 errors for static ressources once I use my own configuration.
My directory structure for static content
src/main/resources/
|- static/
|- res/
|- css/
|- js/
|- images/
|- favicon.ico
Problem/Question
I think my misunderstanding is in how ResourceHandlerRegistry#addResourceHandler
and ResourceHandlerRegistry#addResourceLocations
work together.
I tried to do it similar to the default resource resolving (see org.springframework.boot.autoconfigure.web.WebProperties.Resources.CLASSPATH_RESOURCE_LOCATIONS
) and ended up with this (not working) code:
@Configuration
public class CacheStaticResourcesConfiguration implements WebMvcConfigurer {
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/favicon.ico", "/res/**", "/images/**")
.addResourceLocations("classpath:/static")
.setCacheControl(CacheControl.maxAge(7, TimeUnit.DAYS)
.noTransform()
.mustRevalidate());
}
}
Solution
The resource/class path resolving is indeed confusing and it took me also a bit to get behind it. The following example with a lot of comments hopefully clarifies everything.
TL;DR:
registry
// Request URLs
.addResourceHandler("/**" /*, ... */)
// Path inside application (refers to src/main/resources directory)
// TRAILING SLASH IS IMPORTANT
.addResourceLocations("classpath:/static/")
Spring appends the variable portion of the path from the pattern one puts into addResourceHandler()
to the ressource locations provided in .addResourceLocations()
. Example:
// TRAILING SLASH IS IMPORTANT!
.addResourceLocations("classpath:/static/")
=>
addResourceHandler("/**")
=> GET /res/css/main.css
=> resolved as: "classpath:/static/res/css/main.css"
BUT
addResourceHandler("/res/**")
=> GET /res/css/main.css
(spring only appends the ** to the value from
addResourceLocations())
=> resolved as: "classpath:/static/css/main.css"
Working Example Configuration
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.concurrent.TimeUnit;
@Configuration
public class CacheStaticResourcesConfiguration implements WebMvcConfigurer {
/**
* We provide a custom configuration which resolves URL-Requests to static files in the
* classpath (src/main/resources directory).
*
* This overloads a default configuration retrieved at least partly from
* {@link WebProperties.Resources#getStaticLocations()}.
*
* @param registry ResourceHandlerRegistry
*/
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
/*
* BE AWARE HERE:
*
* .addResourceHandler(): URL Paths
* .addResourceLocations(): Paths in Classpath to look for file
* root "/" refers to src/main/resources
* For configuration example, see:
* org.springframework.boot.autoconfigure.web.WebProperties.Resources().getStaticLocations()
*
* .addResourceLocations("classpath:/static/")
* =>
* addResourceHandler("/**")
* => GET /res/css/main.css
* => resolved as: "classpath:/static/res/css/main.css"
* BUT
* addResourceHandler("/res/**")
* => GET /res/css/main.css
* (spring only appends the ** to the value from
* addResourceLocations())
* => resolved as: "classpath:/static/css/main.css"
*/
registry
.addResourceHandler("/favicon.ico")
// trailing slash is important!
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS)
.noTransform()
.mustRevalidate());
registry
.addResourceHandler("/res/**")
// trailing slash is important!
.addResourceLocations("classpath:/static/res/")
.setCacheControl(CacheControl.maxAge(7, TimeUnit.DAYS)
.noTransform()
.mustRevalidate());
registry
.addResourceHandler("/images/**")
// trailing slash is important!
.addResourceLocations("classpath:/static/images/")
.setCacheControl(CacheControl.maxAge(7, TimeUnit.DAYS)
.noTransform()
.mustRevalidate());
}
}
Answered By - phip1611
Answer Checked By - David Goodson (JavaFixing Volunteer)