I use <mvc:resources /> to host Asciidoctor-generated HTML content in few webapps. Those webapps share source code, but are differently configured and deployed to different contexts on an Apache Tomcat 8.5 instance. The documentation must be able to reference configuration parameters for documentation purpose. Since this is statically generated, I cannot inject parameters, for obvious reasons. Those HTML files contain a ${name} placeholder and with the following snippet the interpolation happens.

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Properties;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.text.StringSubstitutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.resource.ResourceTransformer;
import org.springframework.web.servlet.resource.ResourceTransformerChain;
import org.springframework.web.servlet.resource.TransformedResource;

public class PropertiesResourceTransformer implements ResourceTransformer, EnvironmentAware {

    private static final Logger logger = LoggerFactory
            .getLogger(PropertiesResourceTransformer.class);

    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

    Environment environment;

    Properties properties;

    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    public Resource transform(HttpServletRequest request, Resource resource,
            ResourceTransformerChain transformerChain) throws IOException {

        resource = transformerChain.transform(request, resource);

        String filename = resource.getFilename();
        if (!"html".equals(StringUtils.getFilenameExtension(filename))) {
            return resource;
        }

        logger.trace("Transforming resource: {}", resource);

        byte[] bytes = FileCopyUtils.copyToByteArray(resource.getInputStream());
        String content = new String(bytes, DEFAULT_CHARSET);

        content = environment.resolvePlaceholders(content);
        content = StringSubstitutor.replace(content, properties);

        return new TransformedResource(resource, content.getBytes(DEFAULT_CHARSET));
    }

}
````

followed by:
```xml
    <beans:bean id="config"
        class="org.springframework.beans.factory.config.PropertiesFactoryBean">
        <beans:property name="locations">
            <beans:array>
                <beans:value>classpath:app.properties</beans:value>
                <beans:value>classpath:app-custom.properties</beans:value>
            </beans:array>
        </beans:property>
        <beans:property name="ignoreResourceNotFound" value="true" />
    </beans:bean>

    <mvc:resources mapping="/**" location="/" cache-period="86400">
        <mvc:resource-chain resource-cache="true">
            <mvc:transformers>
                <beans:bean
                    class="...PropertiesResourceTransformer" p:properties-ref="config" />
            </mvc:transformers>
        </mvc:resource-chain>
    </mvc:resources>

I am not 100% happy with mine because I need to inject my Properties object directly and cannot totally rely on Environment, maybe that's just my lack of knowledge how to do it right. Please add such a resource tranformer upstream, I guess many would require something like this. Feel free to reuse and modify my code for upstream.

I am on Spring Framework 5.3.14.

Comment From: bclozel

Declined for reasons explained in https://github.com/spring-projects/spring-framework/issues/27930#issuecomment-1015251647