Externalize favicon with Spring Boot and Thymeleaf
For unimportant reasons, I'm trying to source a favicon in my Spring Boot project from a directory not on the classpath.
I'm able to load images or other assets that directory but the favicon for some reason cannot be loaded.
Here's a demo project I created to illustrate the problem and the things I've tried so far
.
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── demo
│ │ ├── Demo2Application.java
│ │ ├── ExternalConfig.java
│ │ └── PageController.java
│ └── resources
│ ├── application.properties
│ ├── static
│ └── templates
│ └── index.html
└── test
└── java
└── com
└── example
└── demo
└── Demo2ApplicationTests.java
- I have a single
@Configuration
class calledExternalConfig.java
. - I have a single
@Controller
class calledPageController.java
which is used to return the Thymeleaf template to the browser. application.properties
only contains one entrytest.repoPath = /path/to/external/directory
Demo2Application (launch class)
@SpringBootApplication
public class Demo2Application {
public static void main(String[] args) {
SpringApplication.run(Demo2Application.class, args);
}
}
PageController
@Controller
public class PageController {
@GetMapping("/")
public String getIndex () {
return "index";
}
}
index.html
Note, that I've added an extra img
line here to show to myself that I can source content from an external directory.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title th:text="${blogTitle}"/>
<link rel="icon" th:href="@{~/blog-repo/images/favicon.ico}" sizes="any" />
<link rel="stylesheet" type="text/css" th:href="@{/css/style.css}"/>
</head>
<body>
<p>Just to render something</p>
<img th:src="@{~/testing-path/images/logo.svg}" alt="Blog Logo" height="200px"/>
</body>
</html>
ExternalConfig.java (Where I'm trying to make the changes)
This is where the magic is supposed to happen. From what I've been able to find online, the solution is to add a new resourceHandler
to the ResourceHandlerRegistry
. This works to allow Thymeleaf and Spring to serve images assets. I believe it'll also work if you're trying to store your favicon in a different folder in your classpath. But for external files, it doesn't seem to work. Here's the code.
@Configuration
@Data
@Validated
@ConfigurationProperties(prefix = "test")
public class ExternalConfig implements WebMvcConfigurer {
private String repoPath;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
repoPath = repoPath.endsWith(File.separator) ? repoPath : repoPath File.separator;
var resourceLocationRoot = "file:" repoPath;
registry.addResourceHandler("/testing-path/**").addResourceLocations(resourceLocationRoot);
registry.addResourceHandler("/favicon.ico").addResourceLocations(resourceLocationRoot "/images/");
}
}
When I debug, I can see that the path is properly added to the registry. When I go to http://localhost:8080
, index.html
page shows up as expected as well as a the img
that I put in the html file. The favicon does not show up however.
I've tried also specifying the specific file path to the exact file but that didn't work either.
registry.addResourceHandler("/favicon.ico").addResourceLocations(resourceLocationRoot "/images/favicon.ico");
I'm using Spring Boot 3.0.0 for this project.
CodePudding user response:
Solution in this case was two-fold:
Hardcode the specific path for the resourceHandler
registry.addResourceHandler("/favicon.ico").addResourceLocations(resourceLocationRoot "/images/favicon.ico");
Comment out the explicit favicon request in my template
<!-- <link rel="icon" th:href="@{~/blog-repo/images/favicon.ico}" sizes="any" />-->
CodePudding user response:
The favicon can also be served from a custom location as explained in:
- Baeldung's Guide to the Favicon in Spring Boot
For example have an application.properties
like:
favicon.directory = /path/to/external/directory
and a configuration applying a SimpleUrlHandlerMapping
using a customized ResourceHttpRequestHandler
that loads the favicon.ico
from any Resource
like:
@Configuration
@ConfigurationProperties(prefix = "favicon")
public class FaviconConfiguration {
File directory; // configured in application.properties
@Bean
public SimpleUrlHandlerMapping customFaviconHandlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(Integer.MIN_VALUE); // to be first
mapping.setUrlMap(Collections.singletonMap("/favicon.ico", faviconRequestHandler())); // use the handler defined below
return mapping;
}
@Bean
protected ResourceHttpRequestHandler faviconRequestHandler() {
ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
FileSystemResource faviconResource = new FileSystemResource(directory);
List<Resource> locations = Arrays.asList(faviconResource);
requestHandler.setLocations(locations);
return requestHandler;
}
}
This has 2 benefits over already answered solution:
- the
File
typed property is already validated as path (can add additional checks likeexists()
orisDirectory()
) - the configuration class is isolated and follows SRP (single-responsibility: favicon mapping)