我从网上找了一张web项目的目录结构,如下:

spring不写main函数怎么启动还有什么 spring blooms没有了_web.xml

是不是很熟悉的赶脚^-^ 

几年之前这是当之无愧的主流,但是随着技术的发展,servlet3.0、springboot的诞生,基于xml配置的web项目一去不复返了,取而代之的是springboot。今天不谈springboot是如何玩的,今天的主题是web.xml是如何消失的。因为当下主流的技术就是spring全家桶,探究的目的也是spring是如何让web.xml消失的。springmvc中的核心是DispacherServlet,在使用的时候会在web.xml中配置<servlet>标签,那么当web.xml消失了,DispacherServlet如何注册到容器中的呢?解决了这个问题也就能回答web.xml消失的原因了。

Servlet3.0

从servlet3.0开始,支持ServletContainerInitializer接口,也就是说提供了一种通过代码层面来初始化servlet容器的方式,比如注册servlet、listener、filter。官方文档中关于ServletContainerInitializer的介绍如下:

ServletContainerInitializer 类通过 jar services API 查找。对于每一个应用,应用启动时,由容器创建一个 ServletContainerInitializer 实例。框架提供的 ServletContainerInitializer 实现必须绑定在 jar 包的 META-INF/services 目录中的一个叫做 javax.servlet.ServletContainerInitializer 的文件,根据 jar services API, 指定 ServletContainerInitializer 的实现。 除 ServletContainerInitializer 外,我们还有一个注解—HandlesTypes。在 ServletContainerInitializer 实现上的 HandlesTypes 注解用于表示感兴趣的一些类,它们可能指定了 HandlesTypes 的 value 中的注解(类型、方 法或自动级别的注解),或者是其类型的超类继承/实现了这些类之一。

简单来说,只要在jar中的特定目录文件下配置ServletContainerInitializer的实现类,servlet容器在启动的过程中就会去调用这个实现类,从而可以达到注册servlet、listener、filter的功能,那么web.xml的用处完全可以被取代了。

spring-web包

对于使用spring很熟悉的朋友肯定知道,在开发web应用时spring-web包是必不可少的,来看看这个包的目录结构

spring不写main函数怎么启动还有什么 spring blooms没有了_xml_02

是不是很惊喜?针对servlet3.0,spring对其ServletContainerInitializer的特性做了支持,看到这里,心里有底了,spring是支持通过这种方式来注册servlet的。下面我们来看看SpringServletContainerInitializer是怎么实现的。上源码

//1
@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 {
                        //2
						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) {
            //3
			initializer.onStartup(servletContext);    
		}
	}

}

//1 通过@HandlesTypes注解获取感兴趣的类(WebApplicationInitializer及其子类),作为集合webAppInitializerClasses

传入到onStartup方法中

//2 筛选出webAppInitializerClasses集合中的实现类

//3 遍历WebApplicationInitializer的实现类,调用onStartup方法

WebApplicationInitializer的实现类

spring不写main函数怎么启动还有什么 spring blooms没有了_servlet容器_03

如图所示,红框中的抽象类具体帮助实现了DispatcherServlet的注册。除此之外还看到了一个熟悉的类,有没有?对,就是SpringBootServletInitializer,熟悉吧,没有有一种万物归宗的赶脚。Springboot如果需要使用外部的web容器启动,需要在之前的基础上改造一下Application,其中之一便是继承SpringBootServletInitializer,这里不展开讲,有兴趣的可以自己去研究下,后续我也会springboot相关的文章,想知道细节的朋友可以关注我^-^

好了,来看看这三个抽象类

我们关注的是onStartup方法,AbstractDispatcherServletInitializer中诠释了onStartup的实现

spring不写main函数怎么启动还有什么 spring blooms没有了_xml_04

首先调用父类AbstractContextLoaderInitializer的onStartup方法,然后注册DispatcherServlet

AbstractContextLoaderInitializer中的onStartup方法

spring不写main函数怎么启动还有什么 spring blooms没有了_spring_05

第一步,创建根容器(父容器),在AbstractDispatcherServletInitializer中实现的

spring不写main函数怎么启动还有什么 spring blooms没有了_spring_06

创建AnnotationConfigWebApplicationContext,注册@Configuration注释的类,一般包括service、dao(repository)、bean

2、创建ContextLoaderListener,并注册到servlet容器中。当servlet容器启动后,refresh根容器。

注册DispatcherServlet

spring不写main函数怎么启动还有什么 spring blooms没有了_web.xml_07

第一步,createServletApplicationContext(),也是在AbstractAnnotationConfigDispatcherServletInitializer中实现的,它实际上是一个AnnotationConfigWebApplicationContext,作为子容器和DispatcherServlet绑定在一起

spring不写main函数怎么启动还有什么 spring blooms没有了_xml_08

初始化一个AnnotationConfigWebApplicationContext实例,注册@Configuration注释的类,一般是controller,子容器中还会存放springmvc相关的bean,比如ViewRover、Interceptor。

第二步,创建DispatcherServlet,将子容器作为属性绑定到DispatcherServlet中

第三步,将DispatcherServlet添加到Servlet容器中,并且设置servlet-mapping路径,loadOnstartup顺序,所以当servlet容器启动后便会调用DispatcherServlet的init方法,当中主要调用了initServletBean方法,具体实现在FrameworkServlet,上代码

spring不写main函数怎么启动还有什么 spring blooms没有了_spring_09


看initWebApplicationContext方法


spring不写main函数怎么启动还有什么 spring blooms没有了_web.xml_10

先获取根容器,然后把根容器设置为字容器的父亲(这里也印证了父容器、字容器说法的来源),最后refresh字容器(熟悉spring ioc的朋友应该很清楚refresh对于spirng容器的重要性)

到这里,WebApplicationInitializer之下的三个继承类已经介绍完毕,是不是恍然大悟,原来spring是这么玩的。其实还没有完。还记得最开始的时候我说SpringServletContainerInitializer在执行onStartup方法时会传入感兴趣的WebApplicationInitailizer类及其子类,然后剔除掉接口和抽象类,刚刚我们介绍的全是抽象类,是不是有种想打我的冲动?稍安勿躁。我以前也很困惑,为啥没有提供实现类呢?由于现在做的是springboot项目,它把这个问题隐藏了,实际上sprigboot压根就不走这条路来注册DispatcherServlet到servlet容器中。这里不提springboot是怎么玩的,如果没有用springboot,又想走这条路来注册servlet该怎么办呢?很简单,没有实现类,创建一个不就好了,是不是很简单,我困惑了好久(555)。上代码

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class AnnotationConfigDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{ServletConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/*"};
    }
}

@Configuration
@ComponentScan(excludeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class})})
class RootConfig {

}

@Configuration
@ComponentScan(useDefaultFilters = false, includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class})})
class ServletConfig

主要的逻辑抽象类已经帮忙实现了,我只需要实现三个方法即可,getRootConfigClasses帮助根容器的创建,getServletConfigClasses帮助子容器的创建,getServletMappings帮助配置DispatcherServlet的映射路径。