Search code examples
springspring-bootspring-integrationdeclarative

SpringIntegration and SpringBoot: choose webserver to start by a means other than dependency inclusion/exclusion


We have a project in spring-integration that will work with either webflex or servlet implementations and this seems to work pretty well using the spring.main.web-application-type property set as a system property.

I'm now looking for how to choose the embedded web server at runtime.

The spring-boot documentation says that this is accomplished via dependency inclusion or exclusion with the pom.xml.

https://docs.spring.io/spring-boot/docs/2.1.9.RELEASE/reference/html/howto-embedded-web-servers.html

What I'm looking for is to be able to start any of jetty, netty, undertow or tomcat from the same project/executable jar.

Is this feasible by some specific startup sequence in SpringApplication?

Thanks for any pointers/suggestions.


Solution

  • I think that I've found a relatively simple and clean way to do this.

    At Application context creation, spring-boot loads the WebServerApplicationContext in one of two flavors, depending upon the value in property spring.main.web-application-type: either reactive or servlet (respectively ReactiveWebServerApplicationContext and ServletWebServerApplicationContext).

    These two classes both have a method createWebServer() which will look for a bean of type (respectively) ReactiveWebServerFactory or ServletWebServerFactory loaded into the context, in which case this bean will be the WebServerFactory used by the WebApplicationContext.

    Hence need to declare the target WebServerFactory as a bean.

    There are a number of different ways of doing this, including using profiles.

    I've chosen to use a factory bean that instantiates the WebServerFactory as a function of a system property, here my.main.web-server (as a parallel to spring.main.web-application-type).

    Using a BeanFactory to create the bean that holds the WebServerFactory:

    package net.demo;
    
    ...
    
    public class MyWebServerFactoryBeanFactory implements EnvironmentAware {
        private Environment environment;
    
        public ReactiveWebServerFactory createReactiveWebServerFactory() {
            switch(getWebServer()) {
                case "jetty":
                    return new JettyReactiveWebServerFactory();
                case "netty":
                    return new NettyReactiveWebServerFactory();
                case "undertow":
                    return new UndertowReactiveWebServerFactory();
                case "tomcat":
                case default:
                    return new TomcatReactiveWebServerFactory();
            }
        }
    
        public ServletWebServerFactory createServletWebServerFactory() {
            switch(getWebServer()) {
                case "jetty":
                    return new JettyServletWebServerFactory();
                case "undertow":
                    return new UndertowServletWebServerFactory();
                case "tomcat":
                case default:
                    return new TomcatServletWebServerFactory();
            }
        }
    
        private String getWebServer() {
            return environment.getProperty("my.main.web-server");
        }
    }
    

    Using XML context definitions, I define the BeanFactory and import a source which will be different for reactive and servlet:

    ...
    <import resource="classpath:my-${spring.main.web-application-type:servlet}.xml" />
    
    <bean id="myWebServerFactoryBeanFactory"
          class="net.demo.MyWebServerFactoryBeanFactory" />
    ...
    

    Finally, create the WebServerFactory within the specific source file. Within my-reactive.xml:

    <bean id="myWebServerFactory" factory-bean="net.demo.MyWebServerFactoryBeanFactory"
                                  factory-method="createReactiveWebServerFactory" />
    

    and within my-servlet.xml:

    <bean id="myWebServerFactory" factory-bean="net.demo.MyWebServerFactoryBeanFactory"
                                  factory-method="createServletWebServerFactory" />
    

    I suppose that you don't necessarily need to have the specialized import because you can create both the ReactiveWebServerFactory and the ServletWebServerFactory within the context but only the matching bean will be used.