Here's an example of how to handle static web resources in Spring. Let us pretend that one of the business requirement of our Angular FE Spring Boot app is A/B testing. A/B testing is a method of comparing two versions of an app or webpage against each other to determine which one performs better. In short, we'll need a control webpage and a variant webpage. For the control webpage, there will be no changes. For the variant webpage, we'll need to inject "ab-testing.js" in the head of our webpage.
We will not be talking about how to do A/B testing. Instead, we will focus on how to inject the script tag in the head of our webpage. Requirements clear enough? Let's begin. Below is our control webpage.
Control Page |
We will utilize the code from my previous blog, Angular FE Spring Boot Example and you can clone the code here, github.com/jpllosa/angular-fe-spring-boot.
Two Strategies
As far as I know, there are two ways to fulfill this business requirement.
First one is to insert the script tag when ngOnInit
is called in a Component
class.
Second is to use Spring's ResourceTransformer
. We will use this method because the script tag is injected server side. Prior to serving the HTML file, it is transformed. The first strategy happens on the client side which is why I think this is a better solution. This also seems better because the "ab-testing.js" is already in the head prior to DOM manipulation by Angular. So let's get to it.
POM
Update the POM file. We need the dependency below for IOUtils
.
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
index.html
Add a marker text in dev/my-app/src/index.html, like so:
<head>
... snipped ...
<link rel="icon" type="image/x-icon" href="favicon.ico">
<!-- INJECT_HERE -->
</head>
Java Code
Create a com.blogspot.jpllosa.transformer
package and add the following files below:
package com.blogspot.jpllosa.transformer;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
@Configuration
public class IndexTransformationConfigurer extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("index.html")
.addResourceLocations("classpath:/static/")
.resourceChain(false)
.addTransformer(new IndexTransformer());
}
}
We create the class above so that we can add a handler when serving the index.html file. Here it specifies that the index.html file from the classpath:/static/ location will have to pass through a Transformer
. Other static resources will not be affected. We are not caching the result of the resource resolution (i.e. resourceChain(false)
).
package com.blogspot.jpllosa.transformer;
import org.apache.commons.io.IOUtils;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.resource.ResourceTransformer;
import org.springframework.web.servlet.resource.ResourceTransformerChain;
import org.springframework.web.servlet.resource.TransformedResource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Service
public class IndexTransformer implements ResourceTransformer {
@Override
public Resource transform(HttpServletRequest request, Resource resource, ResourceTransformerChain chain) throws IOException {
String html = IOUtils.toString(resource.getInputStream(), "UTF-8");
html = html.replace("<!-- INJECT_HERE -->",
"<script src=\"//third.party.com/ab-testing.js\"> </script>");
return new TransformedResource(resource, html.getBytes());
}
}
We've kept our transformer simple for this example. The code is readable enough as to what it is doing, isn't it? We are replacing the placeholder text with the script tag. Job done.
Demonstration
If you have read the Angular FE Spring Boot Example then you should know what to do next. In case you have not. I'll reiterate.
Build the Angular project, ng build
on dev/my-app. This will create files in the dist directory.
Run mvn clean package
on the project root directory. After that, run
Variant Page |
There you have it. A quick example of how to handle or transform static web resources in Spring. Grab the full repo here, github.com/jpllosa/angular-fe-spring-boot