栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SpringBoot - 浅析 ServletContextInitializer 如何注册 Servlet 组件

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SpringBoot - 浅析 ServletContextInitializer 如何注册 Servlet 组件

概述

在 Java - Servlet 3.0 这篇文章中,我们知道在 Servlet 3.0 之后,可以使用 注解来配置 servlet 以及 运行时插件可插拔 能力。

SpringBoot(使用版本:2.3.12.RELEASE) 为我们提供了开箱即用的 web 环境,当然也提供了与 Servlet 3.0 的集成,核心在 org.springframework.boot.web.servlet 包中:


ServletContextInitializer

在 Java - Servlet 3.0 这篇文章中,我们知道 Servlet 3.0 提供了 ServletContainerInitializer 接口来提供插件化能力,SpingBoot 提供了 ServletContextInitializer 接口,以编程的方式来配置 Servlet 3.0+ 上下文,但是生命周期是由 Spring 来管理的,而不是 Servlet 容器。

集成 Servlet 容器

在 SpringBoot - 内嵌 tomcat 等 servlet 容器的原理 这篇文章中,我们知道 SpringBoot 默认使用 Tomcat 作为 servlet 容器,在 SpringBoot 的启动流程中,ServletWebServerApplicationContext 类中的 createWebServer() 方法用于创建 WebServer。

private void createWebServer() {
	WebServer webServer = this.webServer;
	ServletContext servletContext = getServletContext();
	if (webServer == null && servletContext == null) {
		ServletWebServerFactory factory = getWebServerFactory();
		// [1]getSelfInitializer():返回用于完成 WebApplicationContext 设置的 ServletContextInitializer
		// [2]getWebServer():会完成 ServletContextInitializer 转换为 ServletContainerInitializer
		this.webServer = factory.getWebServer(getSelfInitializer());
		...
	}
	...
}
[1] getSelfInitializer()
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
	return this::selfInitialize;
}

private void selfInitialize(ServletContext servletContext) throws ServletException {
	// 准备 WebApplicationContext
	prepareWebApplicationContext(servletContext);
	registerApplicationScope(servletContext);
	WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
	// getServletContextInitializerBeans:获取所有 ServletContextInitializer 类型的 Bean
	for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
		beans.onStartup(servletContext);
	}
}

查看 getServletContextInitializerBeans() 方法,返回了一个集合:ServletContextInitializerBeans

protected Collection getServletContextInitializerBeans() {
	return new ServletContextInitializerBeans(getBeanFactory());
}
ServletContextInitializerBeans

ServletContextInitializerBeans 类实现了 AbstractCollection,并重写了 iterator() 和 size(),遍历的自然也就是 iterator() 中使用的成员变量 sortedList

public class ServletContextInitializerBeans extends AbstractCollection {
	...
	
	private final MultiValueMap, ServletContextInitializer> initializers;

	private final List> initializerTypes;
	
	private List sortedList;
	...
	@Override
	public Iterator iterator() {
		return this.sortedList.iterator();
	}

	@Override
	public int size() {
		return this.sortedList.size();
	}
	...
}

查看 ServletContextInitializerBeans 集合类的构造器:

@SafeVarargs
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
		Class... initializerTypes) {
	this.initializers = new LinkedMultiValueMap<>();
	// 这里我们没有在构造器中传 initializerTypes,所以默认为 ServletContextInitializer
	this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
			: Collections.singletonList(ServletContextInitializer.class);
	// 核心方法
	addServletContextInitializerBeans(beanFactory);
	addAdaptableBeans(beanFactory);
	List sortedInitializers = this.initializers.values().stream()
			.flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
			.collect(Collectors.toList());
	this.sortedList = Collections.unmodifiableList(sortedInitializers);
	logMappings(this.initializers);
}

查看 addServletContextInitializerBeans(beanFactory) 方法:

private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
	// 这里的 this.initializerTypes 就是 ServletContextInitializer
	for (Class initializerType : this.initializerTypes) {
		for (Entry initializerBean : 
					// 获取所有 initializerType 类型的 Bean 并排序
					getOrderedBeansOfType(beanFactory, initializerType)) {
			// 添加 ServletContextInitializerBean 到成员变量:initializers 中,是一个 Spring 实现 Map 接口的 MultiValueMap 类型
			addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory);
		}
	}
}

打断点获取到 getOrderedBeansOfType() 方法返回的值,默认是 DispatcherServletRegistrationBean:

接着遍历上面 getOrderedBeansOfType() 方法中获取到的 Bean,判断是属于 ServletRegistrationBean、FilterRegistrationBean、DelegatingFilterProxyRegistrationBean、ServletListenerRegistrationBean,并添加到成员变量:initializers 中:

private void addServletContextInitializerBean(Class type, String beanName, ServletContextInitializer initializer, ListableBeanFactory beanFactory, Object source) {
	this.initializers.add(type, initializer);
	...
}

接着在构造器中按指定规则排序并赋值给成员变量 sortedList:

@SafeVarargs
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
		Class... initializerTypes) {
	...
	// 赋值 sortedList
	List sortedInitializers = this.initializers.values().stream()
			.flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
			.collect(Collectors.toList());
	this.sortedList = Collections.unmodifiableList(sortedInitializers);
	logMappings(this.initializers);
}

接着会遍历 sortedList,并调用其中的 Bean 的 onStartUp 方法:

private void selfInitialize(ServletContext servletContext) throws ServletException {
	prepareWebApplicationContext(servletContext);
	registerApplicationScope(servletContext);
	WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
	for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
		// 调用 onStartUp 方法
		beans.onStartup(servletContext);
	}
}
[2] getWebServer() ServletWebServerFactory

SpringBoot 对所支持的 Servlet 容器 做了抽象处理,提取出了工厂类:ServletWebServerFactory

@FunctionalInterface
public interface ServletWebServerFactory {

	WebServer getWebServer(ServletContextInitializer... initializers);

}

常用的 Servlet 容器 实现:

Servlet 容器类型WebServer 模型接口WebServer 工厂实现类
TomcatTomcatWebServerTomcatServletWebServerFactory
JettyJettyWebServerJettyServletWebServerFactory
UndertowUndertowWebServerUndertowServletWebServerFactory

类路径下有且只能有一个 ServletWebServerFactory 的实现类:

protected ServletWebServerFactory getWebServerFactory() {
	// Use bean names so that we don't consider the hierarchy
	String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
	if (beanNames.length == 0) {
		throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
				+ "ServletWebServerFactory bean.");
	}
	if (beanNames.length > 1) {
		throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
				+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
	}
	return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}

private void createWebServer() {
	...
		this.webServer = factory.getWebServer(getSelfInitializer());
	...
}

从代码中可以看到,getWebServer 是把 getSelfInitializer() 返回的 ServletWebServerApplicationContext 作为参数:


查看 TomcatServletWebServerFactory 中的 getWebServer 方法:

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
	...
	prepareContext(tomcat.getHost(), initializers);
	return getTomcatWebServer(tomcat);
}

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
	...
	context.addLifecycleListener(new StaticResourceConfigurer(context));
	// 组合 SpringBoot 需要初始化的所有 ServletContextInitializer
	ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
	host.addChild(context);
	// 配置上下文环境
	configureContext(context, initializersToUse);
	postProcessContext(context);
}

protected void configureContext(Context context, ServletContextInitializer[] initializers) {
	TomcatStarter starter = new TomcatStarter(initializers);
	if (context instanceof TomcatEmbeddedContext) {
		TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
		embeddedContext.setStarter(starter);
		embeddedContext.setFailCtxIfServletStartFails(true);
	}
	context.addServletContainerInitializer(starter, NO_CLASSES);
	for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) {
		context.addLifecycleListener(lifecycleListener);
	}
	...
}

TomcatStarter 是 SpringBoot 对于 嵌入式 Tomcat 的定义,实现了 ServletContainerInitializer,查看源码:

class TomcatStarter implements ServletContainerInitializer {

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

	private final ServletContextInitializer[] initializers;

	private volatile Exception startUpException;

	// 会把 ServletContextInitializer 作为构造器参数传进来
	TomcatStarter(ServletContextInitializer[] initializers) {
		this.initializers = initializers;
	}

	@Override
	public void onStartup(Set> classes, ServletContext servletContext) throws ServletException {
		try {
			// Tomcat 启动的时候会调用 onStartUp 方法
			for (ServletContextInitializer initializer : this.initializers) {
				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;
	}

}
总结

通过以上分析,源码追踪,我们知道了 SpringBoot 是如何结合 Tomcat 等 Servlet 容器 实现 Servlet、Filter、Listerner 等组件与 Spring IoC 容器 结合并实现加载和初始化的。

从 ServletContextInitializer 的关系类图中可以看到,SpringBoot 定义了 ServletRegistrationBean、ServletListenerRegistrationBean、FilterRegistrationBean、DelegatingFilterProxyRegistrationBean 等基于 ServletContextInitializer 接口的实现类来实现组件的加载,在 SpringBoot 学习专栏 中会具体讲解每一种 RegistrationBean 的用法,欢迎订阅关注。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1014171.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号