Hi,

We have multiple standalone Tomcat instances. On each Tomcat instance, we deploy more than one Spring Boot application instance. So we use this kind of application declaration:

@SpringBootApplication
public class App extends SpringBootServletInitializer {

  public static void main(String[] args) {
    SpringApplication.run(App.class, args);
  }
}

We want our applications to be Spring Cloud Config clients. For that, property spring.application.name needs to be set very early in the application lifecyle. It seems that spring.application.name is never set automatically (by a Listener or Initializer).

On these kinds of deployments, neither environment variables nor java system properties can be used since there are multiple applications on one JVM.

We want SpringBootServletInitializer to automatically bind spring.application.name to the ServletContext path the same way GenericWebApplicationContext binds ApplicationContext.getApplicationName() to the ServletContext path.

I imagined 2 options for doing that: * Let Spring Boot support natively this behaviour in SpringBootServletInitializer. I think this could be added to WebEnvironmentPropertySourceInitializer. It would set the property conditionnally. * Add the ServletContext as a second parameter to SpringBootServletInitializer.configure(SpringApplicationBuilder) to let the application add its own ApplicationListener based on the ServletContext.

Please know that I am willing to craft a PR based on your feedbacks.

Comment From: snicoll

I am not sure I understood the problem. What is wrong with the following?

public class App extends SpringBootServletInitializer {

    public static void main(String[] args) {
        new App().configure(new SpringApplicationBuilder()).run(args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(App.class).properties("spring.application.name=myapp");
    }

}

Comment From: reda-alaoui

Hi @snicoll ,

Let's imagine one Spring Application code base. This code base is deployed multiple times per Tomcat.

On one tomcat we would have the following deployments of the same code base: - /app1 - /app2 - /app3 - ...

Because of the high number of applications, we use the Tomcat Context path (e.g. app1) as the application name. Therefore, we want spring.application.name to be ServletContext.getContextPath() minus /. That's why I need at least access to the ServletContext in SpringBootServletInitializer.configure(SpringApplicationBuilder). For example, this would be a solution:

public class App extends SpringBootServletInitializer {

    public static void main(String[] args) {
        new App().configure(new SpringApplicationBuilder()).run(args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application, ServletContext servletContext) {
                String appName = StringUtils.removeStart(servletContext.getContextPath(), "/");
        return application.sources(App.class).properties("spring.application.name=" + appName);
    }

}

Please notice that is already the default behaviour of ApplicationContext in a Servlet application. GenericWebApplicationContext.getApplicationName() already returns the ContextPath (e.g. /app1).

For now, I achieved that with WebApplicationInitializer:

public class ServletContextPathAsApplicationNameInitializer
    implements WebApplicationInitializer, Ordered {

  @Override
  public void onStartup(ServletContext servletContext) {
    // ServletContext init parameters will be added to Spring Environment by SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer
    servletContext.setInitParameter(
        "spring.application.name", StringUtils.removeStart(servletContext.getContextPath(), "/"));
  }

  @Override
  public int getOrder() {
    return Ordered.HIGHEST_PRECEDENCE;
  }
}

Comment From: wilkinsona

@reda-alaoui While we consider what, if anything, to do here, you could remove the need for the WebApplicationInitializer by overriding onStartup(ServletContext) instead:

public class App extends SpringBootServletInitializer {

    private ServletContext servletContext;

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        this.servletContext = servletContext;
        super.onStartup(servletContext);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        String appName = StringUtils.removeStart(this.servletContext.getContextPath(), "/");
        return application.sources(App.class).properties("spring.application.name=" + appName);
    }

}

Comment From: wilkinsona

Flagging for team attention to see what, if anything, we want to do here. FWIW, I don't think this is something that should be supported out-of-the-box (even as an opt-in), but I do wonder if we should make it a little bit easier to access the servlet context in the configure method of SpringBootServletInitializer. Something like this:

--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/support/SpringBootServletInitializer.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/support/SpringBootServletInitializer.java
@@ -135,7 +135,7 @@ public abstract class SpringBootServletInitializer implements WebApplicationInit
                }
                builder.initializers(new ServletContextApplicationContextInitializer(servletContext));
                builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
-               builder = configure(builder);
+               builder = configure(builder, servletContext);
                builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
                SpringApplication application = builder.build();
                if (application.getAllSources().isEmpty()
@@ -189,11 +189,28 @@ public abstract class SpringBootServletInitializer implements WebApplicationInit
         * @param builder a builder for the application context
         * @return the application builder
         * @see SpringApplicationBuilder
+        * @deprecated since 2.5.0 in favor of
+        * {@link #configure(SpringApplicationBuilder, ServletContext)}
         */
+       @Deprecated
        protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
                return builder;
        }

+       /**
+        * Configure the application. Normally all you would need to do is to add sources
+        * (e.g. config classes) because other settings have sensible defaults. You might
+        * choose (for instance) to add default command line arguments, or set an active
+        * Spring profile.
+        * @param builder a builder for the application context
+        * @param servletContext the servlet context for the web application
+        * @return the application builder
+        * @see SpringApplicationBuilder
+        */
+       protected SpringApplicationBuilder configure(SpringApplicationBuilder builder, ServletContext servletContext) {
+               return configure(builder);
+       }
+
        /**
         * {@link ApplicationListener} to trigger
         * {@link ConfigurableWebEnvironment#initPropertySources(ServletContext, javax.servlet.ServletConfig)}.

Comment From: snicoll

I agree this should be done in custom code. The change looks like an improvement on surface but, unfortunately, I think it makes it harder for folks that want to share the configuration of their app if they run it also via java -jar. The example I've shared above illustrates it quite nicely. With ServletContext being enforced in the signature, it is now a requirement to call configure.

Comment From: wilkinsona

Good point, @snicoll. Thank you. At the very least that's an argument against deprecating configure(SpringApplicationBuilder). However, I think having two configure methods for the longer-term would be confusing so the alternative is a war-deployment-specific method that takes the ServletContext and allows further fine-tuning of the SpringApplicationBuilder. That's really not much different to overriding onStartup(ServletContext) as shown above so I don't think it's worth it.

I think the use case is sufficiently unusual that it's hard to justify compromising the API for SpringBootServletInitializer, particularly given that it's already possible by overriding both onStartup(ServletContext) and configure(SpringApplicationBuilder). All told, I think it's best if we decline this one. Thanks anyway for the suggestion, @reda-alaoui.

Comment From: reda-alaoui

What about passing the ServletContext to SpringBootServletInitializer.createSpringApplicationBuilder()?

Like this:

public class App extends SpringBootServletInitializer {

    public static void main(String[] args) {
        new App().configure(new SpringApplicationBuilder()).run(args);
    }

    @Override
    protected SpringApplicationBuilder createSpringApplicationBuilder(ServletContext servletContext) {
                String appName = StringUtils.removeStart(this.servletContext.getContextPath(), "/");
        return super.createSpringApplicationBuilder(servletContext).properties("spring.application.name=" + appName);
    }

}

Comment From: wilkinsona

Thanks for the suggestion. The createSpringApplicationBuilder method is intended to allow you to use a custom sub-class of SpringApplicationBuilder. It isn't intended as a hook point for configuring the SpringApplicationBuilder instance. Furthermore, adding ServletContext to its signature may break existing usage in an executable war where the ServletContext isn't available from the main method.