基于SpingMVC组件初始化那一章节,了解了WebApplicationInitializer和SpringServletContainerInitializer的关系,以及在容器启动过程中的重要作用。
但是在Spring Boot应用启动的时候,却不能debug到这两个类相关类中,是不是SCI在Spring Boot项目不起作用?
对于Spring Boot应用来说,并没有使用SpringServletContainerInitializer来进行容器初始化,而是使用了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) {
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());
}
}
}
public Exception getStartUpException() {
return this.startUpException;
}
}
从源码上看,TomcatStarter是无法通过SPI来进行加载实例化的:
- 它所在的Jar包是spring-boot.jar,并没有提供META-INT/services/目录
- 它的声明是class不是public的
那么它使怎么启动的呢?
源码追溯
在SpringApplication源码分析这一章里讲了SpringApplication.run()方法所完成的事情。其内部主要逻辑就是创建AnnotationConfigServletWebServerApplicationContext以及ConfigurableEnvironment环境的准备(Environment主要与PropertySource以及activeProfile相关),最后调用applicationContext的refresh()方法。这里就是很关键的地方。
AnnotationConfigServletWebServerApplicationContext继承自GenericWebApplicationContext,refresh()方法就在它这里实现。
@Override
public final void refresh() throws BeansException, IllegalStateException {
try {
//调用abstractApplicationContext里面的refresh()方法,
//而该refresh()方法会调用onRefresh()方法,即下面的实现
super.refresh();
}
catch (RuntimeException ex) {
stopAndReleaseWebServer();
throw ex;
}
}
//abstractApplicationContext会调用子类实现的onRefresh()方法
@Override
protected void onRefresh() {
super.onRefresh();
try {
//创建webServer
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
//创建webServer
private void createWebServer() {
//一开始webServer和servletContext为null
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
//获取WebServer的工厂类,由不同的厂家实现,这里返回的是TomcatServletWebServerFactory
ServletWebServerFactory factory = getWebServerFactory();
//getSelfInitializer会加载所配置的ServletRegistrationBean,FIlterRegistrationBean等
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();
}
//从BeanFactory中获取所配置的ServletRegistrationBean,FIlterRegistrationBean等加入到ServletContext中
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
beanFactory);
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
getServletContext());
existingScopes.restore();
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
getServletContext());
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
}
这部分代码主要就是调用abstractApplicationContext.refresh()方法,以及通过TomcatServletWebServerFactory工厂类创建webServer。
//TomcatServletWebServerFactory开始
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,prepareContext就是准备TomcatEmbeddedContext
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File documentRoot = getValidDocumentRoot();
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
if (documentRoot != null) {
context.setResources(new LoaderHidingResourceRoot(context));
}
context.setName(getContextPath());
。。。这里给TomcatEmbeddedContext赋值
context.addLifecycleListener(new StaticResourceConfigurer(context));
//mergeInitializers就是添加TomcatStarter所需要的ServletContextInitializer
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
host.addChild(context);
//创建TomcatStarter的地方
configureContext(context, initializersToUse);
postProcessContext(context);
}
//添加TomcatStarter所需要的ServletContextInitializer,比如SessionConfiguringInitializer
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的地方
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);
}
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);
for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) {
customizer.customize(context);
}
}
从这代码看到TomcatStarter是如何创建的。
与SpringMVC容器初始化类比
- SpringServletContainerInitializer对应TomcatStarter
- WebApplicationInitializer对应ServletContextInitializer
Spring Boot怎么添加Servlet,filter,Listener?
在上述代码示例中createWebServer的时候会调用selfInitialize(),这里面会调用这些RegistrationBean。
使用RegistrationBean(ServletRegistrationBean, FilterRegistrationBean, ServletListenerRegistrationBean)。
RegistrationBean实现了ServletContextInitializer。
网友评论