Spring web 启动过程分析

Spring在设计时主要针对接口进行开发(顶级类均为接口BeanDefinition、Resource、BeanFactory等),为满足各种场景的需要,其源码中包含了各种实现。想要了解整个源码实在是是一件很困难且耗费时间的一件事。因此本文只涉及到了常用配置的实现(Spring+SpringMVC),且在启动时涉及到的部分。SpringMVC的启动以后再谈。

Step 1:容器加载并解析web.xml

  1. 当启动一个WEB项目时,容器(JBOSS/Tomcat/Weblogic等)首先会加载并解析项目中的web.xml文件。
  2. 容器会创建一个应用上下文(ServletContext)。顾名思义,ServletContxt就是为了方便在整个Servlet应用中共享数据。例如,web.xml中<context-param></context-param>标签就是为了应用中动态获取对应属性。Spring配置文件的位置也是通过该标签设置的。
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-context.xml</param-value>
  </context-param>
  1. 容器实例化web.xml中<listener></listener>配置的类(即ContextLoaderListener),实例化后,容器会通过监听机制执行其**contextInitialized()**方法正式揭开Spring的启动流程
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

Step 2:初始化Spring上下文WebApplicationContext

Spring的启动开始于容器回调ContextLoaderListener的**contextInitialized()**方法。Spring启动时默认的WebApplicationContext实现类是XmlWebApplicationContext。

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        // 判断当前ServletContext是否初始化过WebApplicationContext
        // key="org.springframework.web.context.WebApplicationContext.ROOT"
		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!");
		}
		long startTime = System.currentTimeMillis();

		try {
			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			if (this.context == null) {
                // 若当前上下文未初始化WebApplicationContext,则进行初始化,此处实例化的实现类是XmlWebApplicationContext
                // 该过程仅仅是类的实例化,未做任何其他处理,实现类为XmlWebApplicationContext。
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				// WebApplicationContext实例化后处于未激活状态,即cwac.isActive()=false
                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.
                        // Spring应用上下文,此处返回null
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
                    // 真正的IOC容器初始化入口,即调用refresh()的方法,了解SpringIOC容器的应该知道refresh()的重要性
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
            // 在Servlet上下文中添加属性,表示该上下文已经初始化过WebApplicationContext,与该方法入口处的校验对应
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
                // 此处的目的是 方便Spring其他模块在依赖Spring时 能够快速获取到对应的Spring上下文WebApplicationContext
				currentContextPerThread.put(ccl, this.context);
			}

			if (logger.isDebugEnabled()) {
				logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
						WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
			}
			if (logger.isInfoEnabled()) {
				long elapsedTime = System.currentTimeMillis() - startTime;
				logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
			}

			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;
		}
	}

Step 3: 配置Spring上下文WebApplicationContext

配置内容主要包含配置上下文ID、确定Spring配置文件位置、关联Spring上下文和Servlet上下文等。WebApplicationContext作为Spring上下文,同时其实现了BeanFactory接口,即其可以作为IOC容器使用,但其实现方式却是通过内部持有一个BeanFactory的实现类来实现的。

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {**DefaultListableBeanFactory**// 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) {
			   // 若配置中指定了id,则设置为配置中的id
				wac.setId(idParam);
			}
			else {
				// 生成上下文的Id, 以区分不同的IOC容器(Spring和SpringMVC容器可以同时存在)
				// id=org.springframework.web.context.WebApplicationContext:
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(sc.getContextPath()));
			}
		}
		// 将Spring应用上下文和Servlet上下文进行关联
		wac.setServletContext(sc);
		// CONFIG_LOCATION_PARAM=contextConfigLocation即web.xml中的Spring配置
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			// 指定Spring的配置文件,默认为/WEB-INF/applicationContext.xml
			wac.setConfigLocation(configLocationParam);
		}
		
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}
		// 自定义化Spring上下文,通过实现ApplicationContextInitializer来实现
		customizeContext(sc, wac);
		// IOC容器的启动入口
		wac.refresh();
	}

Step 4: IOC容器初始化

此处IO容器指的是Spring上下文内部持有的BeanFactory的实现类的初始化。默认实现为DefaultListableBeanFactory

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        // 2.3.1.设置启动时间 激活状态 初始化PropertySources等
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        // 2.3.2.刷新自身持有的BeanFactory
        // 此过程包含4步:
        // 1.存在旧BeanFactory时,则销毁旧BeanFactory。
        // 2.实例化新的BeanFactory,默认实现类为DefaultListableBeanFactory
        // 3.初始化新BeanFactory,配置自定义属性等
        // 4.向容器注册BeanDefinition即Spring Bean。此过程会读取web.xml中的contextConfigLocation配置的文件,将依据其中的配置解析成BeanDefinition
        //  步骤4中只加载定义BeanDefinition,未实例化Bean
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        // 配置BeanFactory的标准特征、类加载器和默认的后置处理器
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            // 配置环境Environment和初始化BeanScop等 
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            // 执行配置的BeanFactory的后置处理器(执行必须发生在单例Bean被使用前)
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            // 注册Bean的后置处理器(区别于BeanFactory的后置处理器)
            registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
            // 初始化消息源
            initMessageSource();

            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            // 初始化themeSource
            onRefresh();

            // Check for listener beans and register them.
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            // 实例化单例模式的Bean,非单例模式在getBean()时实例化
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            // IOC容器启动完成后,调用生命周期处理器并发布事件通知(实现ApplicationListener子类会被回调)等
            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.
            // 异常时,销毁已实例化的Bean.
            destroyBeans();

            // Reset 'active' flag.
            // 设置当前Spring上下文的状态为false.
            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...
            // 清空在整个Spring上下文加载过程中的缓存,主要是指反射。
            resetCommonCaches();
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值