Servlet Context Initialization Parameter not Accessible in Spring Boot

309 views Asked by At

I'm running a Spring Boot(3.1.0) application inside a standalone Tomcat Server (10.12) where I defined a set of Context Parameters in /opt/tomcat/conf/Catalina/web1/ROOT.xml as follows:

<?xml version="1.0" encoding="UTF-8"?>
  <Context path="" docBase="/var/www/web1">
    <Valve className="org.apache.catalina.valves.rewrite.RewriteValve"
       resourcePath="rewrite.config"/>
    <WatchedResource>WEB-INF/rewrite.config</WatchedResource>
    <Parameter name="website" value="web1-dev" override="false"/>
    <Parameter name="logging.file.name" value="web" override="false"/>
    <Parameter name="log4j2.configurationFile" value="log4j2-dev.xml" override="false"/>
  </Context>

For instance, the website parameter should be dynamically used in the Property Configuration class:

@PropertySource(ignoreResourceNotFound = true, value =
    {"classpath:/enviroment/default.properties",
     "classpath:/enviroment/${website}.properties",

The Application however doesn't seem to see them during the startup phase. I've also tried to declare them in the application.yml file:

server.servlet.context-parameters.log4j2.configurationFile=log4j2-dev.xml

but it still doesn't make any difference. It doesn't matter if I run the app locally with an embedded Tomcat or as WAR within the standalone Container.

What should I do to make these params visible/accessible to my Spring Boot app at the startup time?

The parameters are actually listed in the Spring Actuator /env endpoint:

{
 "activeProfiles":[
  
 ],
  "propertySources":[
  {
     "name":"servletContextInitParams",
     "properties":{
        "contextConfigLocation":{
           "value":"******"
        },
        "website":{
           "value":"******"
        },
        "log4j2.configurationFile":{
           "value":"******"
        },
        "logging.file.name":{
           "value":"******"
        }
     }
  },

My idea was to read the params from the ServletContext in the onStartup method in the main Application.java class, since it extends SpringBootServletInitializer and then directly pass it to the system properties, e.g. System.setProperty("website", "web1"). The problem is that the onStartup never gets executed.

Side note: There are two applications (web1, web2) running parallel on the same server, both are using the same code base. I could eventually move two out of there parameters to the application.yml since both apps currently use the same value on each stage (dev, staging, prod) but at least the logging.file.name should differ from one another.

Any help much appreciated!

2

There are 2 answers

0
mooor On BEST ANSWER

Finally I managed to pass the servlet context init params at the right stage.

The trick was to run the application as a .war file inside a standalone Tomcat container and override the onStartup(ServletContext servletContext) method in my Application.java, which subclasses the SpringBootServletInitializer. There, I read the expected servlet context params and pass them directly to the System.setProperty(key, value) method.

The clue here is in the jakarta.servlet.ServletContainerInitializer interface and it's Spring implementation org.springframework.web.SpringServletContainerInitializer. It scans all implementations of the org.springframework.web.WebApplicationInitializer and executes their onStartup(ServletContext servletContext) methods.

It only works this way with a standalone servlet container (servlet context loaded first, then passed to the onStartup hook of the spring boot application).

To overcome this problem when starting a spring boot application with an embedded servlet container, one has to use VM Options.

Hope this will help someone in the future.

3
Az.MaYo On

When you define context parameters in a Context configuration like you've done in your /opt/tomcat/conf/Catalina/web1/ROOT.xml, those parameters are specific to the Tomcat context and aren't automatically available to your Spring Boot application. To make these context parameters available in your Spring Boot application, you can access them through the ServletContext during application startup. Here's how you can load them in your Spring Boot application:

  1. Create a ServletContextListener in your Spring Boot application. This listener will be called when the application starts.

    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    
    public class ContextParamListener implements ServletContextListener {
    
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        // Access the ServletContext and retrieve context parameters
        String website = servletContextEvent.getServletContext().getInitParameter("website");
        String log4j2Config = servletContextEvent.getServletContext().getInitParameter("log4j2.configurationFile");
    
        // Store these values in a place accessible to your application
        // For example, you can store them in Environment properties or Spring Boot configuration.
        // You can set them as System properties or as Spring Boot application properties.
        System.setProperty("website", website);
        System.setProperty("log4j2.configurationFile", log4j2Config);
    }
    
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        // Cleanup if needed
    }
    }
    
  2. Now, in your @PropertySource annotation, you can access the context parameters you've stored as system properties:

    @PropertySource(ignoreResourceNotFound = true, value = { "classpath:/environment/default.properties", "classpath:/environment/${website}.properties", "classpath:/environment/log4j2-${log4j2.configurationFile}.xml"})

By setting the context parameters as system properties in the ContextParamListener, you can make them available for configuration in your Spring Boot application. This approach allows you to access context parameters defined in Tomcat within your Spring Boot application during startup.