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<ServletContextInitializer> getServletContextInitializerBeans() {
	return new ServletContextInitializerBeans(getBeanFactory());
}

ServletContextInitializerBeans

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

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

	private final List<Class<? extends ServletContextInitializer>> initializerTypes;
	
	private List<ServletContextInitializer> sortedList;
	...
	@Override
	public Iterator<ServletContextInitializer> iterator() {
		return this.sortedList.iterator();
	}

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

查看 ServletContextInitializerBeans 集合类的构造器:

@SafeVarargs
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
		Class<? extends ServletContextInitializer>... initializerTypes) {
	this.initializers = new LinkedMultiValueMap<>();
	// 这里我们没有在构造器中传 initializerTypes,所以默认为 ServletContextInitializer
	this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
			: Collections.singletonList(ServletContextInitializer.class);
	// 核心方法
	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);
}

查看 addServletContextInitializerBeans(beanFactory) 方法:

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

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

在这里插入图片描述

接着遍历上面 getOrderedBeansOfType() 方法中获取到的 Bean,判断是属于 ServletRegistrationBeanFilterRegistrationBeanDelegatingFilterProxyRegistrationBeanServletListenerRegistrationBean,并添加到成员变量: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<? extends ServletContextInitializer>... initializerTypes) {
	...
	// 赋值 sortedList
	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);
}

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

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);
	}
	...
}

TomcatStarterSpringBoot 对于 嵌入式 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<Class<?>> 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 是如何结合 TomcatServlet 容器 实现 ServletFilterListerner 等组件与 Spring IoC 容器 结合并实现加载和初始化的。

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

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值