文章源自JAVA秀-https://www.javaxiu.com/19947.html
前一章节回顾: 通过前一个章节的了解,我们基本上了解了tomcat的源代码解析我们的web.xml文件的过程。如果说我们的后台服务不是spring开发的,而是使用的最原始的servlet开发的,那么当我们的StandardContext类的startInternal方法执行完毕之后,那么也就意味着我们的后台服务启动成功了;但是如果说我们的后台服务是基于spring开发的,那么spring这个容器又是如果被启动起来的呢?本章节我们将重点讲解里面的原理,文章的开头我们将首先回顾一些基本知识。文章源自JAVA秀-https://www.javaxiu.com/19947.html
文章源自JAVA秀-https://www.javaxiu.com/19947.html
1. spring容器和web后台服务的基本知识内容回顾
1.什么是一个spring容器?
其实就是一个beanfactory对象或者applicationcontext对象。前者是标准版spring容器,后者是豪华版spring容器。文章源自JAVA秀-https://www.javaxiu.com/19947.html
2.什么是启动一个spring容器呢?
其实就是创建一个beanfactory对象或者applicationcontext对象。文章源自JAVA秀-https://www.javaxiu.com/19947.html
3.什么是启动一个web后台服务呢?
其实就是将一个war包丢到一个web容器(tomcat或者jetty)的执行目录下,由容器自动解压war包,并读取web.xml文件并将该文件解析成为一个StandardContext对象,该对象可以理解为就是一个web后台服务的核心对象。文章源自JAVA秀-https://www.javaxiu.com/19947.html
了解了上面的三点之后,我们基本上知道了如果一个基于spring开发的后台服务,那么一定会经历两个过程:第一,首先tomcat容器需要解析web.xml文件为一个StandardContext对象(启动一个web后台服务);第二,启动spring容器(创建一个beanfactory对象或者applicationcontext对象)。我们看看下面的启动基于spring开发的后台服务的启动日志,我们会发现,确实是有两段日志存在。文章源自JAVA秀-https://www.javaxiu.com/19947.html
第一步:启动后台服务文章源自JAVA秀-https://www.javaxiu.com/19947.html
文章源自JAVA秀-https://www.javaxiu.com/19947.html
第二步:启动spring容器文章源自JAVA秀-https://www.javaxiu.com/19947.html
文章源自JAVA秀-https://www.javaxiu.com/19947.html
经过上面两个过程,我们基于spring开发的后台服务也就真正的启动起来了。文章源自JAVA秀-https://www.javaxiu.com/19947.html
2. springmvc的真正启动过程分析
从前面的知识点,我们知道了StandardContext类的startInternal方法里面,当执行完fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null)代码后,我们的web后台服务重要的部分已经启动完毕了,那么在哪儿开始了spring容器的启动呢?答案是loadOnStartup(findChildren())代码行的执行。文章源自JAVA秀-https://www.javaxiu.com/19947.html
第一步:执行loadOnStartup(findChildren())代码行,开始spring的启动工作。loadOnStartup方法内部也不直接启动spring容器,而是进一步调用StandardWrapper类的load方法。startInternal方法和loadOnStartup方法都是在StandardContext类中。文章源自JAVA秀-https://www.javaxiu.com/19947.html
@Override
protected synchronized void startInternal() throws LifecycleException {
if(log.isDebugEnabled())
log.debug("Starting " + getBaseName());
// Send j2ee.state.starting notification
if (this.getObjectName() != null) {
Notification notification = new Notification("j2ee.state.starting",
this.getObjectName(), sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}
setConfigured(false);
boolean ok = true;
// Currently this is effectively a NO-OP but needs to be called to
// ensure the NamingResources follows the correct lifecycle
if (namingResources != null) {
namingResources.start();
}
// Post work directory
postWorkDirectory();
// Add missing components as necessary
if (getResources() == null) { // (1) Required by Loader
if (log.isDebugEnabled())
log.debug("Configuring default Resources");
try {
setResources(new StandardRoot(this));
} catch (IllegalArgumentException e) {
log.error(sm.getString("standardContext.resourcesInit"), e);
ok = false;
}
}
if (ok) {
resourcesStart();
}
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
// An explicit cookie processor hasn't been specified; use the default
if (cookieProcessor == null) {
cookieProcessor = new Rfc6265CookieProcessor();
}
// Initialize character set mapper
getCharsetMapper();
// Validate required extensions
boolean dependencyCheck = true;
try {
dependencyCheck = ExtensionValidator.validateApplication
(getResources(), this);
} catch (IOException ioe) {
log.error(sm.getString("standardContext.extensionValidationError"), ioe);
dependencyCheck = false;
}
if (!dependencyCheck) {
// do not make application available if dependency check fails
ok = false;
}
// Reading the "catalina.useNaming" environment variable
String useNamingProperty = System.getProperty("catalina.useNaming");
if ((useNamingProperty != null)
&& (useNamingProperty.equals("false"))) {
useNaming = false;
}
if (ok && isUseNaming()) {
if (getNamingContextListener() == null) {
NamingContextListener ncl = new NamingContextListener();
ncl.setName(getNamingContextName());
ncl.setExceptionOnFailedWrite(getJndiExceptionOnFailedWrite());
addLifecycleListener(ncl);
setNamingContextListener(ncl);
}
}
// Standard container startup
if (log.isDebugEnabled())
log.debug("Processing standard container startup");
// Binding thread
ClassLoader oldCCL = bindThread();
try {
if (ok) {
// Start our subordinate components, if any
Loader loader = getLoader();
if (loader instanceof Lifecycle) {
((Lifecycle) loader).start();
}
// since the loader just started, the webapp classloader is now
// created.
setClassLoaderProperty("clearReferencesRmiTargets",
getClearReferencesRmiTargets());
setClassLoaderProperty("clearReferencesStopThreads",
getClearReferencesStopThreads());
setClassLoaderProperty("clearReferencesStopTimerThreads",
getClearReferencesStopTimerThreads());
setClassLoaderProperty("clearReferencesHttpClientKeepAliveThread",
getClearReferencesHttpClientKeepAliveThread());
setClassLoaderProperty("clearReferencesObjectStreamClassCaches",
getClearReferencesObjectStreamClassCaches());
setClassLoaderProperty("clearReferencesObjectStreamClassCaches",
getClearReferencesObjectStreamClassCaches());
setClassLoaderProperty("clearReferencesThreadLocals",
getClearReferencesThreadLocals());
// By calling unbindThread and bindThread in a row, we setup the
// current Thread CCL to be the webapp classloader
unbindThread(oldCCL);
oldCCL = bindThread();
// Initialize logger again. Other components might have used it
// too early, so it should be reset.
logger = null;
getLogger();
Realm realm = getRealmInternal();
if(null != realm) {
if (realm instanceof Lifecycle) {
((Lifecycle) realm).start();
}
// Place the CredentialHandler into the ServletContext so
// applications can have access to it. Wrap it in a "safe"
// handler so application's can't modify it.
CredentialHandler safeHandler = new CredentialHandler() {
@Override
public boolean matches(String inputCredentials, String storedCredentials) {
return getRealmInternal().getCredentialHandler().matches(inputCredentials, storedCredentials);
}
@Override
public String mutate(String inputCredentials) {
return getRealmInternal().getCredentialHandler().mutate(inputCredentials);
}
};
context.setAttribute(Globals.CREDENTIAL_HANDLER, safeHandler);
}
// Notify our interested LifecycleListeners
//关键行代码,在这行方法处,正式开始解析web.xml文件,启动web后台服务
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
// Start our child containers, if not already started
for (Container child : findChildren()) {
if (!child.getState().isAvailable()) {
child.start();
}
}
// Start the Valves in our pipeline (including the basic),
// if any
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
// Acquire clustered manager
Manager contextManager = null;
Manager manager = getManager();
if (manager == null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.cluster.noManager",
Boolean.valueOf((getCluster() != null)),
Boolean.valueOf(distributable)));
}
if ((getCluster() != null) && distributable) {
try {
contextManager = getCluster().createManager(getName());
} catch (Exception ex) {
log.error(sm.getString("standardContext.cluster.managerError"), ex);
ok = false;
}
} else {
contextManager = new StandardManager();
}
}
// Configure default manager if none was specified
if (contextManager != null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.manager",
contextManager.getClass().getName()));
}
setManager(contextManager);
}
if (manager!=null && (getCluster() != null) && distributable) {
//let the cluster know that there is a context that is distributable
//and that it has its own manager
getCluster().registerManager(manager);
}
}
if (!getConfigured()) {
log.error(sm.getString("standardContext.configurationFail"));
ok = false;
}
// We put the resources into the servlet context
if (ok)
getServletContext().setAttribute
(Globals.RESOURCES_ATTR, getResources());
if (ok ) {
if (getInstanceManager() == null) {
javax.naming.Context context = null;
if (isUseNaming() && getNamingContextListener() != null) {
context = getNamingContextListener().getEnvContext();
}
Map<String, Map<String, String>> injectionMap = buildInjectionMap(
getIgnoreAnnotations() ? new NamingResourcesImpl(): getNamingResources());
setInstanceManager(new DefaultInstanceManager(context,
injectionMap, this, this.getClass().getClassLoader()));
}
getServletContext().setAttribute(
InstanceManager.class.getName(), getInstanceManager());
InstanceManagerBindings.bind(getLoader().getClassLoader(), getInstanceManager());
}
// Create context attributes that will be required
if (ok) {
getServletContext().setAttribute(
JarScanner.class.getName(), getJarScanner());
}
// Set up the context init params
mergeParameters();
// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}
// Configure and call application event listeners
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
// Check constraints for uncovered HTTP methods
// Needs to be after SCIs and listeners as they may programmatically
// change constraints
if (ok) {
checkConstraintsForUncoveredMethods(findConstraints());
}
try {
// Start manager
Manager manager = getManager();
if (manager instanceof Lifecycle) {
((Lifecycle) manager).start();
}
} catch(Exception e) {
log.error(sm.getString("standardContext.managerFail"), e);
ok = false;
}
// Configure and call application filters
if (ok) {
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
// Load and initialize all "load on startup" servlets
if (ok) {
//关键行代码,在这行方法处,正式开始启动spring容器
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
// Start ContainerBackgroundProcessor thread
super.threadStart();
} finally {
// Unbinding thread
unbindThread(oldCCL);
}
// Set available status depending upon startup success
if (ok) {
if (log.isDebugEnabled())
log.debug("Starting completed");
} else {
log.error(sm.getString("standardContext.startFailed", getName()));
}
startTime=System.currentTimeMillis();
// Send j2ee.state.running notification
if (ok && (this.getObjectName() != null)) {
Notification notification =
new Notification("j2ee.state.running", this.getObjectName(),
sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}
// The WebResources implementation caches references to JAR files. On
// some platforms these references may lock the JAR files. Since web
// application start is likely to have read from lots of JARs, trigger
// a clean-up now.
getResources().gc();
// Reinitializing if something went wrong
if (!ok) {
setState(LifecycleState.FAILED);
} else {
setState(LifecycleState.STARTING);
}
}
loadOnStartup并不直接启动spring容器,而是将spring容器的启动委托给StandardWrapper类的实例对象。当我们打断点在wrapper.load();行代码的时候,我们会发现wrapper对象实际上就是经过包装后的dispatchServlet对象。到这一步,我们就比较熟悉了,终于到这个重要的servlet了。本方法的本质实际上就是把spring容器的启动工作交给dispatchServlet对象进行处理。文章源自JAVA秀-https://www.javaxiu.com/19947.html
public boolean loadOnStartup(Container children[]) {
// Collect "load on startup" servlets that need to be initialized
TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
for (int i = 0; i < children.length; i++) {
Wrapper wrapper = (Wrapper) children[i];
int loadOnStartup = wrapper.getLoadOnStartup();
if (loadOnStartup < 0)
continue;
Integer key = Integer.valueOf(loadOnStartup);
ArrayList<Wrapper> list = map.get(key);
if (list == null) {
list = new ArrayList<>();
map.put(key, list);
}
list.add(wrapper);
}
// Load the collected "load on startup" servlets
for (ArrayList<Wrapper> list : map.values()) {
for (Wrapper wrapper : list) {
try {
//关键行代码
wrapper.load();
} catch (ServletException e) {
getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
// NOTE: load errors (including a servlet that throws
// UnavailableException from the init() method) are NOT
// fatal to application startup
// unless failCtxIfServletStartFails="true" is specified
if(getComputedFailCtxIfServletStartFails()) {
return false;
}
}
}
}
return true;
}
第二步:在第一步中,StandardContext类会把启动spring的工作委托给StandardWrapper类。当前方法也并没有直接创建spring容器,而是通过三次方法调用最终将启动spring容器的工作委托给DispatchServlet类。文章源自JAVA秀-https://www.javaxiu.com/19947.html
@Override
public synchronized void load() throws ServletException {
//在前面的第一步中,我们说wrapper对象的本质就是dispatchServlet对象的奥妙就在这里了。在启动spring容器这件工作上面,StandardWrapper类自己本身没有亲自做,而是委托给了DispatchServlet类。
instance = loadServlet();
if (!instanceInitialized) {
initServlet(instance);
}
if (isJspServlet) {
StringBuilder oname = new StringBuilder(getDomain());
oname.append(":type=JspMonitor");
oname.append(getWebModuleKeyProperties());
oname.append(",name=");
oname.append(getName());
oname.append(getJ2EEKeyProperties());
try {
jspMonitorON = new ObjectName(oname.toString());
Registry.getRegistry(null, null).registerComponent(instance, jspMonitorON, null);
} catch (Exception ex) {
log.warn(sm.getString("standardWrapper.jspMonitorError", instance));
}
}
}
//loadServlet方法本身并不启动spring容器,而是进一步委托给initServlet方法处理
public synchronized Servlet loadServlet() throws ServletException {
// Nothing to do if we already have an instance or an instance pool
if (!singleThreadModel && (instance != null))
return instance;
PrintStream out = System.out;
if (swallowOutput) {
SystemLogHandler.startCapture();
}
Servlet servlet;
try {
long t1=System.currentTimeMillis();
// Complain if no servlet class has been specified
if (servletClass == null) {
unavailable(null);
throw new ServletException
(sm.getString("standardWrapper.notClass", getName()));
}
InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
try {
//创建一个DispatchServlet类的实例对象,也就是在这个实例对象中,启动我们的spring容器
servlet = (Servlet) instanceManager.newInstance(servletClass);
} catch (ClassCastException e) {
unavailable(null);
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.notServlet", servletClass), e);
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
unavailable(null);
// Added extra log statement for Bugzilla 36630:
// https://bz.apache.org/bugzilla/show_bug.cgi?id=36630
if(log.isDebugEnabled()) {
log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
}
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.instantiate", servletClass), e);
}
if (multipartConfigElement == null) {
MultipartConfig annotation =
servlet.getClass().getAnnotation(MultipartConfig.class);
if (annotation != null) {
multipartConfigElement =
new MultipartConfigElement(annotation);
}
}
// Special handling for ContainerServlet instances
// Note: The InstanceManager checks if the application is permitted
// to load ContainerServlets
if (servlet instanceof ContainerServlet) {
((ContainerServlet) servlet).setWrapper(this);
}
classLoadTime=(int) (System.currentTimeMillis() -t1);
if (servlet instanceof SingleThreadModel) {
if (instancePool == null) {
instancePool = new Stack<>();
}
singleThreadModel = true;
}
//把启动spring容器的工作进一步委托给StandardWrapper类的initServlet方法处理
initServlet(servlet);
fireContainerEvent("load", this);
loadTime=System.currentTimeMillis() -t1;
} finally {
if (swallowOutput) {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
if (getServletContext() != null) {
getServletContext().log(log);
} else {
out.println(log);
}
}
}
}
return servlet;
}
private synchronized void initServlet(Servlet servlet)
throws ServletException {
if (instanceInitialized && !singleThreadModel) return;
// Call the initialization method of this servlet
try {
if( Globals.IS_SECURITY_ENABLED) {
boolean success = false;
try {
Object[] args = new Object[] { facade };
SecurityUtil.doAsPrivilege("init",
servlet,
classType,
args);
success = true;
} finally {
if (!success) {
// destroy() will not be called, thus clear the reference now
SecurityUtil.remove(servlet);
}
}
} else {
//关键行代码,也就是在这里将启动spring容器的工作委托给DispatchServlet类
servlet.init(facade);
}
instanceInitialized = true;
} catch (UnavailableException f) {
unavailable(f);
throw f;
} catch (ServletException f) {
// If the servlet wanted to be unavailable it would have
// said so, so do not call unavailable(null).
throw f;
} catch (Throwable f) {
ExceptionUtils.handleThrowable(f);
getServletContext().log(sm.getString("standardWrapper.initException", getName()), f);
// If the servlet wanted to be unavailable it would have
// said so, so do not call unavailable(null).
throw new ServletException
(sm.getString("standardWrapper.initException", getName()), f);
}
}
第三步:经过第一步和第二步的处理,终于来到了DispatchServlet类(因为有继承关系,所以虽然有些方法并不是在DispatchServlet类,而是在DispatchServlet类的父类,所以我们仍然认为这些方法就是DispatchServlet类的方法),我们依次追踪一下spring容器的启动过程。文章源自JAVA秀-https://www.javaxiu.com/19947.html
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
//本方法也不直接启动spring容器,而是进一步将启动spring容器的工作委托给initServletBean方法
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
// Let subclasses do whatever initialization they like.
// 关键代码行
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
//本方法也不直接启动spring容器,而是进一步将启动spring容器的工作委托给initWebApplicationContext方法。从这个方法命名,我们就知道离真正的创建一个applicationcontext对象越来越近了。
@Override
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 {
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");
}
}
//本方法就是真正的开始我们的spring容器的启动的核心方法。
protected WebApplicationContext initWebApplicationContext() {
//在此处就会完成applicationcontext对象的创建和初始化的工作
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);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
// 从这里创建一个applicationcontext对象
wac = createWebApplicationContext(rootContext);
}
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);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
第四步:创建applicationcontext对象以及初始化applicationcontext对象文章源自JAVA秀-https://www.javaxiu.com/19947.html
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
//本方法主要是创建spring容器对象也就是ConfigurableWebApplicationContext对象
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
//获取当前需要创建的spring容器的实际类,也就是XmlWebApplicationContext类,这个就是真正实例化的spring容器的类。
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
//使用BeanUtils工具类的instantiateClass方法创建一个XmlWebApplicationContext实例,也就是spring容器。
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
//设置当前服务环境信息
wac.setEnvironment(getEnvironment());
//spring容器启动有两种方式:一种比较简单,就是单容器启动方式;一种稍微复杂,就是父子容器启动方式。父子容器的意思是会创建两个applicationcontext对象,但是这两个对象一起构成了一个整体的spring容器(形态就是子容器对象持有父容器对象,两个对象作为一个整体代表一个spring容器)。因为本章节讲解的时单容器启动,这里不多做讲解,这里的的parent父容器也就是为空。
wac.setParent(parent);
//此处就是在web.xml中servlet配置的contextConfigLocation属性配置的内容,也就是spring配置文件所在位置
wac.setConfigLocation(getContextConfigLocation());
//前面的几步只是初步的设置了ConfigurableWebApplicationContext实例的基本信息,本行代码调用将进一步的初始化ConfigurableWebApplicationContext实例
configureAndRefreshWebApplicationContext(wac);
return wac;
}
//本方法主要用于使得Spring容器对象持有有ServletContext对象,这样我们得spring容器也就具备了代表一个web后台服务得能力。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
}
}
//我们前面讲过一个web.xml文件最终被解析成为一个ServletContext对象,这个对象也就可以理解为一个web后台服务的实例对象。而我们的spring容器启动过程中,会将这个web后台服务实例对象添加到自己的属性中。也就是说基于spring开发的后台服务,我们可以认为spring容器是我们的后台服务实例对象,因为他更能代表这个后台服务。
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
//到这里就回到我们熟悉的刷新spring容器环境的方法了。
wac.refresh();
}
//applicationContext版本spring容器的核心初始化模板方法。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
到这一步方法执行完,我们的spring容器也就算是真正的启动成功了。文章源自JAVA秀-https://www.javaxiu.com/19947.html
3. springmvc启动过程总结
基于spring开发的后台服务在启动过程中,总体上分为两个阶段:
第一阶段:解析web.xml文件为一个ServletContext对象,启动web后台服务;
第二阶段:解析applicationContext.xml文件为一个applicationContext容器对象,启动spring容器。文章源自JAVA秀-https://www.javaxiu.com/19947.html
那么下面我们把上面的重要步骤在这里总结一下:文章源自JAVA秀-https://www.javaxiu.com/19947.html
1.首先tomcat的StandardContext实例中的startInternal核心模板方法中,调用fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null)代码,完成web.xml文件的解析工作,完成后也就完成了web后台服务的启动。然后在startInternal核心模板方法的结尾部分,调用loadOnStartup方法开始spring容器的启动工作,在loadOnStartup方法中也并不会直接启动spring容器,而是将启动spring容器的工作委托给wrapper.load();这行代码,实际上就是委托给StandardWrapper类。文章源自JAVA秀-https://www.javaxiu.com/19947.html
2.其次在StandardWrapper中首先调用loadServlet方法,在这种方法中创建一个DispatchServlet实例。创建实例完成后再调用initServlet方法进行初始化。在initServlet方法中,调用servlet.init(facade);行代码进行初始化。在这里也就意味着实际上StandardWrapper类并不亲自做spring容器的启动工作,而是再次委托给了FrameworkServlet类。文章源自JAVA秀-https://www.javaxiu.com/19947.html
3.最后在FrameworkServlet中依次调用下面的几个方法:
FrameworkServlet(GenericServlet) -> init
FrameworkServlet(HttpServletBean) -> init
FrameworkServlet -> initServletBean
FrameworkServlet -> initWebApplicationContext
FrameworkServlet -> createWebApplicationContext
FrameworkServlet -> createWebApplicationContext
FrameworkServlet -> (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
完成上述的几个步骤的嵌套调用,尤其是红色字体部分的代码,也就意味着我们的spring容器就真正的启动成功了。文章源自JAVA秀-https://www.javaxiu.com/19947.html
文章源自JAVA秀-https://www.javaxiu.com/19947.html
更多知识请关注公众号文章源自JAVA秀-https://www.javaxiu.com/19947.html

评论