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

标签: # Spring Boot  spring boot  tomcat  servlet  web  filter

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的启动依赖:
在这里插入图片描述

开发阶段对我们来说使用内置的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

在这里插入图片描述

而其中我们常用的有两个: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:
在这里插入图片描述
在这里插入图片描述
关于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 的关键。

在这里插入图片描述
主要关注ServletWebServerApplicationContext,注意图中可以看出是一个lambda表达式,这个ServletContextInitializer实际上是通过ServletWebServerApplicationContext的getSelfInitializer方法创建的匿名内部类实例:
在这里插入图片描述

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到底是什么:
在这里插入图片描述

有的人可能不知道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
    在这里插入图片描述
    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);
}
版权声明:本文为weixin_41947378原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_41947378/article/details/108729720

智能推荐

【Sublime】使用 Sublime 工具时运行python文件

使用 Sublime 工具时报Decode error - output not utf-8解决办法   在菜单中tools中第四项编译系统 内最后一项增添新的编译系统 自动新建 Python.sublime-build文件,并添加"encoding":"cp936"这一行,保存即可 使用python2 则注释encoding改为utf-8 ctr...

java乐观锁和悲观锁最底层的实现

1. CAS实现的乐观锁 CAS(Compare And Swap 比较并且替换)是乐观锁的一种实现方式,是一种轻量级锁,JUC 中很多工具类的实现就是基于 CAS 的,也可以理解为自旋锁 JUC是指import java.util.concurrent下面的包, 比如:import java.util.concurrent.atomic.AtomicInteger; 最终实现是汇编指令:lock...

Python 中各种imread函数的区别与联系

  原博客:https://blog.csdn.net/renelian1572/article/details/78761278 最近一直在用python做图像处理相关的东西,被各种imread函数搞得很头疼,因此今天决定将这些imread总结一下,以免以后因此犯些愚蠢的错误。如果你正好也对此感到困惑可以看下这篇总结。当然,要了解具体的细节,还是应该 read the fuc...

用栈判断一个字符串是否平衡

注: (1)本文定义:左符号:‘(’、‘[’、‘{’…… 右符号:‘)’、‘]’、‘}’……. (2)所谓的字符串的符号平衡,是指字符串中的左符号与右符号对应且相等,如字符串中的如‘(&r...

JAVA环境变量配置

位置 计算机->属性->高级系统设置->环境变量 方式一 用户变量新建path 系统变量新建classpath 方式二 系统变量 新建JAVA_HOME,值为JDK路径 编辑path,前加 方式三 用户变量新建JAVA_HOME 此路径含lib、bin、jre等文件夹。后运行tomcat,eclipse等需此变量,故最好设。 用户变量编辑Path,前加 系统可在任何路径识别jav...

猜你喜欢

常用的伪类选择器

CSS选择器众多 CSS选择器及权重计算 最常用的莫过于类选择器,其它的相对用的就不会那么多了,当然属性选择器和为类选择器用的也会比较多,这里我们就常用的伪类选择器来讲一讲。 什么是伪类选择器? CSS伪类是用来添加一些选择器的特殊效果。 常用的为类选择器 状态伪类 我们中最常见的为类选择器就是a标签(链接)上的为类选择器。 当我们使用它们的时候,需要遵循一定的顺序问题,否则将可能出现bug 注意...

ButterKnife的使用介绍及原理探究(六)

前面分析了ButterKnife的源码,了解其实现原理,那么就将原理运用于实践吧。 github地址:       点击打开链接 一、自定义注解 这里为了便于理解,只提供BindView注解。 二、添加注解处理器 添加ViewInjectProcessor注解处理器,看代码, 这里分别实现了init、getSupportedAnnotationTypes、g...

1.写一个程序,提示输入两个字符串,然后进行比较,输出较小的字符串。考试复习题库1|要求:只能使用单字符比较操作。

1.写一个程序,提示输入两个字符串,然后进行比较,输出较小的字符串。 要求只能使用单字符比较操作。 参考代码: 实验结果截图:...

小demo:slideDown()实现二级菜单栏下拉效果

效果如下,鼠标经过显示隐藏的二级菜单栏 但是这样的时候会存在一个问题,就是鼠标快速不停移入移出会导致二级菜单栏闪屏现象,一般需要使用stop()来清除事件  ...

基于docker环境的mysql主从复制

1、安装docker 可以参考之前的博客,之前写过了~ 2、拉取mysql镜像 3、创建mysql01和mysql02实例 主: 从: 4、进入容器修改配置 1)修改主数据库配置 进入主数据库容器 切换到 etc/mysql/目录下 查看可以看到my.cnf文件,使用vim编辑器打开,但是需要提前安装 安装vim命令: 安装成功后,修改my.cnf文件 新增配置后的my.cnf: binlog 日...