准备来一波SpringMVC拦截器原理的,但是发现,Spring源码流程应该是个前置内容,要先解决。
Spring初始化
通过xml方式使用Spring提供的IOC容器功能,首先需要在web.xml引入这段配置(还有Java配置的方式,不需要写配置文件,通过注解,这里先不作讨论)
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
配置org.springframework.web.context.ContextLoaderListener这个监听器。ContextLoaderListener会在Servlet容器启动时执行
debug通过调用栈可以看到,(我使用的是jetty,tomcat类似) servlet容器启动的过程中会调用ContextLoaderListener的contextInitailized方法。
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
contextInitialized是ServletContextListener接口的方法,如果自己做一些其他的初始化工作,也可以实现ServletContextListener接口,然后把实现的类注册到web.xml中,这样在servlet容器启动时就会触发初始化。
可以模仿ContextLoaderListener的实现如下:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener
新写一个监听器,继承自己的逻辑ContextLoader ,并实现ServletContextListener 接口。
configureAndRefreshWebApplicationContext方法中,主要做了两件事:
1、Servlet容器配置转化为Spring容器配置
首先将Servlet Context(Servlet容器加载的上下文内容,比如解析的web.xml中的内容)的内容传递给ConfigurableWebApplicationContext的实现。具体的实现取决于你用的是哪个,不必纠结于此。
ConfigurableWebApplicationContext的实现:
我上面的例子中,contextConfigLocation——Spring上下文的配置文件就是在这个时候设置到XmlWebApplicationContext中的。
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
Spring上下文运行的系统环境配置也会在这时初始化。
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
2、Spring上下文的初始化刷新
wac.refresh();
AbstractApplicationContext.java
@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();
}
}
}
refresh方法是Spring加载Bean的核心方法,定义了加载Bean的流程。
Bean是Spring的核心概念,把Spring作为IOC容器使用,就是通过把要用的普通Java对象转换为Spring中的Bean对象来使用。
进入refresh方法,首先对代码块进行加锁
synchronized (this.startupShutdownMonitor) {
(1)为什么要加锁?
避免多线程同时执行这个refresh流程
(2)加锁的部分代码块是整个refresh内部方法,为什么不在refresh方法上加锁,为什么使用同步代码块?
首先,这个可以看startupShutdownMonitor的注释
/** Synchronization monitor for the "refresh" and "destroy". */
private final Object startupShutdownMonitor = new Object();
startupShutdownMonitor是刷新和销毁共用的,保证刷新和销毁操作不能同时进行,避免冲突错误。
按照阅读源码的流程,先把Spring销毁的逻辑放一边,后面分析。
另外一种解释是:涉及到锁的粒度的问题(缩小同步的范围),粒度越小,性能越好,但这里体现好像不明显啊,Spring这里的设计应该是出于上面一种情况的考虑,即避免同时刷新和销毁Bean。
下一篇开始Spring启动流程(二)之Spring加载Bean的流程
参考文章:
“五月的仓颉”大佬,在这篇文章里,对关键类画了继承层次图和数据结构图,我暂且不画了,可以看看他画的,跟着分析完整个Spring源码流程后,后面再考虑有没有必要画。