欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SpringBoot内置tomcat启动原理、以及SpringBoot初始化Servlet的源码分析

程序员文章站 2022-07-14 10:49:42
...

SpringBoot内置tomcat启动原理

前言

我们知道SpringBoot工程是可以被打成jar包,直接运行jar包启动的,那么为什么不用部署到web服务器也能够像web一样访问呢?其本质原因就是springBoot工程内嵌了一个tomcat,jar包方式以main方法作为入口执行代码的时候,底层启动了一个tomcat。

内置tomcat依赖

如果我们用springBoot开发web工程,通常都会导入如下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

点击进去就能看到SpringBoot tomcat的启动依赖:
SpringBoot内置tomcat启动原理、以及SpringBoot初始化Servlet的源码分析

开发阶段对我们来说使用内置的tomcat是非常够用了,当然也可以使用jetty。

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

这里是main函数入口,两句代码最耀眼,分别是SpringBootApplication注解和SpringApplication.run()方法。

war包方式发布

发布的时候,目前大多数的做法还是排除内置的tomcat,打瓦包(war)然后部署在生产的tomcat中,好吧,那打包的时候应该怎么处理?

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <!-- 移除嵌入式tomcat依赖 -->
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--添加servlet-api依赖--->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <scope>provided</scope>
</dependency>

更新main函数,主要是继承SpringBootServletInitializer,并重写configure()方法。

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(this.getClass());
    }
}

关于为什么war包方式发布需要继承SpringBootServletInitializer,并重写configure方法:

  • 1.启动入口不一样
    首先jar包方式启动的入口是main方法,底层会启动一个内嵌的tomcat
    而war包方式发布入口就是由外部的tomcat管理了,其会加载web.xml
  • 2.至与为什么要继承SpringBootServletInitializer,根本原因是servlet3.0 的新特性,不再需要web.xml配置文件了,这里就不具体说明了,推荐一篇博文:SpringBoot为什么没有web.xml了

从main函数说起

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
	return run(new Class<?>[] { primarySource }, args);
}

//这里run方法返回的是ConfigurableApplicationContext
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	//这里对比war包方式继承SpringBootServletInitializer重写configure方法
	//中的builder.sources(this.getClass())
	//其实目的是一样的,让SpringApplication知道配置信息
	return new SpringApplication(primarySources).run(args);
}

主要看run方法:

public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	configureHeadlessProperty();
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting();
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		//准备环境,这里会用监听器方式加载各种配置文件,例如application.yml,properties...
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
		configureIgnoreBeanInfo(environment);
		//打印banner,这里你可以自己涂鸦一下,换成自己项目的logo
		Banner printedBanner = printBanner(environment);
		//创建应用上下文
		context = createApplicationContext();
		exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
				new Class[] { ConfigurableApplicationContext.class }, context);
		//预处理上下文
		prepareContext(context, environment, listeners, applicationArguments, printedBanner);
		//刷新上下文
		refreshContext(context);
		//再刷新上下文
		afterRefresh(context, applicationArguments);
		stopWatch.stop();
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
		}
		listeners.started(context);
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}

	try {
		listeners.running(context);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	return context;
}

既然我们想知道tomcat在SpringBoot中是怎么启动的,那么run方法中,重点关注创建应用上下文(createApplicationContext)和刷新上下文(refreshContext)。

创建上下文

//创建上下文
protected ConfigurableApplicationContext createApplicationContext() {
	Class<?> contextClass = this.applicationContextClass;
	if (contextClass == null) {
		try {
			switch (this.webApplicationType) {
			case SERVLET:
				//内嵌tomcat走这
				//创建AnnotationConfigServletWebServerApplicationContext
				contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
				break;
			case REACTIVE:
				//Spring Boot的特性: Spring WebFlux相关的不用管
				contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
				break;
			default:
				//不需要内嵌web服务器走这
				contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
			}
		}
		catch (ClassNotFoundException ex) {
			throw new IllegalStateException(
					"Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",
					ex);
		}
	}
	return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

这里会创建AnnotationConfigServletWebServerApplicationContext类。
而AnnotationConfigServletWebServerApplicationContext类继承了ServletWebServerApplicationContext,而这个类是最终集成了AbstractApplicationContext。

刷新上下文

//SpringApplication.java
//刷新上下文
private void refreshContext(ConfigurableApplicationContext context) {
	refresh(context);
	if (this.registerShutdownHook) {
		try {
			context.registerShutdownHook();
		}
		catch (AccessControlException ex) {
			// Not allowed in some environments.
		}
	}
}

//这里直接调用最终父类AbstractApplicationContext.refresh()方法
protected void refresh(ApplicationContext applicationContext) {
	Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
	((AbstractApplicationContext) applicationContext).refresh();
}
//AbstractApplicationContext.java
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		prepareRefresh();
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
		prepareBeanFactory(beanFactory);
		try {
			postProcessBeanFactory(beanFactory);
			invokeBeanFactoryPostProcessors(beanFactory);
			registerBeanPostProcessors(beanFactory);
			initMessageSource();
			initApplicationEventMulticaster();
			//调用各个子类的onRefresh()方法,也就说这里要回到子类:ServletWebServerApplicationContext,调用该类的onRefresh()方法
			onRefresh();
			registerListeners();
			finishBeanFactoryInitialization(beanFactory);
			finishRefresh();
		}catch (BeansException ex) {
			...
			throw ex;
		}finally {
			resetCommonCaches();
		}
	}
}
//ServletWebServerApplicationContext.java
//在这个方法里看到了熟悉的面孔,this.createWebServer,神秘的面纱就要揭开了。
protected void onRefresh() {
	super.onRefresh();
	try {
		createWebServer();
	}
	catch (Throwable ex) {
		throw new ApplicationContextException("Unable to start web server", ex);
	}
}

//ServletWebServerApplicationContext.java
//这里是创建webServer,但是还没有启动tomcat,这里是通过ServletWebServerFactory创建,那么接着看下ServletWebServerFactory
private void createWebServer() {
	WebServer webServer = this.webServer;
	ServletContext servletContext = getServletContext();
	if (webServer == null && servletContext == null) {
		ServletWebServerFactory factory = getWebServerFactory();
		this.webServer = factory.getWebServer(getSelfInitializer());
		getBeanFactory().registerSingleton("webServerGracefulShutdown",
				new WebServerGracefulShutdownLifecycle(this.webServer));
		getBeanFactory().registerSingleton("webServerStartStop",
				new WebServerStartStopLifecycle(this, this.webServer));
	}
	else if (servletContext != null) {
		try {
			getSelfInitializer().onStartup(servletContext);
		}
		catch (ServletException ex) {
			throw new ApplicationContextException("Cannot initialize servlet context", ex);
		}
	}
	initPropertySources();
}

//接口
public interface ServletWebServerFactory {
    WebServer getWebServer(ServletContextInitializer... initializers);
}

//实现
AbstractServletWebServerFactory
JettyServletWebServerFactory
TomcatServletWebServerFactory
UndertowServletWebServerFactory

SpringBoot内置tomcat启动原理、以及SpringBoot初始化Servlet的源码分析

而其中我们常用的有两个:TomcatServletWebServerFactory和JettyServletWebServerFactory。

//TomcatServletWebServerFactory.java
//这里我们使用的tomcat,所以我们查看TomcatServletWebServerFactory。到这里总算是看到了tomcat的踪迹。
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
	if (this.disableMBeanRegistry) {
		Registry.disableRegistry();
	}
	Tomcat tomcat = new Tomcat();
	File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
	tomcat.setBaseDir(baseDir.getAbsolutePath());
	// 创建Connector对象
	Connector connector = new Connector(this.protocol);
	connector.setThrowOnFailure(true);
	tomcat.getService().addConnector(connector);
	customizeConnector(connector);
	tomcat.setConnector(connector);
	tomcat.getHost().setAutoDeploy(false);
	configureEngine(tomcat.getEngine());
	for (Connector additionalConnector : this.additionalTomcatConnectors) {
		tomcat.getService().addConnector(additionalConnector);
	}
	prepareContext(tomcat.getHost(), initializers);
	return getTomcatWebServer(tomcat);
}

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
	return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}

//Tomcat.java
//返回Engine容器,看到这里,如果熟悉tomcat源码的话,对engine不会感到陌生。
public Engine getEngine() {
    Service service = getServer().findServices()[0];
    if (service.getContainer() != null) {
        return service.getContainer();
    }
    Engine engine = new StandardEngine();
    engine.setName( "Tomcat" );
    engine.setDefaultHost(hostname);
    engine.setRealm(createDefaultRealm());
    service.setContainer(engine);
    return engine;
}
//Engine是*别容器,Host是Engine的子容器,Context是Host的子容器,Wrapper是Context的子容器

getWebServer这个方法创建了Tomcat对象,并且做了两件重要的事情:把Connector对象添加到tomcat中,configureEngine(tomcat.getEngine());

getWebServer方法返回的是TomcatWebServer。

//TomcatWebServer.java
//这里调用构造函数实例化TomcatWebServer
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
	Assert.notNull(tomcat, "Tomcat Server must not be null");
	this.tomcat = tomcat;
	this.autoStart = autoStart;
	initialize();
}

private void initialize() throws WebServerException {
    //在控制台会看到这句日志
	logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
	synchronized (this.monitor) {
		try {
			addInstanceIdToEngineName();

			Context context = findContext();
			context.addLifecycleListener((event) -> {
				if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
					removeServiceConnectors();
				}
			});

			//===启动tomcat服务===
			this.tomcat.start();

			rethrowDeferredStartupExceptions();

			try {
				ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
			}
			catch (NamingException ex) {
                
			}
            
            //开启阻塞非守护进程
			startDaemonAwaitThread();
		}
		catch (Exception ex) {
			stopSilently();
			destroySilently();
			throw new WebServerException("Unable to start embedded Tomcat", ex);
		}
	}
}
//Tomcat.java
public void start() throws LifecycleException {
	getServer();
	server.start();
}
public void stop() throws LifecycleException {
	getServer();
	server.stop();
}
// 一个Tomcat会有一个Server
public Server getServer() {
    if (server != null) {
        return server;
    }
    System.setProperty("catalina.useNaming", "false");
    server = new StandardServer();
    initBaseDir();
    // Set configuration source
    ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));
    server.setPort( -1 );
    Service service = new StandardService();
    service.setName("Tomcat");
    server.addService(service);
    return server;
}
//TomcatWebServer.java
//启动tomcat服务
@Override
public void start() throws WebServerException {
	synchronized (this.monitor) {
		if (this.started) {
			return;
		}
		try {
			addPreviouslyRemovedConnectors();
			Connector connector = this.tomcat.getConnector();
			if (connector != null && this.autoStart) {
				performDeferredLoadOnStartup();
			}
			checkThatConnectorsHaveStarted();
			this.started = true;
			//在控制台打印这句日志,如果在yml设置了上下文,这里会打印
			logger.info("Tomcat started on port(s): " + getPortsDescription(true) + " with context path '"
					+ getContextPath() + "'");
		}
		catch (ConnectorStartFailedException ex) {
			stopSilently();
			throw ex;
		}
		catch (Exception ex) {
			throw new WebServerException("Unable to start embedded Tomcat server", ex);
		}
		finally {
			Context context = findContext();
			ContextBindings.unbindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
		}
	}
}

//关闭tomcat服务
@Override
public void stop() throws WebServerException {
	synchronized (this.monitor) {
		boolean wasStarted = this.started;
		try {
			this.started = false;
			try {
				stopTomcat();
				this.tomcat.destroy();
			}
			catch (LifecycleException ex) {
				
			}
		}
		catch (Exception ex) {
			throw new WebServerException("Unable to stop embedded Tomcat", ex);
		}
		finally {
			if (wasStarted) {
				containerCounter.decrementAndGet();
			}
		}
	}
}

TomcatStarter 初始化servlet容器

内嵌tomcat的创建和启动已经看到了,那么它是怎么初始化servlet容器的呢? springboot 内嵌的 tomcat 并没有完全遵守 servlet3.0 规范,主要区别就是对ServletContainerInitializer这个接口的处理

  • 如果是外部tomcat,那么ServletContainerInitializer的实现类是通过SPI服务发现机制进行加载的
  • 而内置的tomcat,是专门new了一个TomcatStarter作为ServletContainerInitializer的实现类进行容器初始化的

ServletContainerInitializer 是 Servlet 3.0 新增的一个接口,WEB容器在启动时使用 JAR 服务 API(JAR Service API) 来发现 ServletContainerInitializer 的实现类,并且容器将 WEB-INF/lib 目录下 JAR 包中的类都交给该类的 onStartup() 方法处理,我们通常需要在该实现类上使用 @HandlesTypes 注解来指定希望被处理的类,过滤掉不希望给 onStartup() 处理的类。

比如spring容器以前是通过web.xml配置servlet监听器实现的,现在按照Servlet 3.0规范,只要专门实现ServletContainerInitializer接口,在其onStartup方法中通过代码将spring的servlet监听器加到ServletContext上下文中即可,当然Spring Web确实实现了ServletContainerInitializer这个接口,但并不是把之前的监听器换种方式加入到Servlet容器这么简单,而是委托给一个新的接口WebApplicationinitializer:
SpringBoot内置tomcat启动原理、以及SpringBoot初始化Servlet的源码分析
SpringBoot内置tomcat启动原理、以及SpringBoot初始化Servlet的源码分析
关于servlet3.0 规范,推荐一篇博文:SpringBoot为什么没有web.xml了

继续看,先找到TomcatStarter在哪加载的,入口就是之前获取TomcatWebServer的地方:

//TomcatServletWebServerFactory.java
public WebServer getWebServer(ServletContextInitializer... initializers) {
	Tomcat tomcat = new Tomcat();
	File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
	tomcat.setBaseDir(baseDir.getAbsolutePath());
	Connector connector = new Connector(this.protocol);
	tomcat.getService().addConnector(connector);
	customizeConnector(connector);
	tomcat.setConnector(connector);
	tomcat.getHost().setAutoDeploy(false);
	configureEngine(tomcat.getEngine());
	for (Connector additionalConnector : this.additionalTomcatConnectors) {
		tomcat.getService().addConnector(additionalConnector);
	}
	//为tomcat准备上下文,可以理解为用代码写web.xml,tomcat启动的时候会去加载这里设置的信息
	prepareContext(tomcat.getHost(), initializers);
	return getTomcatWebServer(tomcat);
}
//TomcatServletWebServerFactory.java
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
	File documentRoot = getValidDocumentRoot();
	//tomcat内嵌上下文
	TomcatEmbeddedContext context = new TomcatEmbeddedContext();
	if (documentRoot != null) {
		context.setResources(new LoaderHidingResourceRoot(context));
	}
	context.setName(getContextPath());
	context.setDisplayName(getDisplayName());
	context.setPath(getContextPath());
	File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
	context.setDocBase(docBase.getAbsolutePath());
	context.addLifecycleListener(new FixContextListener());
	context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
			: ClassUtils.getDefaultClassLoader());
	resetDefaultLocaleMapping(context);
	addLocaleMappings(context);
	context.setUseRelativeRedirects(false);
	try {
		context.setCreateUploadTargets(true);
	}
	catch (NoSuchMethodError ex) {
		// Tomcat is < 8.5.39. Continue.
	}
	configureTldSkipPatterns(context);
	WebappLoader loader = new WebappLoader(context.getParentClassLoader());
	loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
	loader.setDelegate(true);
	context.setLoader(loader);
	if (isRegisterDefaultServlet()) {
		addDefaultServlet(context);
	}
	if (shouldRegisterJspServlet()) {
		addJspServlet(context);
		addJasperInitializer(context);
	}
	context.addLifecycleListener(new StaticResourceConfigurer(context));
	ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
	host.addChild(context);
	//设置上下文
	configureContext(context, initializersToUse);
	postProcessContext(context);
}
//TomcatServletWebServerFactory.java
protected void configureContext(Context context, ServletContextInitializer[] initializers) {
	//这个TomcatStarter 实现的就是 servlet3.0 规范的ServletContainerInitializer接口
	TomcatStarter starter = new TomcatStarter(initializers);
	if (context instanceof TomcatEmbeddedContext) {
		//如果context是内嵌的tomcat上下文
		TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
		embeddedContext.setStarter(starter);
		embeddedContext.setFailCtxIfServletStartFails(true);
	}
	//给context添加ServletContainerInitializer!
	context.addServletContainerInitializer(starter, NO_CLASSES);
	for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) {
		context.addLifecycleListener(lifecycleListener);
	}
	for (Valve valve : this.contextValves) {
		context.getPipeline().addValve(valve);
	}
	for (ErrorPage errorPage : getErrorPages()) {
		new TomcatErrorPage(errorPage).addToContext(context);
	}
	for (MimeMappings.Mapping mapping : getMimeMappings()) {
		context.addMimeMapping(mapping.getExtension(), mapping.getMimeType());
	}
	configureSession(context);
	new DisableReferenceClearingContextCustomizer().customize(context);
	for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) {
		customizer.customize(context);
	}
}

找到TomcatStarter了,就是在这里加载的,但是其onStartup方法是等内嵌的tomcat启动后触发的,需要注意的是内嵌的tomcat只会加载TomcatStarter,不会加载其他的ServletContainerInitializer 的实现类

class TomcatStarter implements ServletContainerInitializer {

	private static final Log logger = LogFactory.getLog(TomcatStarter.class);

	private final ServletContextInitializer[] initializers;

	private volatile Exception startUpException;

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

	@Override
	public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
		try {
			for (ServletContextInitializer initializer : this.initializers) {
				//ServletContextInitializer 才是初始化的关键
				initializer.onStartup(servletContext);
			}
		}
		catch (Exception ex) {
			this.startUpException = ex;
			// Prevent Tomcat from logging and re-throwing when we know we can
			// deal with it in the main thread, but log for information here.
			if (logger.isErrorEnabled()) {
				logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: "
						+ ex.getMessage());
			}
		}
	}

	Exception getStartUpException() {
		return this.startUpException;
	}

}

可以看到TomcatStarter 中的 org.springframework.boot.context.embedded.ServletContextInitializer 是 springboot 初始化 servlet,filter,listener 的关键。

SpringBoot内置tomcat启动原理、以及SpringBoot初始化Servlet的源码分析
主要关注ServletWebServerApplicationContext,注意图中可以看出是一个lambda表达式,这个ServletContextInitializer实际上是通过ServletWebServerApplicationContext的getSelfInitializer方法创建的匿名内部类实例:
SpringBoot内置tomcat启动原理、以及SpringBoot初始化Servlet的源码分析

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
	//返回一个ServletContextInitializer的匿名内部类实例
	return this::selfInitialize;
}

private void selfInitialize(ServletContext servletContext) throws ServletException {
	prepareWebApplicationContext(servletContext);
	registerApplicationScope(servletContext);
	WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
	//核心在这
	for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
		//getServletContextInitializerBeans返回的是spring容器中所有的ServletContextInitializer的实例
		//最终会调用每个ServletContextInitializer的onStartup方法对Servlet容器进行初始化
		beans.onStartup(servletContext);
	}
}
//先看getServletContextInitializerBeans方法:
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
	//可以看出ServletContextInitializerBeans本质是一个集合
	//并且该集合的元素是ServletContextInitializer
	//getBeanFactory获取spring容器,这里就可以猜到
	//集合中的元素肯定是从Spring容器获取的
	return new ServletContextInitializerBeans(getBeanFactory());
}

看一下构造,证实我们的想法:

//ServletContextInitializerBeans.java的构造
//包含的元素是ServletContextInitializer
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
		Class<? extends ServletContextInitializer>... initializerTypes) {
	this.initializers = new LinkedMultiValueMap<>();
	//initializerTypes 默认是ServletContextInitializer.class
	this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
			: Collections.singletonList(ServletContextInitializer.class);
	//从spring容器中获取所有类型是ServletContextInitializer的实例
	addServletContextInitializerBeans(beanFactory);
	addAdaptableBeans(beanFactory);
	List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
			.flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
			.collect(Collectors.toList());
	this.sortedList = Collections.unmodifiableList(sortedInitializers);
	logMappings(this.initializers);
}

private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
	//initializerTypes 默认只有ServletContextInitializer.class
	for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {
		//getOrderedBeansOfType 方法便是去容器中寻找注册过得 ServletContextInitializer
		//其中 RegistrationBean 继承自 ServletContextInitializer 还实现了 Ordered 接口,在这儿用于排序
		for (Entry<String, ? extends ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory,
				initializerType)) {
			// 找到以后遍历每个实例,下面这个方法会根据每个实例具体Class类型进行分类收集
			addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory);
		}
	}
}

private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer,
		ListableBeanFactory beanFactory) {
	//根据不同的类型进行分类整理
	if (initializer instanceof ServletRegistrationBean) {
		//ServletRegistrationBean专门注册Servlet的
		Servlet source = ((ServletRegistrationBean<?>) initializer).getServlet();
		addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);
	}
	else if (initializer instanceof FilterRegistrationBean) {
		//FilterRegistrationBean专门注册Servlet的Filter的
		Filter source = ((FilterRegistrationBean<?>) initializer).getFilter();
		addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
	}
	else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
		String source = ((DelegatingFilterProxyRegistrationBean) initializer).getTargetBeanName();
		addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
	}
	else if (initializer instanceof ServletListenerRegistrationBean) {
		//ServletListenerRegistrationBean专门注册Servlet的Listener的
		EventListener source = ((ServletListenerRegistrationBean<?>) initializer).getListener();
		addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source);
	}
	else {
		addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory,
				initializer);
	}
}

private void addServletContextInitializerBean(Class<?> type, String beanName, ServletContextInitializer initializer,
		ListableBeanFactory beanFactory, Object source) {
	//最终存放到MultiValueMap,key是类型,value是一个集合
	this.initializers.add(type, initializer);
	if (source != null) {
		// Mark the underlying source as seen in case it wraps an existing bean
		this.seen.add(source);
	}
	if (logger.isTraceEnabled()) {
		String resourceDescription = getResourceDescription(beanName, beanFactory);
		int order = getOrder(initializer);
		logger.trace("Added existing " + type.getSimpleName() + " initializer bean '" + beanName + "'; order="
				+ order + ", resource=" + resourceDescription);
	}
}

我们看一下ServletContextInitializer到底是什么:
SpringBoot内置tomcat启动原理、以及SpringBoot初始化Servlet的源码分析

有的人可能不知道RegistrationBean是什么,SpringBoot 加载 Servlet有两种方式:

  • 注册方式一:
    servlet3.0注解(@WebServlet,@WebFilter,@WebListener…)+ @ServletComponentScan
    @WebServlet("/hello")
    public class HelloWorldServlet extends HttpServlet{...}
    
    @SpringBootApplication
    @ServletComponentScan
    public class SpringBootServletApplication {
    
       public static void main(String[] args) {
          SpringApplication.run(SpringBootServletApplication.class, args);
       }
    }
    
  • 注册方式二:RegistrationBean
    SpringBoot内置tomcat启动原理、以及SpringBoot初始化Servlet的源码分析
    ServletRegistrationBean 和 FilterRegistrationBean 都集成自 RegistrationBean ,RegistrationBean 是 springboot 中广泛应用的一个注册类,负责把 servlet,filter,listener 给容器化,使他们被 spring 托管,并且完成自身对 web 容器的注册。
    @Bean
    public ServletRegistrationBean helloWorldServlet() {
        ServletRegistrationBean helloWorldServlet = new ServletRegistrationBean();
        myServlet.addUrlMappings("/hello");
        myServlet.setServlet(new HelloWorldServlet());
        return helloWorldServlet;
    }
    

再回到selfInitialize方法,现在我们知道了这里遍历的就是Spring容器中的ServletContextInitializer

//ServletWebServerApplicationContext.java
private void selfInitialize(ServletContext servletContext) throws ServletException {
	prepareWebApplicationContext(servletContext);
	registerApplicationScope(servletContext);
	WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
	//核心在这
	for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
		//getServletContextInitializerBeans返回的是spring容器中所有的ServletContextInitializer的实例
		//最终会调用每个ServletContextInitializer的onStartup方法对Servlet容器进行初始化
		beans.onStartup(servletContext);
	}
}

我们只看一个最常用的ServletContextInitializer的实现,ServletRegistrationBean的onStartup方法:

//RegistrationBean.java
//先调用父类RegistrationBean.onStartup
public final void onStartup(ServletContext servletContext) throws ServletException {
	String description = getDescription();
	if (!isEnabled()) {
		logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
		return;
	}
	//register实现在具体子类
	register(description, servletContext);
}
//DynamicRegistrationBean.java
protected final void register(String description, ServletContext servletContext) {
	//addRegistration实现在具体子类
	D registration = addRegistration(description, servletContext);
	if (registration == null) {
		logger.info(
				StringUtils.capitalize(description) + " was not registered " + "(possibly already registered?)");
		return;
	}
	configure(registration);
}
//ServletRegistrationBean.java
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
	// 真正向Servlet容器添加Servlet的地方!!!
	String name = getServletName();
	return servletContext.addServlet(name, this.servlet);
}