Spring MVC项目启动过程

注:如无特别说明,本系列文章使用的Spring框架版本均为5.0.4.RELEASE

在以前的web项目中,通常会有一个web.xml文件,部署在WEB-INF目录下,诸如Tomcat之类的Servlet容器会通过读取web.xml文件来启动项目,从而初始化配置在web.xml中的一些类,如Spring MVC中的DispatcherServlet类。但是,现在的项目往往没有这个web.xml文件,那么Tomcat是如何知道要去初始化Spring MVC中的相关类呢?
首先,我们来看一个JavaEE中的ServletContainerInitializer接口.

javax.servlet
Interface ServletContainerInitializer

Interface which allows a library/runtime to be notified of a web application’s startup phase and perform any required programmatic registration of servlets, filters, and listeners in response to it.

Implementations of this interface may be annotated with HandlesTypes, in order to receive (at their onStartup(java.util.Set>, javax.servlet.ServletContext) method) the Set of application classes that implement, extend, or have been annotated with the class types specified by the annotation.

If an implementation of this interface does not use this annotation, or none of the application classes match the ones specified by the annotation, the container must pass a null Set of classes to onStartup(java.util.Set>, javax.servlet.ServletContext).

When examining the classes of an application to see if they match any of the criteria specified by the HandlesTypes annotation of a ServletContainerInitializer, the container may run into classloading problems if any of the application’s optional JAR files are missing. Because the container is not in a position to decide whether these types of classloading failures will prevent the application from working correctly, it must ignore them, while at the same time providing a configuration option that would log them.

Implementations of this interface must be declared by a JAR file resource located inside the META-INF/services directory and named for the fully qualified class name of this interface, and will be discovered using the runtime’s service provider lookup mechanism or a container specific mechanism that is semantically equivalent to it. In either case, ServletContainerInitializer services from web fragment JAR files excluded from an absolute ordering must be ignored, and the order in which these services are discovered must follow the application’s classloading delegation model.

上面这段文字的大意就是说,ServletContainerInitializer接口用来给web应用在启动时动态注册servlets,filters和listener。这个接口的实现必须要在被jar包声明,这个声明文件要放在META-INF/services目录下,并且这个文件的名称要叫做javax.servlet.ServletContainerInitializer,其中这个文件内容写着这个接口的实现类的限定名,如Spring MVC中这个接口的实现类就是org.springframework.web.SpringServletContainerInitializer

接下来我们再去看一下这个SpringServletContainerInitializer类。(注:为使下类简洁,已去掉部分注释)

package org.springframework.web;

import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;

import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;


@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) {
                // 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);
        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);
        }
    }

}

SpringServletContainerInitializer类实现了ServletContainerInitializer接口,在web启动时,容器将会执行onStartup方法,并且将实现了WebApplicationInitializer接口的类(由HandlesTypes注解指定)通过webAppInitializerClasses参数传递给onStartup方法。就这样,onStartup方法的执行带动了整个Spring MVC。

参考文献:

https://docs.oracle/javaee/6/api/javax/servlet/ServletContainerInitializer.html

https://stackoverflow/questions/32550131/how-does-tomcat-exactly-bootstrap-the-app-without-web-xml

更多推荐

Spring MVC项目的启动