了解spring 的原理,一般从 官方提供的运行机制的图,然后根据代码 进行源码分析,就可以了,这里先从spring mvc 启动开始分析。先来看看web.xml 的配置吧。这里基于spring 3.2
<!-- 启动监听 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/META-INF/spring/applicationContext*.xml</param-value>
</context-param>
<!-- app 请求 -->
<servlet>
<servlet-name>SpringMVCServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/config/app-config.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVCServlet</servlet-name>
<url-pattern>/c/*</url-pattern>
</servlet-mapping>
上面主要有两个东西:
1.ContextLoaderListener 监听器:
2.DispatcherServlet 控制器:
在启动的时候,如果观察启动日志 就能发现,1.首先启动,2.然后是DispatcherServlet ,我们来看看这两个东西 在初始化的时候干了什么?
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
private ContextLoader contextLoader;
// 这是实现了ServletContextListener 监听,一个初始化方法 和一个销毁的方法
public void contextInitialized(ServletContextEvent event) {
// 这里已经废弃,不用了,一直未null
this.contextLoader = createContextLoader();
if (this.contextLoader == null) {
this.contextLoader = this;
}
// 主要看看初始化,他到底做了什么,进入源码分析
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
public void contextDestroyed(ServletContextEvent event) {
// ....略
}
}
进入ContextLoader,找到initWebApplicationContext 方法:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// 这里只贴方法
// 先判断 是否已经加载过了
if (servletContext.getAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
// ...
}
// 创建 WebApplicationContext 对象,详细的看 create 方法
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
// 这一步就是加载你xml 配置<context-param> 里面的内容,默认是application...
// 看后面他是如何实现的。
if (this.context instanceof ConfigurableWebApplicationContext) {
configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, servletContext);
}
}
}
createWebApplicationContext 方法,负责创建 WebApplicationContext 上下文对象
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// 这里拿到XmlWebApplicationContext 的对象
Class<?> contextClass = determineContextClass(sc);
// 这里转成他的扩展接口对象ConfigurableWebApplicationContext ,多加了一些获得配置信息的方法
// 至于这里一步的原因我不是很明白,大概是为了一个兼容的的问题,以前的设计这里会出错。
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
return wac;
}
// 这个类默认创建指定处理xml/bean 的类对象
protected Class<?> determineContextClass(ServletContext servletContext) {
// 这里表示你在web.xml 可以手动自己写一个需要处理的加载类对象,
// 默认是XmlWebApplicationContext
// 如何你需要额外的加载,一般是继承XmlWebApplicationContext 行了
String contextClassName = servletContext.getInitParameter(
CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
// 这里是默认是获得当前线程的加载器
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
}
else {
// 这里是通过properties 进行加载,defaultStrategies 是一个静态类
// 默认获得当前目录下ContextLoader.properties里面的XmlWebApplicationContext 类路径
contextClassName = defaultStrategies.getProperty(
WebApplicationContext.class.getName());
try {
// 这里同样通过类路径装载类,加载器是ContextLoader
return ClassUtils.forName(contextClassName,
ContextLoader.class.getClassLoader());
}
}
}
这里主要负责加载xml 文件
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
// 这里官方解释 和EJB 有关系,具体请看API。类似我们这种纯web 中,这里一般都是空的
ApplicationContext parent = loadParentContext(sc);
// 这里就是加载我们配置的contextConfigLocation 的参数,这也是为什么需要这个名字的原因
// 同时里面所有对应的xml文件都会找到,对应的参数值是:
// classpath*:com/home/config/spring/spring-*.xml
String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (initParameter != null) {
wac.setConfigLocation(initParameter);
}
// 这里是定制自己的context,也就是你可以扩展,这里我是没扩展,所以没用,不解释
customizeContext(sc, wac);
// 这里方法,在子类 AbstractApplicationContext 里面
// 它负责加载xml properties 以及database schema ,看看他是如何操作的!
wac.refresh();
}
refresh()方法
public void refresh() throws BeansException, IllegalStateException {
// 这里会更新当前操作的时间,以及操作状态,表示现在可以操作了。
prepareRefresh();
// 这里是实例化bean工厂,准备开始了,详细看下面
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
}
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 初始化工厂,并获得配置的xml 指向的地址
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
return beanFactory;
}
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 创建新工厂
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
// 工厂加载对应的xml bean,接着看下面
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
}
}
这是XmlWebApplicationContext 里面的
// 加载初始化 设置的bean 信息
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 设置一些资源信息,为加载做准备
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 执行加载
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
// 具体的加载步骤,通过XmlBeanDefinitionReader 进行操作
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
// 这里会获得classpath*:....*.xml 等你配置的地址
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
// 这里根据地址得到相对文件并进行加载
// 主要通过PathMatchingResourcePatternResolver 解析获
// 相对路径
// 然后通过 XmlBeanDefinitionReader里面各种方法,
// 最后 doLoadBeanDefinitions 进行处理xml 文件属性
reader.loadBeanDefinitions(configLocation);
}
}
}
到现在为止,我们配置的xml bean文件已经加载完了,下面看看 bean 里面相关联的properties 文件如何加载的。继续回到AbstractApplicationContext refresh()方法:
public void refresh() {
prepareRefresh();
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 这是准备bean 工厂,初始化一些工具类
prepareBeanFactory(beanFactory);
postProcessBeanFactory(beanFactory);
invokeBeanFactoryPostProcessors(beanFactory);
// 注册 加载的bean,
registerBeanPostProcessors(beanFactory);
// 初始化消息
initMessageSource();
// 初始化事件列表,或者监听,对加载的bean 的一些添加移除等操作进行管理
initApplicationEventMulticaster();
// 扩展的时候需要
onRefresh();
// 注册监听,并通过name 把所有的bean 放入进行管理
registerListeners();
// 把context 剩下的延迟加载的bean 加载完
finishBeanFactoryInitialization(beanFactory);
// 发布时间,对一些生命周期进行监控。 这个里面的方法 是主体方法,后面再分开研究吧。
finishRefresh();
}
小结: 整个初始化步骤
1.ContextLoaderListener 启动,开始调用初始化方法
2.ContextLoader 具体执行初始化任务,主要创建上下文对象 WebApplicationContext
2.1 读取一些配置配置文件路径,设置一些初始化参数
3 XmlWebApplicationContext xml 文件进行解析,封装成集合信息
4.AbstractApplicationContext 把刚那些集合对象信息,进行基本的初始化 监听 发布等等操作,最后
我们的就能从上下文信息,里面取得我们想要的的信息了。
整个过程,只写了个大概,辅助了解,深入需要自己去看官方文档和源码,建议设计图结合看。