SpringBoot Source Text: Deep analysis of how SpringBoot eliminates web.xml

Posted by xeelee on Fri, 06 Dec 2019 12:36:19 +0100

1. Preface

Start with this blog post and officially start the Spring and SpringBoot Source Analysis journey.This may be a long process, because I have read the source code before is very one-sided, Spring source does not have a systematic understanding.From the beginning of this article, I will keep updating and strive to have a systematic understanding of Spring source code after the series is finished.

Set up the next flag here, hoping you can keep it up.If you have the privilege of learning a little from a series of articles, please comment, follow or recommend.If there are any errors, please point out in the comments section that we can discuss and grow together.

2. Historical Background of SpringBoot's Birth

With more and more individuals and businesses developing with Spring, Spring has slowly changed from a single simple and compact framework to a large and complete open source software. Spring's boundaries have been expanding. Later, Spring can do almost anything. The mainstream open source software and middleware on the market have Spring component support, and people are enjoying SpAfter ring's convenience, there are also some problems.Every time Spring integrates an open source software, it needs to add some basic configurations. Slowly, as people develop more and more projects, they often need to integrate a lot of open source software. So later, large projects using Spirng need to introduce many configuration files. Too many configurations are very difficult to understand and easy to configure incorrectly. Later, people even called Spring configurationInfernal.

Spring seems to be aware of these issues as well, and there is an urgent need for such a suite of software to solve them. At this time, the concept of micro-service is also rising, and it is more urgent to rapidly develop micro-independent applications. Just at this intersection, Spring started its research and development of Spring Boot project in early 2013. Spring Boot 1.0.0 was released in April 2014.

Since its inception, Spring Boot has received constant attention from the open source community, with some individuals and businesses trying to use it and quickly liking it.Spring Boot was not really used in China until 2016. During this period, many developers who studied Spring Boot wrote a lot of articles about Spring Boot on the Internet, while some companies made small-scale use within the enterprise and shared their experience.From 2016 to 2018, more and more enterprise and individual developers are using Spring Boot.The SpringBoot 2.0 release in 2018 pushed the popularity of SpringBoot to an unprecedented height.

3. Technical Basis of SpringBoot's Birth

1. The history of Spring

(1) Spring 1.0 Era

The birth of Spring greatly promoted the development of JAVA.It also reduces the technology and time costs of enterprise Java application development.

(2) Spring 2.0 era
spring1.0 has been optimized on a complex xml configuration file to make configuration seem simpler and simpler, but it does not address xml redundancy completely.

(3) Spring 3.0 era
You can use the java annotations provided by spring to replace problems with your XML configuration, as if we had forgotten what happened and spring became simpler than ever.Spring3.0 lays the foundation for SpringBoot automatic assembly.The java annotations provided in 3.0 allow us to configure the spring container by annotating it.Configuration files similar to spring-context.xml are omitted.

In the same year, the birth of the Servlet3.0 specification laid the theoretical foundation for SpringBoot to completely remove XML (web.xml) (web.xml is no longer necessary for servlet 3.0).However, the Servlet3.0 specification recommends retaining web.xml).

(4) Spring 4.0 Era
In the 4.0 era, we can quickly develop spring applications without even xml profiles requiring full use of java source-level configuration and spring-provided annotations. However, we still cannot change the running mode of Java Web applications. We still need to deploy war s on Web Server to provide services to the outside world.

4.0 Begins full support for Java 8.0

In the same year, the Servlet 3.1 specification was born (tomcat8 began using the Servlet 3.1 specification).

2. Servlet3.0 lays the foundation for SpringBoot zero xml configuration

Analyzing how SpringBoot omits web.xml also starts with the Servlet 3.0 specification.The Servlet3.0 specification is as follows (from the Servlet3.1 specification translated by Mu Maoqiang and Zhang Kaitao, 3.0 and 3.1 have only a few detailed transformations on this point, which are not described here too much):

The ServletContainerInitializer class is found through the jar services API.For each application, when the application starts, a ServletContainerInitializer instance is created by the container.The ServletContainerInitializer implementation provided by the framework must be bound to a file called javax.servlet.ServletContainerInitializer in the META-INF/services directory of the jar package, specifying the implementation of ServletContainerInitializer according to the jar services API.In addition to ServletContainerInitializer, we have a comment @HandlesTypes.The @HandlesTypes annotation on the ServletContainerInitializer implementation is used to represent some classes of interest, either by specifying annotations in the value of the HandlesTypes (type, method, or Auto-level annotations) or by inheriting/implementing one of these classes from a superclass of its type.The @HandlesTypes annotation applies whether metadata-complete is Set or not.When detecting an applied class to see if they match the conditions specified by ServletContainerInitializer's Handles Types, the container may encounter class loading problems if one or more of the optional JAR packages for the application are missing.Since the container cannot determine whether these types of class loading failures will prevent the application from working properly, it must ignore them and provide a configuration option that will record them.If the ServletContainerInitializer implementation does not have the @HandlesTypes annotation, or if it does not match any of the specified @HandlesTypes, it will be called once for each application with a collection of null values.This allows initializer to decide whether to initialize Servlet/Filter based on the resources available in the application.The onStartup method of ServletContainerInitializer is called when the application is starting before any Servlet Listener event is triggered.OnStartup of ServletContainerInitializer's gets a Set of classes, either inheriting/implementing initializer to represent the class of interest, or it uses any class annotation specified in the @HandlesTypes annotation.

How do you understand this specification?

Simply put, when a container that implements the Servlet 3.0 specification (such as tomcat7 and above) starts, automatically scans the full path class specified in META-INF/services/javax.servlet.ServletContainerInitializer under all added jar packages through the SPI extension mechanism, instantiates the class, and calls back META-INF/services/javax.servlet.ServletInitializer ContainerThe onStartup method of the implementation class of ServletContainerInitializer specified in the file.If the class has the @HandlesTypes annotation and the @HandlesTypes annotation specifies the class we are interested in, all onStartup methods that implement this class will be invoked.

To put it bluntly, when web.xml exists, the Servlet container initializes our jar package based on the configuration in web.xml (in other words, web.xml is the intermediary between our jar package and the Servlet contact).The implementation of the classes specified in the jar package META-INF/services/javax.servlet.ServletContainerInitializer is invoked when the Servlet3.0 container is initialized (the implementation in javax.servlet.ServletContainerInitializer replaces the role of web.xml, while the so-called classes of interest specified in the @HandlesTypes annotation can be understood as implementing the functionality of web.xml, or of course, as wellOther uses).

4. Analyzing how SpringBoot omits web.xml from Spring source code

1,META-INF/services/javax.servlet.ServletContainerInitializer

In the previous section, we introduced the technical basis for the birth of SpringBoot and the Servlet 3.0 specification.In this section, we analyze how Spring achieves the omission of web.xml through Spring source code.

As illustrated below, under the org.springframework:spring-web project, the META-INF/services/javax.servlet.ServletContainerInitializer file specifies the classes that will be callback when the Servlet container starts.

2,SpringServletContainerInitializer 

Looking at the source code for the SpringServletContainerInitializer class, we found that it does implement the ServletContainerInitializer as described above, and that it is also specified in the @HandlesTypes annotation for the class of interest WebApplicationInitializer

You can see a large comment on the onStartup method, which translates the general meaning:

When the servlet 3.0+ container starts, it automatically scans the class path to find all implementations of the web applicationinitializer interface that implements Spring, puts it into a Set collection, and supplies it with the first parameter of Spring ServletContainerInitializer onStartup (end of translation).

The onStartup method of SpringServletContainerInitializer is called when the Servlet container is initialized. Continue to look at the code logic of the onStartup method, which uses onStartup methods in all implementation classes that call webapplicationinitializer one by one.

 

 1 @HandlesTypes(WebApplicationInitializer.class) 2 public class SpringServletContainerInitializer implements ServletContainerInitializer { 3  4     /** 5      * Delegate the {@code ServletContext} to any {@link WebApplicationInitializer} 6      * implementations present on the application classpath. 7      * <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)}, 8      * Servlet 3.0+ containers will automatically scan the classpath for implementations 9      * of Spring's {@code WebApplicationInitializer} interface and provide the set of all10      * such types to the {@code webAppInitializerClasses} parameter of this method.11      * <p>If no {@code WebApplicationInitializer} implementations are found on the classpath,12      * this method is effectively a no-op. An INFO-level log message will be issued notifying13      * the user that the {@code ServletContainerInitializer} has indeed been invoked but that14      * no {@code WebApplicationInitializer} implementations were found.15      * <p>Assuming that one or more {@code WebApplicationInitializer} types are detected,16      * they will be instantiated (and <em>sorted</em> if the @{@link17      * org.springframework.core.annotation.Order @Order} annotation is present or18      * the {@link org.springframework.core.Ordered Ordered} interface has been19      * implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)}20      * method will be invoked on each instance, delegating the {@code ServletContext} such21      * that each instance may register and configure servlets such as Spring's22      * {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener},23      * or any other Servlet API componentry such as filters.24      * @param webAppInitializerClasses all implementations of25      * {@link WebApplicationInitializer} found on the application classpath26      * @param servletContext the servlet context to be initialized27      * @see WebApplicationInitializer#onStartup(ServletContext)28      * @see AnnotationAwareOrderComparator29      */30     @Override31     public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)32             throws ServletException {33 34         List<WebApplicationInitializer> initializers = new LinkedList<>();35 36         if (webAppInitializerClasses != null) {37             for (Class<?> waiClass : webAppInitializerClasses) {38                 // Be defensive: Some servlet containers provide us with invalid classes,39                 // no matter what @HandlesTypes says...40                 if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&41                         WebApplicationInitializer.class.isAssignableFrom(waiClass)) {42                     try {43                         initializers.add((WebApplicationInitializer)44                                 ReflectionUtils.accessibleConstructor(waiClass).newInstance());45                     }46                     catch (Throwable ex) {47                         throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);48                     }49                 }50             }51         }52 53         if (initializers.isEmpty()) {54             servletContext.log("No Spring WebApplicationInitializer types detected on classpath");55             return;56         }57 58         servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");59         AnnotationAwareOrderComparator.sort(initializers);60         for (WebApplicationInitializer initializer : initializers) {61             initializer.onStartup(servletContext);62         }63     }64 65 }

 3,WebApplicationInitializer 

Look at the WebApplicationInitializer interface, which is the class of interest specified in the @HandlesTypes(WebApplicationInitializer.class) annotation in the Servlet 3.0 specification mentioned above.

Intercept a very important comment.This comment tells us that the main function that a class implementing this interface needs to implement is what is configured in the configuration file in web.xml.

 1 /* 2  * <servlet> 3  *   <servlet-name>dispatcher</servlet-name> 4  *   <servlet-class> 5  *     org.springframework.web.servlet.DispatcherServlet 6  *   </servlet-class> 7  *   <init-param> 8  *     <param-name>contextConfigLocation</param-name> 9  *     <param-value>/WEB-INF/spring/dispatcher-config.xml</param-value>10  *   </init-param>11  *   <load-on-startup>1</load-on-startup>12  * </servlet>13  *14  * <servlet-mapping>15  *   <servlet-name>dispatcher</servlet-name>16  *   <url-pattern>/</url-pattern>17  * </servlet-mapping>}</pre>18  *19  */20 public interface WebApplicationInitializer {21     void onStartup(ServletContext servletContext) throws ServletException;22 }

4. Implementation of Web Application Initializer for SpringBoot

View the SpringBoot SpringBoot ServletInitializer source code, which is in the spring-boot dependency package.

Take a closer look at the blue code below.It's not difficult to find out how the Servlet Container (tomcat) finds SpringBoot and starts it.

  1 package org.springframework.boot.web.support;  2   3 import javax.servlet.Filter;  4 import javax.servlet.Servlet;  5 import javax.servlet.ServletContext;  6 import javax.servlet.ServletContextEvent;  7 import javax.servlet.ServletException;  8   9 import org.apache.commons.logging.Log; 10 import org.apache.commons.logging.LogFactory; 11  12 import org.springframework.boot.SpringApplication; 13 import org.springframework.boot.builder.ParentContextApplicationContextInitializer; 14 import org.springframework.boot.builder.SpringApplicationBuilder; 15 import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; 16 import org.springframework.boot.web.servlet.ServletContextInitializer; 17 import org.springframework.context.ApplicationContext; 18 import org.springframework.context.annotation.Configuration; 19 import org.springframework.core.annotation.AnnotationUtils; 20 import org.springframework.util.Assert; 21 import org.springframework.web.WebApplicationInitializer; 22 import org.springframework.web.context.ContextLoaderListener; 23 import org.springframework.web.context.WebApplicationContext; 24 import org.springframework.web.context.support.StandardServletEnvironment; 25  26 /** 27  * An opinionated {@link WebApplicationInitializer} to run a {@link SpringApplication} 28  * from a traditional WAR deployment. Binds {@link Servlet}, {@link Filter} and 29  * {@link ServletContextInitializer} beans from the application context to the servlet 30  * container. 31  * <p> 32  * To configure the application either override the 33  * {@link #configure(SpringApplicationBuilder)} method (calling 34  * {@link SpringApplicationBuilder#sources(Object...)}) or make the initializer itself a 35  * {@code @Configuration}. If you are using {@link SpringBootServletInitializer} in 36  * combination with other {@link WebApplicationInitializer WebApplicationInitializers} you 37  * might also want to add an {@code @Ordered} annotation to configure a specific startup 38  * order. 39  * <p> 40  * Note that a WebApplicationInitializer is only needed if you are building a war file and 41  * deploying it. If you prefer to run an embedded container then you won't need this at 42  * all. 43  * 44  * @author Dave Syer 45  * @author Phillip Webb 46  * @author Andy Wilkinson 47  * @since 1.4.0 48  * @see #configure(SpringApplicationBuilder) 49  */ 50 public abstract class SpringBootServletInitializer implements WebApplicationInitializer { 51  52     protected Log logger; // Don't initialize early 53  54     private boolean registerErrorPageFilter = true; 55  56     /** 57      * Set if the {@link ErrorPageFilter} should be registered. Set to {@code false} if 58      * error page mappings should be handled via the Servlet container and not Spring 59      * Boot. 60      * @param registerErrorPageFilter if the {@link ErrorPageFilter} should be registered. 61      */ 62     protected final void setRegisterErrorPageFilter(boolean registerErrorPageFilter) { 63         this.registerErrorPageFilter = registerErrorPageFilter; 64     } 65  66     @Override 67     public void onStartup(ServletContext servletContext) throws ServletException { 68         // Logger initialization is deferred in case a ordered 69         // LogServletContextInitializer is being used 70         this.logger = LogFactory.getLog(getClass()); 71         WebApplicationContext rootAppContext = createRootApplicationContext( 72                 servletContext); 73         if (rootAppContext != null) { 74             servletContext.addListener(new ContextLoaderListener(rootAppContext) { 75                 @Override 76                 public void contextInitialized(ServletContextEvent event) { 77                     // no-op because the application context is already initialized 78                 } 79             }); 80         } 81         else { 82             this.logger.debug("No ContextLoaderListener registered, as " 83                     + "createRootApplicationContext() did not " 84                     + "return an application context"); 85         } 86     } 87  88     protected WebApplicationContext createRootApplicationContext( 89             ServletContext servletContext) { 90         SpringApplicationBuilder builder = createSpringApplicationBuilder(); 91         StandardServletEnvironment environment = new StandardServletEnvironment(); 92         environment.initPropertySources(servletContext, null); 93         builder.environment(environment); 94         builder.main(getClass()); 95         ApplicationContext parent = getExistingRootWebApplicationContext(servletContext); 96         if (parent != null) { 97             this.logger.info("Root context already created (using as parent)."); 98             servletContext.setAttribute( 99                     WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);100             builder.initializers(new ParentContextApplicationContextInitializer(parent));101         }102         builder.initializers(103                 new ServletContextApplicationContextInitializer(servletContext));104         builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);105         builder = configure(builder);106         SpringApplication application = builder.build();107         if (application.getSources().isEmpty() && AnnotationUtils108                 .findAnnotation(getClass(), Configuration.class) != null) {109             application.getSources().add(getClass());110         }111         Assert.state(!application.getSources().isEmpty(),112                 "No SpringApplication sources have been defined. Either override the "113                         + "configure method or add an @Configuration annotation");114         // Ensure error pages are registered115         if (this.registerErrorPageFilter) {116             application.getSources().add(ErrorPageFilterConfiguration.class);117         }118         return run(application);119     }120 121     /**122      * Returns the {@code SpringApplicationBuilder} that is used to configure and create123      * the {@link SpringApplication}. The default implementation returns a new124      * {@code SpringApplicationBuilder} in its default state.125      * @return the {@code SpringApplicationBuilder}.126      * @since 1.3.0127      */128     protected SpringApplicationBuilder createSpringApplicationBuilder() {129         return new SpringApplicationBuilder();130     }131 132     /**133      * Called to run a fully configured {@link SpringApplication}.134      * @param application the application to run135      * @return the {@link WebApplicationContext}136      */137     protected WebApplicationContext run(SpringApplication application) {138         return (WebApplicationContext) application.run();139     }140 141     private ApplicationContext getExistingRootWebApplicationContext(142             ServletContext servletContext) {143         Object context = servletContext.getAttribute(144                 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);145         if (context instanceof ApplicationContext) {146             return (ApplicationContext) context;147         }148         return null;149     }150 151     /**152      * Configure the application. Normally all you would need to do is to add sources153      * (e.g. config classes) because other settings have sensible defaults. You might154      * choose (for instance) to add default command line arguments, or set an active155      * Spring profile.156      * @param builder a builder for the application context157      * @return the application builder158      * @see SpringApplicationBuilder159      */160     protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {161         return builder;162     }163 164 }

 

5. View Spring's official documents

View Spring 5.0.14 official documents: https://docs.spring.io/spring/docs/5.0.14.RELEASE/spring-framework-reference/web.html#spring-web

Configuration content in web.xml in traditional springMVC is given in the document

 

 1 <web-app> 2     <!-- Initialization Spring context --> 3     <listener> 4         <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 5     </listener> 6     <!-- Appoint Spring Profile --> 7     <context-param> 8         <param-name>contextConfigLocation</param-name> 9         <param-value>/WEB-INF/app-context.xml</param-value>10     </context-param>11     <!-- Initialization DispatcherServlet -->12     <servlet>13         <servlet-name>app</servlet-name>14         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>15         <init-param>16             <param-name>contextConfigLocation</param-name>17             <param-value></param-value>18         </init-param>19         <load-on-startup>1</load-on-startup>20     </servlet>21     <servlet-mapping>22         <servlet-name>app</servlet-name>23         <url-pattern>/app/*</url-pattern>24     </servlet-mapping>25 </web-app>

 

The documentation provides a way to configure the Servlet container example using java-based code

 1 public class MyWebApplicationInitializer implements WebApplicationInitializer { 2  3     @Override 4     public void onStartup(ServletContext servletCxt) { 5  6         //Load Spring web application configuration 7 //Initialize Spring's context by annotation 8 //AnnotationConfigWebApplicationContext AC = new AnnotationConfigWebApplicationContext(); 9 //Register Spring's configuration class (instead of xml configuration in traditional projects) 10 //ac.register(AppConfig.class);11  ac.refresh();12  13  ac// Create and register the Dispatcher Servlet14 // java code-based initialization of Dispatcher Servlet15  Dispatcher Servlet servlet = new Dispatcher Servlet (ac); 16  Servlet Registration.Dynamic registration = servletCxt.addServlet("app", servlet);17 * registration.setLoadOnStartup(1);18 * registration.addMapping("/app/*");19}}20}

 

Comparing the example s given in the official documentation, it is not difficult to see that this java code above is a specific implementation of SpringBoot that omits web.xml.The MyWebApplicationInitializer above is the implementation of the WebApplicationInitializer (@HandlesTypes(WebApplicationInitializer.class)) interface.

The MyWebApplicationInitializer class provided in the official documentation is the key code for SpringBoot to be independent of web.xml.

The code that implements the web.xml configuration in SpringBoot is not as simple as the example in the official document. The class that initializes Dispatcher Servlet in SpringBoot is Dispatcher Servlet AutoConfiguration.You can debug breakpoints if you are interested.

 

V. Summary

The above chapters describe the historical background of SpringBoot's birth, and each new technology is scene driven.The technical conditions for SpringBoot to be independent of web.xml are then described.Finally, the specific implementation in SpringBoot is analyzed through the source code.

The next blog post will use the knowledge described in this article to simulate the basic functions of SpringBoot based on Spring framework's built-in tomcat.Simply put, implement a simple version of SpringBoot.


Topics: Programming Spring xml SpringBoot Java