The Tomcat container
Spring incorporates the web. XML that Tomcat often sees
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
Copy the code
Servlet 3.0 provides an annotation +SPI mechanism to configure servlets.
ServletContainerInitializer
Allows the library/runtime to be notified of the startup phase of the Web application and perform any required programmatic registration of servlets, filters, and listeners in response to its interface.
Implementations of this interface can be annotated with HandlesTypes to receive (in their onStartup methods) an implementation, extension, or set of application classes that have been annotated with the class type specified by the annotation. If the implementation of this interface does not use the HandlesTypes annotation, or if no application class matches the class specified by the annotation, the container must pass the empty class set to onStartup. In checking application class to see if they are matching with ServletContainerInitializer HandlesTypes annotation to specify any conditions, if the lack of any application optional JAR file, the container may encounter class loading problems. Because the container cannot determine whether these types of classloading failures will prevent the application from working properly, it must ignore them and provide a configuration option to log them. The implementation of this interface must be declared by a JAR file resource in the META-INF/services directory, named by the fully qualified class name of this interface, and found semantically equivalent to it using the runtime service provider lookup mechanism or container specific mechanism. In any case, must be ignored from the sorting of absolute rule out Web fragments ServletContainerInitializer services in the JAR file, and found that these services must follow the order of the application of the class loader delegation model.
ServletContainerInitializer was new to the Servlet 3.0 an interface, it is mainly used for registration by programming style in container startup phase Filter, the Servlet and the Listener, instead of through the web. The XML configuration register. When Tomcat is started, it will use the JAR API to discover classes that implement such interfaces for configuration loading
WebApplicationInitializer and SpringServletContainerInitializer
- org.springframework.web.SpringServletContainerInitializer#onStartup
@Override
public void onStartup(@NullableSet<Class<? >> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if(webAppInitializerClasses ! =null) {
for(Class<? > waiClass : webAppInitializerClasses) {// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
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);
/ / activate WebApplicationInitializer# onStartup
for(WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); }}Copy the code
SpringServletContainerInitializer ServletContainerInitializer is achieved, in the onStartup method, Through @ HandlesTypes (WebApplicationInitializer. Class) injection WebApplicationInitializer to Tomcat container. Activate WebApplicationInitializer# onStartup. So to achieve the WebApplicationInitializer classes will be loaded.
Members of the family of WebApplicationInitializer
Servlet WebApplicationContext and Root WebApplicationContext
- org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer#createServletApplicationCon text
protected WebApplicationContext createServletApplicationContext(a) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
// Get the configuration class and inject it into the containerClass<? >[] configClasses = getServletConfigClasses();if(! ObjectUtils.isEmpty(configClasses)) { context.register(configClasses); }return context;
}
Copy the code
- org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer#createRootApplicationContex t
protected WebApplicationContext createRootApplicationContext(a) { Class<? >[] configClasses = getRootConfigClasses();if(! ObjectUtils.isEmpty(configClasses)) { AnnotationConfigWebApplicationContext context =new AnnotationConfigWebApplicationContext();
context.register(configClasses);
return context;
}
else {
return null; }}Copy the code
Servlet WebApplicationContext: Scans Controller, ViewResolver, and HandleMapping related classes into the Servlet web container context. Root WebApplicationContext: Service, Repositories. Such objects are managed by Root WebApplicationContext.
In general, it is the layering of business objects.
The general flow of Spring MVC
-
The process of establishing a mapping collection of RequestMapping and Controller methods
-
The process of finding the corresponding Controller method based on the RequestURI
-
The request parameters are bound to the method parameters that execute the method to process the request and render the view
The flow chart
- The request first passes through the browser to the Tomcat container, which delegates to the thread pool to invoke servlets in response to the current request.
Spring MVC
throughDispatcherServlet
All requests are intercepted and resolved in advance with IoC through the path of the current requestHanlderMapping
The method of the Controller is then positioned to respond.- Method returns a response
ModelAndView
Instance.DispatcherServlet
Will be delegated toViewResolver
Perform view resolution.- return
View
Put the response in the response stream to the browser.
Annotation configuration – AbstractDispatcherServletInitializer container entry
Configuration of annotations takes precedence over configuration execution of XML. SpringServletContainerInitializer ServletContainerInitializer interface is achieved, By @ HandlesTypes (WebApplicationInitializer. Class) of SPI discovery mechanism, realize the interface class object can be passed to the onStartup.
- org.springframework.web.SpringServletContainerInitializer#onStartup
@Override
public void onStartup(@NullableSet<Class<? >> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if(webAppInitializerClasses ! =null) {
for(Class<? > waiClass : webAppInitializerClasses) {// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
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);
/ / activate WebApplicationInitializer# onStartup
for(WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); }}Copy the code
In these initializers are sorted, the last will, in turn, activate all WebApplicationInitializer onStartup method.
- org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#onStartup
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
Copy the code
- org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#registerDispatcherServlet
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
Create a Spring Web IoC container
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
// Create a dispatcherServlet instance
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'." +
"Check if there is another servlet registered under the same name.");
}
registration.setLoadOnStartup(1);
// Path mapping
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
// Request interceptor, which can control the encoding here
Filter[] filters = getServletFilters();
if(! ObjectUtils.isEmpty(filters)) {for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
Copy the code
This is a template method, in which the createServletApplicationContext can go to realize by subclasses. Support in the form of annotation is AbstractAnnotationConfigDispatcherServletInitializer implementation class
Container entry for XML configuration -ContextLoaderListener
ContextLoaderListener implements the ServletContextListener Servlet Tomcat at startup, will first load the Servlet listener components, to ensure that before the Servlet is created, Call the listener contextInitialized method, the Spring is in the ContextLoaderListener initWebApplicationContext calls, namely startup, to refresh operation of the vessel.
- org.springframework.web.context.ContextLoaderListener#contextInitialized
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
Copy the code
- org.springframework.web.context.ContextLoader#initWebApplicationContext
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// Check whether the WebApplicationContext ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE is the key
if(servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) ! =null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
/ / in AbstractContextLoaderInitializer# onStartup
// ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
// Initialization is performed
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if(! cwac.isActive()) {// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// Configure and refresh the containerconfigureAndRefreshWebApplicationContext(cwac, servletContext); }}// Put WebApplicationContext into servletContext
// Its key is ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if(ccl ! =null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throwex; }}Copy the code
- First check to see if the container is created, and if not, call
createWebApplicationContext
To create the container.- Whether the container belongs to
ConfigurableWebApplicationContext
Type, and then see if the container is in the active state (if refresh is passed, the container is in the active state).- Whether there is a parent container, if any
setParent
- Configure and refresh the container
Refresh again
Spring establishes two contexts in turn, the Root WebApplicationContext. The other is WebApplicationContext For Dispatcher-Servlet. No matter what, will eventually return to org. Springframework. Context. Support. AbstractApplicationContext# refresh this method.
-
The first refresh is the Root WebApplicationContext
- The second refresh is the Servlet WebApplicationContext
A few key objects appear here: FrameworkerServlet, HttpServletBean. Take a look at the call process from the call stack executed:
- org.springframework.web.servlet.HttpServletBean#init
- org.springframework.web.servlet.FrameworkServlet#initServletBean
- org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext
- org.springframework.web.servlet.FrameworkServlet#configureAndRefreshWebApplicationContext
We then use UML to tease out the relationships between these classes:
As you can see, the DispatcherServlet inherits from the FrameworkServlet, which in turn inherits from the HttpServletBean, which inherits from the HttpServlet. At the same time, realize Aware interface, can obtain environment variables, container context and other resources from the container. From this point of view, the order of calls is easy to understand.
An HttpServletBean that implements the HttpServlet interface can override its init method by calling a hook method called initServletBean(HttpServletBean is not responsible for implementation, FrameworkServlet overrides the initServletBean method to start initializing the container’s business. Note that both FrameworkSerlvet and HttpSerlvetBean mentioned here are abstract classes. The real instance is -DispatcherSerlvet.
In the next chapter, we’ll look at how DispatcherSerlvet initializes other MVC components.