Today we are going to relax, not talk about distributed, cloud native, to talk about the most beginner contact Java Web foundation. Almost everyone started their first Hello World project with servlets, JSPS, and Filters. At that time, it was necessary to configure web.xml, and to write the tedious configuration of servlets and filters in XML files. With the popularity of Spring, configuration has evolved in two ways — Java Configuration and XML configuration. Nowadays, with the popularity of SpringBoot and Java Configuration in the mainstream, XML configuration seems to have become “extinct”. Have you ever wondered what changes have taken place, and what alternatives have replaced configuration items in web.xml?

servlet

To illustrate the evolution of the pre-Servlet 3.0 era, let’s look back at how servlets and filters were written years ago.

Project structure (This article uses maven project structure)

public class HelloWorldServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.setContentType("text/plain");
    PrintWriter out = resp.getWriter();
    out.println("hello world");
}
Copy the code

public class HelloWorldFilter implements Filter {

@Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {system.out. println(" Trigger hello World filter..." ); filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy() { }Copy the code

} Don’t forget to configure the servlet and filter in web.xml

<servlet>
    <servlet-name>HelloWorldServlet</servlet-name>
    <servlet-class>moe.cnkirito.servlet.HelloWorldServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>HelloWorldServlet</servlet-name>
    <url-pattern>/hello</url-pattern>
</servlet-mapping>

<filter>
    <filter-name>HelloWorldFilter</filter-name>
    <filter-class>moe.cnkirito.filter.HelloWorldFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>HelloWorldFilter</filter-name>
    <url-pattern>/hello</url-pattern>
</filter-mapping>
Copy the code

This completes a Java Web Hello World. Of course, this article is not a tutorial on servlets, just for comparison.

Servlet 3.0, a member of the Java EE 6 specification architecture, is released with the Java EE 6 specification. This release builds on the previous release (Servlet 2.5) with several new features to simplify the development and deployment of Web applications. One of the new features is the ability to provide no XML configuration.

Servlet 3.0 first provides @webServlet, @webFilter, etc. annotations, so there is a first way to get rid of Web.xml by declaring servlets and filters with annotations.

In addition to this approach, the Servlet 3.0 specification provides more powerful features to dynamically register servlets, filters, and Listeners at run time. In the case of servlets, filters and listeners are similar. ServletContext adds the following methods to dynamically configure servlets:

ServletRegistration.Dynamic addServlet(String servletName,Class<? extends Servlet> servletClass) ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet) ServletRegistration.Dynamic addServlet(String servletName, String className) T createServlet(Class clazz) ServletRegistration getServletRegistration(String servletName) Map<String,? Extends ServletRegistration> getServletRegistrations() the first three methods do the same thing, except for different parameter types. A Servlet created through the createServlet() method usually needs to do some custom configuration and then use the addServlet() method to dynamically register it as a Servlet that can be used as a service. The two getServletRegistration() methods are primarily used to dynamically add mapping information to servlets, which is equivalent to adding mapping information to existing servlets using tags in web.xml.

The methods added to the ServletContext are either called in the contexInitialized method of the ServletContextListener, Either in ServletContainerInitializer onStartup () method call.

ServletContainerInitializer Servlet 3.0 add an interface, Container when it starts using the JAR Service API (JAR Service API) to find ServletContainerInitializer implementation class, And the container hands the classes in the JAR package in the WEB-INF/lib directory to the onStartup() method of that class. We usually use the @handlestypes annotation on the implementation class to specify which classes we want to process. Filter out classes that you don’t want onStartup() to handle.

The structure of a typical servlet3.0+ web project is as follows:

. ├ ─ ─ pom. XML └ ─ ─ the SRC ├ ─ ─ the main │ ├ ─ ─ Java │ │ └ ─ ─ MOE │ │ └ ─ ─ cnkirito │ │ ├ ─ ─ CustomServletContainerInitializer. Java │ │ ├─ ├─ Java │ ├─ Java │ ├─ Java │ ├─ Java │ ├─ Java │ ├─ Java │ ├─ Java │ ├─ Java │ ├─ Java │ ├─ Java │ ├─ Java │ ├─ Java │ ├─ Meta-inf │ └ ─ ─ services │ └ ─ ─ javax.mail. Servlet. ServletContainerInitializer └ ─ ─ the test └ ─ ─ I did not to HelloWorldServlet and Java HelloWorldFilter to make any changes, but a new CustomServletContainerInitializer, it implements the javax.mail. Servlet. ServletContainerInitializer interface, To load the specified servlet and filter when the Web container starts, the code looks like this:

public class CustomServletContainerInitializer implements ServletContainerInitializer {

private final static String JAR_HELLO_URL = “/hello”;

@Override public void onStartup(Set<Class<? >> c, ServletContext servletContext) {

System. Out.println (" create helloWorldServlet..." ); ServletRegistration.Dynamic servlet = servletContext.addServlet( HelloWorldServlet.class.getSimpleName(), HelloWorldServlet.class); servlet.addMapping(JAR_HELLO_URL); System. Out.println (" create helloWorldFilter..." ); FilterRegistration.Dynamic filter = servletContext.addFilter( HelloWorldFilter.class.getSimpleName(), HelloWorldFilter.class); EnumSet<DispatcherType> dispatcherTypes = EnumSet.allOf(DispatcherType.class); dispatcherTypes.add(DispatcherType.REQUEST); dispatcherTypes.add(DispatcherType.FORWARD); filter.addMappingForUrlPatterns(dispatcherTypes, true, JAR_HELLO_URL);Copy the code

}} Some interpretation of the above code. ServletContext is called servlet context. It maintains servlets, filters, and listeners registered in the entire Web container. You can use the servletContext. AddServlet etc method to add the servlet. The Set<Class> c and @handlestypes annotations are not used in the demo. If you are interested, you can debug to see which classes are retrieved. The normal process is to use @handlestypes to specify the class to be processed, and then determine if Set> belongs to that class. As mentioned earlier, onStartup loads classes that do not need to be processed.

Such a statement ServletContainerInitializer implementation classes, the web container does not recognize it, so, need to use SPI mechanism to specify that the initialization, Under the project path, this step is to create a meta-inf/services/javax.mail servlet. ServletContainerInitializer to do that, it contains only a single line

MOE. Cnkirito. Use CustomServletContainerInitializer ServletContainerInitializer and SPI mechanism, our web applications can completely get rid of the web. The XML.

How does Spring support servlet3.0? Back in the spring family bucket, I may have forgotten exactly when I stopped writing web.xml, but I do know that projects don’t see it anymore, and how does spring support the servlet3.0 specification?

Looking for spring ServletContainerInitializer implementation class is not difficult, can quickly SpringServletContainerInitializer to locate the implementation class.

@HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(Set<Class<? >> webAppInitializerClasses, ServletContext servletContext) throws ServletException {

List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>(); if (webAppInitializerClasses ! = null) { for (Class<? > waiClass : webAppInitializerClasses) { // Be defensive: Some servlet containers provide us with invalid classes, // no matter what @HandlesTypes says... // <1> if (! waiClass.isInterface() && ! Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try { initializers.add((WebApplicationInitializer) 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); // <2> for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); }}Copy the code

}

See its Java Doc, described as follows:

The Servlet 3.0 {@ link ServletContainerInitializer} designed to support code – -based configuration of the Servlet container Using Spring’s {@ link WebApplicationInitializer} SPI as opposed to (or possibly in combination with) the traditional {@code web.xml}-based approach.

Note that I annotated two serial numbers in the source code, which is important for understanding spring’s process of assembling servlets.

< 1 > English annotation is the spring’s own source code, it prompts us due to differences in the servlet vendors implement onStartup method loads we didn’t want to deal with the class, so the sentence.

< 2 > spring before the demo is different with us, and not directly to the servlet and the filter in the SpringServletContainerInitializer register, But is delegated to a strange kind of WebApplicationInitializer WebApplicationInitializer class spring is used to initialize the consignor of the web environment, it usually has three implementation class:

WebApplicationInitializer

You’re no stranger to Dispatcherservlets, AbstractDispatcherServletInitializer# registerDispatcherServlet is no web. On the premise of XML to create dispatcherServlet key code.

Can go to a program to find the org. Springframework: spring – web: version, below it is a servletContainerInitializer extension, Points to the SpringServletContainerInitializer, such as long as in servlet3.0 environment deployment, the spring can be initialized automatically loaded:

SpringServletContainerInitializer

Note that all of these features have been around since Spring 3, and spring 5 has now been released with SpringBoot 2.0.

How does SpringBoot load servlets? By this point, you have read half of the text. Springboot’s handling of servlets is important, firstly, because springBoot is so widely used that few people use Spring instead of SpringBoot. Second, because it doesn’t fully comply with the servlet3.0 specification!

Yes, the front is describing the servlet specification, whether web. In XML configuration, or servlet3.0 ServletContainerInitializer and springboot in loading process are not too big. As usual, let’s keep things in suspense, see how servlets and filters are registered in SpringBoot, and then explain what makes SpringBoot unique.

Springboot is still compatible with servlet3.0 annotations that start with @Web* : @webServlet, @webFilter, @webListener

@WebServlet(“/hello”) public class HelloWorldServlet extends HttpServlet{}

@webFilter (“/hello/*”) public class HelloWorldFilter implements Filter {} Don’t forget to ask the bootstrap class to scan for these annotations

@SpringBootApplication @ServletComponentScan public class SpringBootServletApplication {

public static void main(String[] args) { SpringApplication.run(SpringBootServletApplication.class, args); }} I think this is the simplest of the several ways. If you really have special needs, you need to register servlet and filter under SpringBoot, you can use this way, which is more intuitive.

Registration Method 2: RegistrationBean @Bean public ServletRegistrationBean helloWorldServlet() { ServletRegistrationBean helloWorldServlet = new ServletRegistrationBean(); myServlet.addUrlMappings(“/hello”); myServlet.setServlet(new HelloWorldServlet()); return helloWorldServlet; }

@Bean public FilterRegistrationBean helloWorldFilter() { FilterRegistrationBean helloWorldFilter = new FilterRegistrationBean(); myFilter.addUrlPatterns(“/hello/*”); myFilter.setFilter(new HelloWorldFilter()); return helloWorldFilter; } ServletRegistrationBean and FilterRegistrationBean both integrate with the self-registrationbean, a registered class widely used in springboot, Be responsible for containerizing servlets, filters, and listeners so that they are hosted by Spring and register themselves with the Web container. This registration method is also worthy of praise.

RegistrationBean

You can see the status of RegistrationBean in the figure. Its implementation classes are: Help filter vessel registration, the servlet, the listener, the final DelegatingFilterProxyRegistrationBean use much, but familiar with SpringSecurity friends won’t feel strange, SpringSecurityFilterChain is through the proxy class to call. Another RegistrationBean implements ServletContextInitializer interface, this interface will is the core of the following analysis interface, you mix a little familiar, first know it has an abstract implementation RegistrationBean can.

The source code analysis of the Servlet loading process in SpringBoot will only introduce these two methods for the time being. The premise of this discussion is the use of embedded containers in a SpringBoot environment, such as tomcat in its most typical case. High-energy warning, the following content is more brain-burning, feel that it looks difficult friends can skip this section to see the summary of the next section directly!

, Initializer is replaced with TomcatStarter when using the embedded tomcat, you will find springboot completely gone another set of initialization process, no use SpringServletContainerInitializer mentioned above, In fact at first I played in the implementation class of various ServletContainerInitializer breakpoints, eventually, to locate didn’t run to SpringServletContainerInitializer inside, Instead, it goes into the TomcatStarter class.

TomcatStarter

Also, a close look at the source package shows no SPI file corresponding to TomcatStarter. So I guess the loading of embedded tomcat may not depend on the servlet3.0 specification and SPI! It follows a completely separate logic. To verify this, I looked through the issue in Spring Github and got a positive response from the spring author: github.com/spring-proj…

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.ServletContextInitializerfor an option that works with Spring Beans.

Springboot is doing this by design. Springboot takes into account the following problems. When we use SpringBoot, we usually use embedded Tomcat containers in the development phase, but there are two choices in deployment: one is to make jar packages and use java-JAR mode to run; Another option is to create a WAR package and hand it to an external container to run. The former leads to the container search algorithm appear problem, because it is a jar package operation strategy, won’t load according to the strategy of servlet3.0 ServletContainerInitializer! Finally the author also provides an alternative option: ServletContextInitializer, attention is ServletContextInitializer! It and ServletContainerInitializer looks particularly like, don’t confuse, The former is ServletContextInitializer org. Springframework. Boot. Web. Servlet. ServletContextInitializer, The latter is ServletContainerInitializer javax.mail. Servlet. ServletContainerInitializer, Above mentioned RegistrationBean ServletContextInitializer interface is realized.

The ServletContextInitializer is the key of TomcatStarter TomcatStarter Org. Springframework. Boot. Context. Embedded. ServletContextInitializer servlet is springboot is initialized, the filter, the key to the listener.

class TomcatStarter implements ServletContainerInitializer {

private final ServletContextInitializer[] initializers;

TomcatStarter(ServletContextInitializer[] initializers) { this.initializers = initializers; }

@Override public void onStartup(Set<Class<? >> classes, ServletContext servletContext) throws ServletException { for (ServletContextInitializer initializer : this.initializers) { initializer.onStartup(servletContext); }}} after cut the source code, can be seen that the main logic TomcatStarter, it is responsible for calling a series of ServletContextInitializer onStartup method, then in the debug, ServletContextInitializer [] initializers contains what class? Will there be a RegisterBean like we introduced earlier?

initializers

There are only three classes in initializers. Only the first class looks core. Note that the first class is not EmbeddedWebApplicationContext! But in this class, $1 anonymous classes, in order to better understand how springboot load filter servlet listener, seems to have EmbeddedWebApplicationContext structure under study.

6 layer iteration of EmbeddedWebApplicationContext load ApplicationContext everyone should be familiar with, it is spring a comparative core classes, generally we can get to those registered the managed Bean in the container, This article, the main analysis is it embedded in a container implementation class: EmbeddedWebApplicationContext, analyzed it loads the filter servlet listener this part of the code. Here is the deepest part of the iteration throughout the code levels, begin to prepare for and EmbeddedWebApplicationContext will show you how to get to all the servlet filter listener! The following method from EmbeddedWebApplicationContext.

Layer 1: onRefresh()

OnRefresh is ApplicationContext lifecycle methods, EmbeddedWebApplicationContext implementation is very simple, only do one thing:

@Override protected void onRefresh() { super.onRefresh(); try { createEmbeddedServletContainer(); / / the second entry} the catch (Throwable ex) {throw new ApplicationContextException (” Unable to start embedded container “, the ex); }} createEmbeddedServletContainer connected to the second floor

The second: createEmbeddedServletContainer ()

Spring wants to create an embedded servlet container. ServletContainer is the general name for a Servlet Filter Listener.

private void createEmbeddedServletContainer() { EmbeddedServletContainer localContainer = this.embeddedServletContainer; ServletContext localServletContext = getServletContext(); if (localContainer == null && localServletContext == null) { EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory(); this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer()); } else if (localServletContext! = null) { try { getSelfInitializer().onStartup(localServletContext); } catch (ServletException ex) { throw new ApplicationContextException(“Cannot initialize servlet context”, ex); } } initPropertySources(); } All methods with the servlet, initializer are the ones to watch out for. GetSelfInitializer () addresses the initialization process we care about most.

Layer 3: getSelfInitializer()

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { return new ServletContextInitializer() { @Override public void onStartup(ServletContext servletContext) throws ServletException { selfInitialize(servletContext); }}; }

private void selfInitialize(ServletContext servletContext) throws ServletException { prepareEmbeddedWebApplicationContext(servletContext); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes( beanFactory); WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, getServletContext()); existingScopes.restore(); WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, getServletContext()); / / for entrance to the fourth floor (ServletContextInitializer beans: getServletContextInitializerBeans ()) {beans. OnStartup (servletContext); }} remember TomcatStarter debug information, in front of the first ServletContextInitializer is an anonymous class appears in the EmbeddedWebApplicationContext, yes, This is created by the getSelfInitializer() method! Explain why getSelfInitializer() and selfInitialize(ServletContext) are designed this way: This is a typical mode way back, when anonymous ServletContextInitializer classes are TomcatStarter onStartup method calls, This is designed to trigger a call to selfInitialize(ServletContext). So it is clear, why not appear in TomcatStarter RegisterBean, is actually in the implicit triggered EmbeddedWebApplicationContext selfInitialize method. SelfInitialize method of getServletContextInitializerBeans became the key ().

The fourth floor: getServletContextInitializerBeans ()

/ * *

  • Returns {@link ServletContextInitializer}s that should be used with the embedded
  • Servlet context. By default this method will first attempt to find
  • {@link ServletContextInitializer}, {@link Servlet}, {@link Filter} and certain
  • {@link EventListener} beans.
  • @return the servlet initializer beans */ protected Collection getServletContextInitializerBeans() { return new ServletContextInitializerBeans(getBeanFactory()); / / the entrance to the fifth floor} yeah, annotations are told us that this ServletContextInitializerBeans is used to load the Servlet and Filter.

The structure of the fifth floor: ServletContextInitializerBeans method

public ServletContextInitializerBeans(ListableBeanFactory beanFactory) { this.initializers = new LinkedMultiValueMap<Class<? >, ServletContextInitializer>(); addServletContextInitializerBeans(beanFactory); // Layer 6 entry addAdaptableBeans(beanFactory); List sortedInitializers = new ArrayList(); for (Map.Entry<? , List> entry : this.initializers .entrySet()) { AnnotationAwareOrderComparator.sort(entry.getValue()); sortedInitializers.addAll(entry.getValue()); } this.sortedList = Collections.unmodifiableList(sortedInitializers); } layer 6: addServletContextInitializerBeans (the beanFactory)

private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) { for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType( beanFactory, ServletContextInitializer.class)) { addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory); }} getOrderedBeansOfType method is to container for registered ServletContextInitializer, at that time you can put all those RegisterBean before load out, The RegisterBean also implements the Ordered interface, which is used here for ordering. No more iterations down.

Conclusion EmbeddedWebApplicationContext loading process If you are not interested in specific code process, you can skip the above 6 layer analysis, direct look at the conclusion of this section. The summary is as follows:

EmbeddedWebApplicationContext onRefresh method ServletContextInitializer trigger is configured with an anonymous. This anonymous ServletContextInitializer onStartup method will go search in all RegisterBean in container and according to the order is loaded into the ServletContext. The anonymous ServletContextInitializer eventually passed to TomcatStarter, Triggered by TomcatStarter onStartup method to ServletContextInitializer onStartup method, finally complete assembly. getServletContextInitializerBeans

The third finished registering the Servlet research springboot start the internals of the above, can find ServletContextInitializer is ServletContainerInitializer agent in the spring, Although Servlet3.0 is not working in SpringBoot, its proxy will still be loaded, so we have a third way to register servlets.

@Configuration public class CustomServletContextInitializer implements ServletContextInitializer {

private final static String JAR_HELLO_URL = "/hello"; @override public void onStartup(ServletContext ServletContext) throws ServletException {system.out.println (" Create helloWorldServlet..." ); ServletRegistration.Dynamic servlet = servletContext.addServlet( HelloWorldServlet.class.getSimpleName(), HelloWorldServlet.class); servlet.addMapping(JAR_HELLO_URL); System. Out.println (" create helloWorldFilter..." ); FilterRegistration.Dynamic filter = servletContext.addFilter( HelloWorldFilter.class.getSimpleName(), HelloWorldFilter.class); EnumSet<DispatcherType> dispatcherTypes = EnumSet.allOf(DispatcherType.class); dispatcherTypes.add(DispatcherType.REQUEST); dispatcherTypes.add(DispatcherType.FORWARD); filter.addMappingForUrlPatterns(dispatcherTypes, true, JAR_HELLO_URL); }Copy the code

} although ServletCantainerInitializer cannot be embedded container loading, ServletContextInitializer can be springboot EmbeddedWebApplicationContext loaded, To assemble the servlets and filters. In practice, there are still two ways to register. This is just a possibility to understand the springBoot loading process.

How is the load process picker TomcatStarter used by Spring if it is not assembled through the SPI mechanism? Natural is new, in TomcatEmbeddedServletContainerFactory# configureContext as you can see, in the TomcatStarter is active instantiation, And also introduced into ServletContextInitializer array, and the above analysis, a total of three ServletContextInitializer, contains the anonymous EmbeddedWebApplicationContext implementation.

protected void configureContext(Context context, ServletContextInitializer[] initializers) { TomcatStarter starter = new TomcatStarter(initializers); if (context instanceof TomcatEmbeddedContext) { // Should be true ((TomcatEmbeddedContext) context).setStarter(starter); } context.addServletContainerInitializer(starter, NO_CLASSES); . How was}} TomcatEmbeddedServletContainerFactory statement? @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @Configuration @ConditionalOnWebApplication @Import(BeanPostProcessorsRegistrar.class) public class EmbeddedServletContainerAutoConfiguration {

/** * Nested configuration if Tomcat is being used. */ @Configuration @ConditionalOnClass({ Servlet.class, Tomcat.class }) @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedTomcat {

  @Bean
  public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
     return new TomcatEmbeddedServletContainerFactory();
  }
Copy the code

Whenever the Tomcat class exists in the classpath and in the Web environment, springBoot will be automatically configured.

Java Web project with web.xml configuration, Java Web project with Servlet 3.0, Java Web project loading servlet with SpringBoot embedded container, filter, The listener process is different, it is not easy to understand the original, at least understand the servlet3.0 specification, springboot embedded container loading process and other pre-logic.

Finally thanks to Mr Ma’s inspiration, before this mistake: since TomcatStarter inherited ServletContainerInitializer, should be in conformity with the servlet3.0 specification, but actually has not been SPI load.

Reproduced in: my.oschina.net/u/2312080/b…