What is the difference between ServletContainerInitializer and ServletContextInitializer

Posted May 27, 202017 min read

Copyright statement:This article is an original blogger's article, please attach the original source link and statement.
Link to the original text:[ http://uhfun.cn/tech/2020/05/22/Stupid indistinguish-ServletContainerInitializer-SpringServletContainerInitializer-WebApplicationInitializer-SpringBootServletInitializer-ServletContextInitializer are all what.html]( http://uhfun.cn/tech/2020/05/22 /%E5%82%BB%E5%82%BB%E5%88%86%E4%B8%8D%E6%B8%85-ServletContainerInitializer-SpringServletContainerInitializer-WebApplicationInitializer-SpringBootServletInitializer-ServletContextInitializer%E9%83%BD%E6%98%AF%E4%BA%9B%E5%95%A5.html)

At first glance, these classes look a bit, but at a closer look, they look a bit similar, especially ServletContainerInitializer and ServletContextInitializer. Those with bad eyesight may not really see it. What is the difference between them? Is there any connection between them?

Fully qualified name

First let ’s take a look at their fully qualified names

  • ServletContainerInitializer(javax.servlet.ServletContainerInitializer)
  • SpringServletContainerInitializer(org.springframework.web.SpringServletContainerInitializer)
  • WebApplicationInitializer(org.springframework.web.WebApplicationInitializer)
  • SpringBootServletInitializer(org.springframework.boot.web.servlet.support.SpringBootServletInitializer)
  • ServletContextInitializer(org.springframework.boot.web.servlet.ServletContextInitializer)

ServletContainerInitializer

What is ServletContainerInitializer

Because this is not from the spring family, let's talk about this class first

ServletContainerInitializer is an interface introduced in the servlet3.0 specification, which enables web applications to do some custom operations after the servlet container is started.

ServletContainerInitializer Based on the concept of service provider interface(SPI), so you need to be in your jar package directory Add META-INF/services/javax.servlet.ServletContainerInitializer file, the content is the fully qualified name of the ServletContainerInitializer implementation class.

For example, the javax.servlet.ServletContainerInitializer file exists in META-INF/services of org.springframework:spring-web, and its content is the implementation class org.springframework.web.SpringServletContainerInitializer provided by springMVC

How to use ServletContainerInitializer

ServletContainerInitializer \ `only one method

package javax.servlet;
...
public interface ServletContainerInitializer {

    public void onStartup(Set <Class <? >> c, ServletContext ctx) throws ServletException;
}

The ServletContainerInitializer # onStartup method is called by the Servlet container(must support at least Servlet 3.0 version). In this method, we programmatically register components such as Servlet`` Filter Listenner, instead of web.xml.

Can cooperate with @ HandleTypes annotation, by specifying Class, the container will put all the children of the specified class The class is passed in as the parameter Set <Class <? >> c of the method onStartup

For example, SpringServletContainerInitializer passed the spring custom WebApplicationInitializer

package org.springframework.web;
...
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
...
}

SpringServletContainerInitializer and WebApplicationInitializer


Let's follow the previous example, let's talk about SpringServletContainerInitializer, so why talk with WebApplicationInitializer?

What is SpringServletContainerInitializer

It is not difficult to see from the name that it has more Spring prefix than ServletContainerInitializer, as the name implies, it is the implementation class of ServletContainerInitializer provided by Spring

What is WebApplicationInitializer

WebApplicationInitializer is an interface provided by Spring and has no direct relationship with ServletContainerInitializer, but has an indirect relationship with it. WebApplicationInitializer is called after it is instantiated in SpringServletContainerInitializer.

Relationship between SpringServletContainerInitializer and WebApplicationInitializer

The relationship between them can be understood as, SpringServletContainerInitializer implements the interface provided by the servlet container with a head, and the next thing can be handed over to WebApplicationInitializer defined by Spring

The source code of SpringServletContainerInitializer is as follows. As you can see, SpringServletContainerInitializer instantiates the incoming webAppInitializerClasses through reflection, and then sorts them according to the annotation @ Order`

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
   @Override
   public void onStartup(@Nullable Set <Class <? >> webAppInitializerClasses, ServletContext servletContext)
         throws ServletException {
      List <WebApplicationInitializer> initializers = new LinkedList <>();
      if(webAppInitializerClasses! = null) {
         for(Class <?> waiClass:webAppInitializerClasses) {
            if(! waiClass.isInterface() &&! Modifier.isAbstract(waiClass.getModifiers()) &&
                  WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
               try {
                  initializers.add((WebApplicationInitializer)
                        ReflectionUtils.accessibleConstructor(waiClass) .newInstance());
               }
               catch(Throwable ex) {
                  throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
               }
            }
         }
      }
      if(initializers.isEmpty()) {
         servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
         return;
      }
      servletContext.log(initializers.size() + "Spring WebApplicationInitializers detected on classpath");
      AnnotationAwareOrderComparator.sort(initializers);
      for(WebApplicationInitializer initializer:initializers) {
         initializer.onStartup(servletContext);
      }
   }

}

How to use SpringServletContainerInitializer and WebApplicationInitializer

We can use the WebApplicationInitializer interface instead of web.xml configuration

Inheriting AbstractAnnotationConfigDispatcherServletInitializer

For example, we define a class that inherits the AbstractAnnotationConfigDispatcherServletInitializer provided by Spring. We do not need to add @Configuration and other annotations, because the Servlet container will automatically pass our custom MyCustomWebApplicationInitializer class into SpringServletContainerInitializer # onStartup, andSpringServletContainerInitializerWill instantiate this class for us and call it.

public class MyCustomWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

   /**
     * For {@ linkPlan # createRootApplicationContext() root application context}
     * Specify {@ code @ Configuration} and/or {@ code @ Component} classes
     * @ Return to the configuration of the root application context, if you do not need to create and register the root context, then return {@code null
     * /
    @Nullable
    @Override
    protected Class <?> []getRootConfigClasses() {
        return null;
    }

   /**
     * Specify for {@ linkPlan # createServletApplicationContext() Servlet application context}
     * {@ code @ Configuration} and/or {@ code @ Component} classes
     * @Return to the configuration of the Servlet application context, or if all configurations are specified by the root configuration class, {@code null}
     * /
    @Nullable
    @Override
    protected Class <?> []getServletConfigClasses() {
        return new Class <?> []{WebConfig.class};
    }

   /**
    * Specify the Servlet mapping of {@code DispatcherServlet}
    * For example {@code “/”}, {@code “/app”}, etc.
    * /
    @Override
    protected String []getServletMappings() {
        return new String []{"/"};
    }

   /**
    * Return the name of registered {@link DispatcherServlet}
    * The default is {@ link # Default_Servlet_Name}(public static final String DEFAULT_SERVLET_NAME = "dispatcher";)
    * /
    @Override
    protected String getServletName() {
        return "myCustomServletName";
    }
}

Implement WebApplicationInitializer yourself

Similarly, we can implement a simple version of WebApplicationInitializer with the same effect.

public class SimpleWebApplicationInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) {
        AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext();
        servletContext.addListener(new ContextLoaderListener(webContext));
        webContext.register(WebConfig.class);
        ServletRegistration.Dynamic registration = servletContext.addServlet("myCustomServletName", new DispatcherServlet(webContext));
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}

SpringBootServletInitializer

What is SpringBootServletInitializer

From the name, we can roughly guess that it is provided by SpringBoot, and SpringBoot implements some functions for us

It is an abstract class that implements the WebApplicationInitializer interface. Note that it is not the interface provided by the Servlet 3.0 specification, but the self-initialization interface provided by Spring. You asked me what it does Let's take a look at what the source code says

This is a stubborn WebApplicationInitializer that allows us to deploy and run SpringApplication using the traditional WAR package, which can bind servlet, filter and ServletContextInitializer from the application context to the server.

If you want to configure the application, either override the configure(SpringApplicationBuilder) method(call SpringApplicationBuilder # Sources(Class.)) Or make the initializer itself become @ configuration. If you use SpringBootServletInitializer in combination with other WebApplicationInitializer, you may also need to add the @Ordered annotation to configure a specific startup sequence.

Please note that WebApplicationInitializer is only required when building and deploying WAR files. If you prefer to run an embedded web server, then you don't need this at all.

/**
* An opinionated {@link WebApplicationInitializer} to run a {@link SpringApplication}
* from a traditional WAR deployment. Binds {@link Servlet}, {@link Filter} and
* {@link ServletContextInitializer} beans from the application context to the server.
*


* To configure the application either override the
* {@link #configure(SpringApplicationBuilder)} method(calling
* {@link SpringApplicationBuilder # sources(Class ...)}) or make the initializer itself a
* {@code @Configuration}. If you are using {@link SpringBootServletInitializer} in
* combination with other {@link WebApplicationInitializer WebApplicationInitializers} you
* might also want to add an {@code @Ordered} annotation to configure a specific startup
* order.
*


* Note that a WebApplicationInitializer is only needed if you are building a war file and
* deploying it. If you prefer to run an embedded web server then you won't need this at
* all.
*
* @author Dave Syer
* @author Phillip Webb
* @author Andy Wilkinson
* @since 2.0.0
* @see #configure(SpringApplicationBuilder)
* /
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {

What do these words mean briefly?

It means that if you need to use an external container, war package and then deploy, then you need to inherit this class and override the configure method, like this

@SpringBootApplication
public class DemoSpringmvcApplication extends SpringBootServletInitializer {
  @Override
  protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
    return builder.sources(DemoSpringmvcApplication.class);
  }
}

If you think the embedded tomcat is very easy to use, then you should think nothing happened

How does SpringBootServletInitializer work

First of all, you need to know that it is a WebApplicationInitializer and it is started by SpringServletContainerInitializer,

The SpringServletContainerInitializer relies on the Servlet container. So it depends on the external container to start.

So when the external container starts, it will call SpringBootServletInitializer # onStartup

Let's take a look at what it does in the onStartup method. It first calls createRootApplicationContext to create the web application context

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
   this.logger = LogFactory.getLog(getClass());
   WebApplicationContext rootAppContext = createRootApplicationContext(servletContext);
   if(rootAppContext! = null) {
      servletContext.addListener(new ContextLoaderListener(rootAppContext) {
         @Override
         public void contextInitialized(ServletContextEvent event) {
            //no-op because the application context is already initialized
         }
      });
   }
   else {
      this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not"
            + "return an application context");
   }
}

Let's look at the createRootApplicationContext method

protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
   SpringApplicationBuilder builder = createSpringApplicationBuilder();
   builder.main(getClass());
   ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
   if(parent! = null) {
      this.logger.info("Root context already created(using as parent).");
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
      builder.initializers(new ParentContextApplicationContextInitializer(parent));
   }
   builder.initializers(new ServletContextApplicationContextInitializer(servletContext));
   builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
   builder = configure(builder);
   builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
   SpringApplication application = builder.build();
   if(application.getAllSources(). isEmpty()
         && MergedAnnotations.from(getClass(), SearchStrategy.TYPE_HIERARCHY) .isPresent(Configuration.class)) {
      application.addPrimarySources(Collections.singleton(getClass()));
   }
   Assert.state(! Application.getAllSources(). IsEmpty(),
         "No SpringApplication sources have been defined. Either override the"
               + "configure method or add an @Configuration annotation");
   //Ensure error pages are registered
   if(this.registerErrorPageFilter) {
      application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
   }
   return run(application);
}
  1. If there is a parent application context, set an initializer ParentContextApplicationContextInitializer, this initializer will set the parent application context and add event listener beforeConfigurableApplicationContext # refresh()refresh

  2. Set an initializer for servletContext initialization, and also add servletContext to the application context beforeConfigurableApplicationContext # refresh()refreshes

  3. Set the context type, AnnotationConfigServletWebServerApplicationContext

  4. Add more sources(configuration class @ Configuration and component @ Component) to this application.

    For example, the configure rewritten in the above startup class is to add our DemoSpringmvcApplication as a configuration class to the source, so that these configurations can be read after startup, because this DemoSpringmvcApplication has the annotation @ SpringBootApplication`

    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
    return builder.sources(DemoSpringmvcApplication.class);
    }

    Looking at the source code, the @ SpringBootApplication annotation is a more powerful @ Configuration

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)})
    public @interface SpringBootApplication {
    ...
    }

  5. Add WebEnvironmentPropertySourceInitializer listener, the listened event is ApplicationEnvironmentPreparedEvent(event when SpringApplication is starting and environment is available for inspection and modification for the first time)

  6. Build SpringApplication, if configure has no configuration source, it will check whether it is a configuration class, if it is added to primarySources, it means that if the subclass of SpringBootServletInitializer is a configuration class, it will be added to source by default in

  7. Make sure that the error page is registered

  8. Run

ServletContextInitializer

What is ServletContextInitializer

Its method is exactly the same as WebApplicationInitializer, but it is provided by SpringBoot

Let's see how the source code is described

Interface for configuring Servlet 3.0+{@link ServletContext Context} programmatically. Unlike WebApplicationInitializer, the class that implements this interface(and does not implement WebApplicationInitializer) ** will not be detected by SpringServletContainerInitializer, so the Servlet container will not be automatically guided

The design of this interface is similar to ServletContainerInitializer, but its life cycle is managed by Spring, not the Servlet container.

For configuration examples, see WebApplicationInitializer

/**
* Interface used to configure a Servlet 3.0+ {@link ServletContext context}
* programmatically. Unlike {@link WebApplicationInitializer}, classes that implement this
* interface(and do not implement {@link WebApplicationInitializer}) will not be
* detected by {@link SpringServletContainerInitializer} and hence will not be
* automatically bootstrapped by the Servlet container.
*


* This interface is designed to act in a similar way to
* {@link ServletContainerInitializer}, but have a lifecycle that's managed by Spring and
* not the Servlet container.
*


* For configuration examples see {@link WebApplicationInitializer}.
*
* @author Phillip Webb
* @since 1.4.0
* @see WebApplicationInitializer
* /
@FunctionalInterface
public interface ServletContextInitializer {

  /**
    * Configure the given {@link ServletContext} with any servlets, filters, listeners
    * context-params and attributes necessary for initialization.
    * @param servletContext the {@code ServletContext} to initialize
    * @throws ServletException if any call against the given {@code ServletContext}
    * throws a {@code ServletException}
    * /
   void onStartup(ServletContext servletContext) throws ServletException;

}

Why did you design this interface?

Isn't it possible to directly implement a ServletContainerInitializer? Why use this interface to achieve the same function.

Because the designer does not allow the embedded container to support ServletContainerInitializer, you can find some answers in this issue

This was actually an intentional design decision. The search algorithm used by the containers was problematic. It also causes problems when you want to develop an executable WAR as you often want a javax.servlet.ServletContainerInitializer for the WAR that is not executed when you run java -jar.

See the org.springframework.boot.context.embedded.ServletContextInitializer for an option that works with Spring Beans.

Do you have a specific case where this is causing problems or was it more of an observation?

This is actually an intentional design decision. There is a problem with the search algorithm used by the container. This can also cause problems when you want to develop an executable WAR, because you usually need a javax.servlet.ServletContainerInitializer for WAR that is not executed when running java-jar.

For options on using SpringBean, see org.springframework.boot.context.embedded.ServletContextInitializer.

My understanding is that when your project is under development, for development convenience, you may use an embedded container. When deploying to the build environment, the war package may be used and deployed to an external container. This way your code must be compatible with both methods. For example like this

@SpringBootApplication
public class DemoSpringmvcApplication extends SpringBootServletInitializer {

    //When using an inline container, the main method entry is here, and I started my inline container at a certain period of time during initialization
      //Ignore my presence when using external containers
    public static void main(String []args) {
        SpringApplication.run(DemoSpringmvcApplication.class, args);
    }

    //When using an inline container, I will not be called.
    //When the external container is detected, the external container detects SpringServletContainerInitializer, and then it detects me who inherits from WebApplicationInitializer, then I am called, and the initialization begins
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources();
    }
}

If the embedded container supports ServletContainerInitializer, then this code will run unexpectedly. Of course, there will be no problems, I do n’t know

How is ServletContextInitializer called

The link is very long, so I will find the key code where ServletContextInitializer is called

First, createWebServer will be called in ServletWebServerApplicationContext # onRefresh, as the name suggests, create a web container

@Override
protected void onRefresh() {
   super.onRefresh();
   try {
      createWebServer();
   }
   catch(Throwable ex) {
      throw new ApplicationContextException("Unable to start web server", ex);
   }
}

As you can see, in createWebServer, according to webServer == null && servletContext == null, I did not start an external container before creating the application context, so create an embedded container, factory.getWebServer(getSelfInitializer()) , Passed in servletContextInitializers,

private void createWebServer() {
   WebServer webServer = this.webServer;
   ServletContext servletContext = getServletContext();
   if(webServer == null && servletContext == null) {
      ServletWebServerFactory factory = getWebServerFactory();
      this.webServer = factory.getWebServer(getSelfInitializer());
   }
   else if(servletContext! = null) {
      try {
         getSelfInitializer(). onStartup(servletContext);
      }
      catch(ServletException ex) {
         throw new ApplicationContextException("Cannot initialize servlet context", ex);
      }
   }
   initPropertySources();
}

Method call createWebServer-> getWebServer-> prepareContext-> configureContext

Then came to configureContext, one step is very important,context.addServletContainerInitializer(starter, NO_CLASSES)

protected void configureContext(Context context, ServletContextInitializer []initializers) {
   TomcatStarter starter = new TomcatStarter(initializers);
   if(context instanceof TomcatEmbeddedContext) {
      TomcatEmbeddedContext embeddedContext =(TomcatEmbeddedContext) context;
      embeddedContext.setStarter(starter);
      embeddedContext.setFailCtxIfServletStartFails(true);
   }
   context.addServletContainerInitializer(starter, NO_CLASSES);
   ...
}

Let's take a look at this method. It puts no one else in it, it is the famous ServletContainerInitializer

/**
* Add a ServletContainerInitializer instance to this web application.
*
* @param sci The instance to add
* @param classes The classes in which the initializer expressed an
* interest
* /
public void addServletContainerInitializer(
ServletContainerInitializer sci, Set <Class <? >> classes);

Then let's see where this TomcatStarter is sacred, take a closer look, inside it is to call the incoming ServletContextInitializers in turn

class TomcatStarter implements ServletContainerInitializer {

   private static final Log logger = LogFactory.getLog(TomcatStarter.class);

   private final ServletContextInitializer []initializers;

   private volatile Exception startUpException;

   TomcatStarter(ServletContextInitializer []initializers) {
      this.initializers = initializers;
   }
   @Override
   public void onStartup(Set <Class <? >> classes, ServletContext servletContext) throws ServletException {
      try {
         for(ServletContextInitializer initializer:this.initializers) {
            initializer.onStartup(servletContext);
         }
      }
      catch(Exception ex) {
         this.startUpException = ex;
         //Prevent Tomcat from logging and re-throwing when we know we can
         //deal with it in the main thread, but log for information here.
         if(logger.isErrorEnabled()) {
            logger.error("Error starting Tomcat context. Exception:" + ex.getClass(). getName() + ". Message:"
                  + ex.getMessage());
         }
      }
   }
   Exception getStartUpException() {
      return this.startUpException;
   }

}

Seeing this, we found that the embedded tomcat does not load ServletContainerInitializer in spi mode, but instead uses the onStartup of TomcatStarter to indirectly start ServletContextInitializers to achieve the effect of ServletContainerInitializer

Is it a bit like SpringServletContainerInitializer big brother with WebApplicationInitializer routine

So it is not unreasonable for you to refer to @see WebApplicationInitializer in the source code of ServletContextInitializer

 *
 * @author Phillip Webb
 * @since 1.4.0
 * @see WebApplicationInitializer
 * /
@FunctionalInterface
public interface ServletContextInitializer {

ServletContextInitializer application

Does ServletContextInitializer have ready-made applications?

Have! DispatcherServletRegistrationBean, what is it? After detecting the configuration of DispatcherServletAutoConfiguration, after it becomes effective, DispatcherServletRegistrationBean is handed over to the IOC container of spring, so ServletContextInitializer can be obtained

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
  ...
  @Configuration(proxyBeanMethods = false)
    @Conditional(DispatcherServletRegistrationCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    @Import(DispatcherServletConfiguration.class)
    protected static class DispatcherServletRegistrationConfiguration {
        @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
        @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
                WebMvcProperties webMvcProperties, ObjectProvider <MultipartConfigElement> multipartConfig) {
            DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
                    webMvcProperties.getServlet(). getPath());
            registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
            registration.setLoadOnStartup(webMvcProperties.getServlet(). getLoadOnStartup());
            multipartConfig.ifAvailable(registration ::setMultipartConfig);
            return registration;
        }
    }
}

It was so taken

  1. Call factory.getWebServer(getSelfInitializer()) in org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext # createWebServer mentioned earlier

  2. Then call org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext # getServletContextInitializerBeans

     protected Collection <ServletContextInitializer> getServletContextInitializerBeans() {
        return new ServletContextInitializerBeans(getBeanFactory());
     }
  3. The ServletContextInitializerBeans class is a collection class that encapsulates ServletContextInitializer. Through its construction method, we can also find that it has passed into beanFactory. You can probably guess that it is from the Spring container without looking at the source code To find bean instances of these classes

     public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
           Class <? Extends ServletContextInitializer> ... initializerTypes) {}

to sum up

Having said that, the starting point of these classes is to register Servlet, Filter or EventListener in the ServletContext container

It's just that the life cycle is hosted by different containers and called in different places, but the end result is the same