Home > front end >  Externalize favicon with Spring Boot and Thymeleaf
Externalize favicon with Spring Boot and Thymeleaf

Time:12-20

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 called ExternalConfig.java.
  • I have a single @Controllerclass called PageController.java which is used to return the Thymeleaf template to the browser.
  • application.properties only contains one entry
    • test.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:

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:

  1. the File typed property is already validated as path (can add additional checks like exists() or isDirectory())
  2. the configuration class is isolated and follows SRP (single-responsibility: favicon mapping)
  • Related