1.启动时,tomcat调用SpringServletContainerInitializer的onStartup函数,传当前可被调用的启动类。
public void onStartup(Set> webAppInitializerClasses, ServletContext servletContext){
for (Class> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer) waiClass.newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
2.如果有RootConfigClasses配置,则会注册ContextLoaderListener,如AdminInitializer类,多个AbstractAnnotationConfigDispatcherServletInitializer中,有且仅有一个可以配置RootConfigClasses,其余的如果配置了则会抛异常。
AdminInitializer类的Root配置。
protected Class>[] getRootConfigClasses() {
return new Class[] { ApplicationConfig.class };
}
WebInitializer和ApiInitializer都是返回null.
protected Class>[] getRootConfigClasses() {
return null;
}
protected void registerContextLoaderListener(ServletContext servletContext) {
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
//在onStartUp函数结束后,Tomcat会主动调ContextLoaderListener类的contextInitialized函数。
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
3.创建DispatcherServlet对象并注册。setLoadOnStartup(1)参数为1,表示Tomcat在执行完ContextLoaderListener的相关beanmap处理后,将依次调用Servlet的loadOnStartup函数。
protected void registerDispatcherServlet(ServletContext servletContext) {
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
//setLoadOnStartup(1),将继ContextLoaderListener之后被调用。
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
}
4.经上述三个步骤,程序将会返回至Tomcat内部,至此完成了onStartup的调用。该过程Tomcat只是完成了HttpServlet的注册和ContextLoaderListener的注册,而beanFactory扫描Controller/Service/Conponent/Repository注解建立beanmap及相应的单实例,以及涉及到的Controller的成员注入,仍没有开始。
5.因为是异步调用的关系,Tomcat接着调用ContextLoaderListener完成WebApplicationContext配置。
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
//这段异常,表示有且只有一个getRootConfigClasses函数能被返回全局配置。其它均会共享使用它的全局beanmap。
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
try {
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
//将扫描指定的目录下的所有Component组件,并形成beanmap,并依据autowired注解自动完成每一个bean对象的单实例化。
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//保存到servletContext的属性中,方便后续DispatcherContext的bean调用及合并。
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
6.因为setLoadOnStartup的关系,Tomcat调用Servlet的bean初始化。
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
//此处是initWebAppicationContext与5步骤中的initWebApplicationContext不是同一个函数。
//但函数的用途是一致的,都是用于依据bean配置构建beanmap及单实例化。
//此处的配置文件是由getServletConfigClasses提供。
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
protected WebApplicationContext initWebApplicationContext() {
//获取getRootConfigClasses设置的全局配置,由此可知每个DispatchServlet对象均会继承全局Root配置。
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
//和Root配置一样的搜索过程,建立DispatchServlet的beanmap列表。
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
return wac;
}
7.到这步骤,已经完成Spring的初始化进程了,如果不报错的话,应该可以访问网站了。