SpringBoot2整合SpringSecurity+Swagger3系列
Spring Boot启动过程中会创建一个WerbServer,对应的代码如下(一般情况下一开始servletContext是不存在的,需要创建Web服务器 )
-- org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
// 从Spring中获取ServletWebServerFactory
ServletWebServerFactory factory = getWebServerFactory();
// 根据ServletWebServerFactory创建Web服务器 并执行启动逻辑
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
- 从Spring中获取ServletWebServerFactory
/**
* Returns the {@link ServletWebServerFactory} that should be used to create the
* embedded {@link WebServer}. By default this method searches for a suitable bean in
* the context itself.
* @return a {@link ServletWebServerFactory} (never {@code null})
*/
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);
}
具体是哪个Sevlet容器,取决于ServletWebServerFactoryConfiguration自动注入的逻辑,比如当前环境下存在Servlet、Tomcat、UpgradeProtocol而且没有其他ServletWebServerFactory类型Bean的时候就会注入一个TomcatServletWebServerFactory类型的ServletWebServerFactory实现。
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
}
- 根据ServletWebServerFactory创建Web服务器
创建Web服务器需要传入ServletContextInitializer
列表,根据Servlet 3.0规范,在Servlet容器在启动初始化的过程中会调用注册的ServletContextInitializer
的onStartup方法,这是一种SPI机制,让用户参与到Servlet容器的启动中,通常来说,主要是用于注册Servlet的一些组件(比如Servlet、Filter、Listener),当然也包括启动的一些组件。以Tomcat为例,在创建web服务器的时候其实就是创建了一个Tomcat实例。
@Override
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);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
首先这里传入的是一个匿名的ServletContextInitializer的实现,而对应的onStartup方法实现则是selfInitialize。在这里selfInitialize并不会真实调用,只有当这个getSelfInitializer返回的ServletContextInitializer的onStartup方法调用时才会触发。
/**
* Returns the {@link ServletContextInitializer} that will be used to complete the
* setup of this {@link WebApplicationContext}.
* @return the self initializer
* @see #prepareWebApplicationContext(ServletContext)
*/
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
创建Tomcat主要涉及到连接器(Connector)和容器(Service)、基础目录(baseDir)。至于传入的ServletContextInitializer还有内置的用于管理Session和Cookie的SessionConfiguringInitializer都统一
/**
* Utility method that can be used by subclasses wishing to combine the specified
* {@link ServletContextInitializer} parameters with those defined in this instance.
* @param initializers the initializers to merge
* @return a complete set of merged initializers (with the specified parameters
* appearing first)
*/
protected final ServletContextInitializer[] mergeInitializers(ServletContextInitializer... initializers) {
List<ServletContextInitializer> mergedInitializers = new ArrayList<>();
mergedInitializers.add((servletContext) -> this.initParameters.forEach(servletContext::setInitParameter));
mergedInitializers.add(new SessionConfiguringInitializer(this.session));
mergedInitializers.addAll(Arrays.asList(initializers));
mergedInitializers.addAll(this.initializers);
return mergedInitializers.toArray(new ServletContextInitializer[0]);
}
如下图所示
最后又打包塞到了一个TomcatStarter类当中
TomcatStarter starter = new TomcatStarter(initializers);
if (context instanceof TomcatEmbeddedContext) {
TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
embeddedContext.setStarter(starter);
embeddedContext.setFailCtxIfServletStartFails(true);
}
context.addServletContainerInitializer(starter, NO_CLASSES);
而这个类又是ServletContainerInitializer。同样也是Servlet规范中的回调SPI接口。
@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
try {
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());
}
}
}
- 执行最后的启动流程
创建完一个Servlet服务器之后,就会启动容器。
/**
* Create a new {@link TomcatWebServer} instance.
* @param tomcat the underlying Tomcat server
* @param autoStart if the server should be started
*/
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
此过程中就会执行org.springframework.boot.web.embedded.tomcat.TomcatStarter
的onStartup
方法,最后触发所有收集到的ServletContainerInitializer
类的onStartup
方法。
总结一下:在Spring Boot当中,Spring根据条件获取ServletWebServerFactory,然后通过工厂方式获取ServletWebServer。如果创建的是Tomcat容器,那么会通过TomcatStarter将几个关键的ServletContextInitializer组合到一起。而TomcatStarter本身是一个ServletContainerInitializer,Tomcat在容器初始化过程中,会回调这个类对象的onStartup,对于TomcatStarter对象来说,又在onStartup方法中遍历其中的ServletContextInitializer列表,依次执行每一个元素的onStartup方法,最后完成整个容器的启动方法。理解Servlet规范的话,就会理解ServletContainerInitializer作为Servlet容器的SPI,是外部系统与Servlet容器整合的最关键接口,其实在Spring MVC中,同样是这个接口在起作用的。