声明
1. 建议先阅读《Spring 源码分析专题 -- 阅读指引》
2. 强烈建议阅读过程中要参照调用过程图, 每篇都有其对应的调用过程图
前言
关于 IoC 容器启动的内容很多, 我将分上中下三篇讲解, 其中上篇相对简单, 中篇最为复杂, 请大家耐心阅读.
上篇 - 主要是相关基础说明和找到分析入口
中篇 - 讲解定位, 加载, 注册的过程 (实例化在依赖注入的章节再讲)
下篇 - 细节补充
调用过程图
由于篇幅问题, 此处我只放个缩略图, 高清大图请点击链接 IoC 容器启动调用过程图. jpg
请务必一边对照图片一边阅读文章.
先放结论
此处先放结论, 大家稍微记一记, 后边将展开详解
Spring 的启动流程主要是定位 -> 加载 -> 注册 -> 实例化
定位 - 获取配置文件路径
加载 - 把配置文件读取成 BeanDefinition
注册 - 存储 BeanDefinition
实例化 - 根据 BeanDefinition 创建实例
所谓的 IoC 容器其实就是 BeanFactory , BeanFactory 是一个接口, 有很多对应的实现类
IoC 容器的关键入口方法是 refresh()
web 应用中使用的容器是 XmlWebApplicationContext , 其类图如下, 可以看出最终是一个实现了 BeanFactory 的类
IoC 容器源码的入口
我们知道 Spring 框架不仅仅是面向 Web 应用, 所以 Spring 中对应不同场景有许多 IoC 容器的实现类, 其中有简单的也有复杂的, 在此我们跳过简单容器的讲解, 直接以我们最熟悉, 也是最感兴趣的 Java Web 项目下手, 寻找其对应的 IoC 容器实现类, 同时一口气寻找到 IoC 容器的关键入口方法 refresh() .
1. 寻找 IoC 容器实现类
以下是我们熟知的 SpringMVC 项目中 Web.xml 的基础配置, 其关键是要配置一个 ContextLoaderListener 和一个 DispatcherServlet
- <Web-App>
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath:spring.xml</param-value>
- </context-param>
- <!-- ContextLoaderListener -->
- <listener>
- <listener-class>org.springframework.Web.context.ContextLoaderListener</listener-class>
- </listener>
- <!-- DispatcherServlet -->
- <servlet>
- <description>spring mvc servlet</description>
- <servlet-name>springMvc</servlet-name>
- <servlet-class>org.springframework.Web.servlet.DispatcherServlet</servlet-class>
- <init-param>
- <description>spring mvc</description>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath:spring-mvc.xml</param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- </servlet>
- <servlet-mapping>
- <servlet-name>springMvc</servlet-name>
- <url-pattern>*.do</url-pattern>
- </servlet-mapping>
- </Web-App>
我们知道在 Java Web 容器中相关组件的启动顺序是 ServletContext -> listener -> filter -> servlet , listener 是优于 servlet 启动的, 所以我们先看一看 ContextLoaderListener 的内容
- public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
- public ContextLoaderListener() {
- }
- public ContextLoaderListener(WebApplicationContext context) {
- super(context);
- }
- public void contextInitialized(ServletContextEvent event) {
- this.initWebApplicationContext(event.getServletContext());
- }
- public void contextDestroyed(ServletContextEvent event) {
- this.closeWebApplicationContext(event.getServletContext());
- ContextCleanupListener.cleanupAttributes(event.getServletContext());
- }
- }
根据 Java Web 容器的规范可知, 当 Listener 启动时会调用 contextInitialized 方法, 而 ContextLoaderListener 中该方法的内容是继续调用 initWebApplicationContext 方法, 于是我们再跟踪 initWebApplicationContext
- ( ContextLoaderListener 是 ContextLoader 的子类, 所以其实是调用了父类的 initWebApplicationContext 方法)
- public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
- if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
- 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!");
- } else {
- Log logger = LogFactory.getLog(ContextLoader.class);
- servletContext.log("Initializing Spring root WebApplicationContext");
- if (logger.isInfoEnabled()) {
- logger.info("Root WebApplicationContext: initialization started");
- }
- long startTime = System.currentTimeMillis();
- try {
- if (this.context == null) {
- this.context = this.createWebApplicationContext(servletContext);
- }
- .
- .
- .
- }
此处我们关心的是 createWebApplicationContext 方法
- protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
- /** [note-by-leapmie] determineContextClass 方法中获取 contextClass **/
- Class<?> contextClass = determineContextClass(sc);
- if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
- throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
- "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
- }
- /** [note-by-leapmie] 根据 contextClass 返回实例 */
- return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
- }
从代码可知, 方法中的逻辑主要是调用 determineContextClass 获取 contextClass , 然后根据 contextClass 创建 IoC 容器实例. 所以, contextClass 的值将是关键.
- protected Class<?> determineContextClass(ServletContext servletContext) {
- String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
- if (contextClassName != null) {
- .
- .
- .
- }
- else {
- /**
- * [note-by-leapmie]
- * defaultStrategies 的值是在本类中的 static 方法中注入的
- * 即该类加载过程中 defaultStrategies 已经被赋值
- * 本类的开始部分有 static 代码块
- * **/
- contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
- try {
- return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
- }
- catch (ClassNotFoundException ex) {
- throw new ApplicationContextException(
- "Failed to load default context class [" + contextClassName + "]", ex);
- }
- }
- }
可以看到, contextClassName 是从 defaultStrategies 中获取的, 而关于 defaultStrategies 的赋值需要追溯到 ContextLoader 类中的静态代码块
- static {
- try {
- /**
- * [note-by-leapmie]
- * DEFAULT_STRATEGIES_PATH 的值是 ContextLoader.properties
- */
- ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
- defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
- }
- catch (IOException ex) {
- throw new IllegalStateException("Could not load'ContextLoader.properties':" + ex.getMessage());
- }
- }
defaultStrategies 是从 resource 中获取的参数, 而 resource 又是从 DEFAULT_STRATEGIES_PATH 中获取, 查看可知 DEFAULT_STRATEGIES_PATH 的值是 ContextLoader.properties , 通过全局查找到 ContextLoader.properties 文件, 其中内容如下
org.springframework.Web.context.WebApplicationContext=org.springframework.Web.context.support.XmlWebApplicationContext
由此可知, SpringMVC 项目中使用到的 IoC 容器类型是 XmlWebApplicationContext.
2. 寻找关键入口方法 refresh()
我们回到 ContextLoader 的 initWebApplicationContext 方法, 前边我们说到调用 createWebApplicationContext 方法创建容器, 容器创建后我们关注的下一个方法是 configureAndRefreshWebApplicationContext
- public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
- .
- .
- .
- try {
- // Store context in local instance variable, to guarantee that
- // it is available on ServletContext shutdown.
- if (this.context == null) {
- /** [note-by-leapmie] 获取 SpringIOC 容器类型 **/
- this.context = createWebApplicationContext(servletContext);
- }
- if (this.context instanceof ConfigurableWebApplicationContext) {
- ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
- 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 ->
- // determine parent for root Web application context, if any.
- ApplicationContext parent = loadParentContext(servletContext);
- cwac.setParent(parent);
- }
- /** [note-by-leapmie] 配置和刷新容器 **/
- configureAndRefreshWebApplicationContext(cwac, servletContext);
- }
- }
- .
- .
- .
- }
configureAndRefreshWebApplicationContext 的代码如下
- protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
- 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
- String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
- if (idParam != null) {
- wac.setId(idParam);
- }
- else {
- // Generate default id...
- wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
- ObjectUtils.getDisplayString(sc.getContextPath()));
- }
- }
- wac.setServletContext(sc);
- String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
- if (configLocationParam != null) {
- wac.setConfigLocation(configLocationParam);
- }
- // 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(sc, null);
- }
- customizeContext(sc, wac);
- /** [note-by-leapmie] 调用容器的 refresh() 方法, 此处 wac 对应的类是 XmlWebApplicationContext **/
- wac.refresh();
- }
在这里我们要关注的是最后一行 wac.refresh() , 意思是调用容器的 refresh() 方法, 此处我们的容器是 XmlWebApplicationContext, 对应的 refresh() 在其父类 AbstractApplicationContext
- @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.
- /**
- * obtainFreshBeanFactory 方法中会调用 loadBeanDefinition 方法, 用于加载 bean 的定义
- */
- 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.
- /** 初始化所有非 lazy-init 的 bean **/
- 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() , 我们看一下在调用过程图中我们所处的位置
refresh 方法是 Spring IoC 容器启动过程的核心方法, 方法中按顺序调用了好几个命名清晰的方法, 其对应的都是 IoC 容器启动过程的关键步骤, 更多的细节我们将在下一节继续讲解.
话痨一下
大家可能会觉得, 在源码分析过程中一个方法中调用了很多方法, 例如先执行方法 a() , 再执行方法 b() , 为什么我们直接看方法 b() 而跳过了方法 a() ?
在这里我想说的是, Spring 的源码量很庞大, 如果每个细节都去了解可能一年过去了都看不完, 我们应该先关注大流程, 其他的细枝末节可以在了解了大流程后再慢慢深入了解.
至于为什么是看方法 b() 而跳过方法 a() , 这些都是前人总结的经验与心血, 在学习过程中我也是跟着别人的步伐在源码中探索, 中间有些缺失的路线我也花费大量时间去踩坑, 最后绘制了每一份调用过程图. 在本专题中我能确保的是, 只要跟着我的步伐, 你们不会在源码分析的路上迷路.
本文首发地址: https://blog.leapmie.com/archives/390/
[目录]
[上一篇]Spring 源码分析专题 -- 阅读指引
[下一篇]Spring 源码分析专题 -- IoC 容器启动过程 (中篇) https://blog.leapmie.com/archives/394/
来源: https://www.cnblogs.com/leap/p/10036205.html