文档章节

springMVC启动过程源码解析(一)

DJZhu
 DJZhu
发布于 2017/06/19 16:09
字数 2830
阅读 29
收藏 1

之前的文章讨论过,一个简单的DefaultListableBeanFactory是如何启动的,这次再来看看SpringMVC项目是怎样启动的。

我们知道SpringMVC项目底层也是通过BeanFactory在维护众多的Bean,不同的地方在在于SpringMVC的BeanFactory是放在一个web容器中运行的,它的启动也由容器来控制(不同于DefaultListableBeanFactory是在一个main方法中调用)。另外,SpringMVC还涉及到一个复杂的Environment需要我们关心。

web.xml是所有web容器跟容器内项目发生联系的首要内容,通常,一个SpringMVC项目的web.xml看起来是这样的:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         metadata-complete="true">

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath*:application-context.xml
        </param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>springServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <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>springServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

相信有过开发经验的朋友对这样的配置已经是再熟悉不过了,每一个配置的含义这里就不赘述,直接来看看web容器是按什么顺序解析此配置的。

在javax.sevlet包中,有所谓的EventListener,这是为了让开发者可以对servlet中的一些事件作出处理的一套设计。

Event,即事件,可以是web程序在运行过程中的启动、销毁、用户请求、打开/关闭一个会话或者是对web参数(attribute)的修改等等。 Listener,即监听器。是servlet提供的可以对上述事件作出处理的接口。

比如我们常用的ContextLoaderListener,他的继承关系是这样的: 输入图片说明 它的作用是监听servlet的启动,并作出具体的初始化动作。 这里可以简单分析下设计者对此Listener的设计理念:实现ServletContextListener是为了监听,此接口包含两个方法:

    /**
     * Receives notification that the web application initialization
     * process is starting.
     * All ServletContextListeners are notified of context
     * initialization before any filters or servlets in the web
     * application are initialized.
     *
     * @param sce the ServletContextEvent containing the ServletContext that is being initialized
     */
    public void contextInitialized(ServletContextEvent sce);

    /**
     * Receives notification that the ServletContext is about to be
     * shut down.
     * All servlets and filters will have been destroyed before any
     * ServletContextListeners are notified of context
     * destruction.
     *
     * @param sce the ServletContextEvent containing the ServletContext that is being destroyed
     */
    public void contextDestroyed(ServletContextEvent sce);

继承ContextLoader是为了实际处理启动的动作。在此类中,指定了web.xml中的一些启动参数名:比如:

	/**
	 * Name of servlet context parameter (i.e., {@value}) that can specify the
	 * config location for the root context, falling back to the implementation's
	 * default otherwise.
	 * @see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION
	 */
	public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";

	/**
	 * Config param for the root WebApplicationContext implementation class to use: {@value}
	 * @see #determineContextClass(ServletContext)
	 */
	public static final String CONTEXT_CLASS_PARAM = "contextClass";

spring-mvc就是利用此listener,在启动servlet的时候初始化一个BeanFactory。来看看具体的流程。

  1. 调用public void contextInitialized(ServletContextEvent event)开始执行启动程序
  2. 调用ContextLoader的initedWebApplicationContext(ServletContextEvent event)方法实际执行启动事务
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        //初始化context完成之后,会把构造的context对象放到servletContext的attribute里面,如果发现此时已有context,表示重复初始化,抛异常
        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!");
        }

        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 {
            // Store context in local instance variable, to guarantee that
            // it is available on ServletContext shutdown.
            if (this.context == null) {
                //实际执行创建webApplicationContext
                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);
                    }
                    configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
           //初始化完成,把webApplicationContext写进servletContext
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            if (ccl == ContextLoader.class.getClassLoader()) {
                this.currentContext = this.context;
            }
            else if (ccl != null) {
                this.currentContextPerThread.put(ccl, this.context);
            }
            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;
        }
    }

在这里涉及到一个比较重要的概念,两个context。一个是servletContext,一个是webApplicationContext。前者是web容器里的概念,在javax.servlet包中被定义,跟spring无关,容器在启动一个web应用的时候,会为之创建一个context,它代表了整个web项目,里面的所有servlet共享这个context。而WebApplicationContext是spring中的概念,它在org.springframework.web.context被定义,它代表了一个spring项目的上下文。他们的关系是:后者只是前者中的一个attribute。

此方法中,关键是调用createWebApplicationContext(servletContext)方法,它实现了从servletContext(javaWeb概念)的配置构造一个WebApplicationContext实例(Spring概念)的过程,默认情况下它构造的是一个XmlWebApplicationContext,你也可以通过在web.xml中配置contextClass参数来指定构造一个自定义的Context(必须继承自ConfigurableWebApplicationContext接口,原因下面再说)。

下面是简化的WebApplicationContext的继承关系,及主要节点的所在包,通过包也可以很清晰的发现每次继承所带给WebApplicationContext的新特性。BeanFactory和ListableBeanFacotry在beans.facotry包中,代表他们带来的只是最基础的bean工厂的特性,相应此接口定义的也是一些获取Bean,判断是否存在特定的bean的方法。core.io包中的接口定义的是整个spring体系的基础特性,比如这里的ResourceLoader里面定义的是如何获取资源,这个特性的引入使得整个WebApplicationContext可以读取配置文件信息。context包,如其名,引入的是应用上下文环境的特性,ApplicationContext是这个包中最基础的一个接口,他定义了“获取context的名字”,“获取context启动时间”,“获取context的父级环境”等这样基础的方法。至于web.context则是把context的含义进一步细化到web,在这里我们有了Servlet的概念,可以获取ServletContext。

输入图片说明

在完成context的初始化之后,判断该context是一个ConfigurableWebApplicationContext的实现,继续做两件事:

  1. 设置context的父级context
  2. 配置context

在配置context的时候,主要是执行了refresh()方法。他是在ConfigurableApplicationContext接口中定义的一个接口。此方法执行的动作是根据我们在web.xml中定义的contextConfigLocation的配置。在这里是构造BeanFactory并装载需要的Bean。XmlApplicationContext类是在其继承的AbstractApplicationContext中实现了refresh()方法。

AbstractApplicationContext.refresh()此方法是整个SpringMVC初始化的关键。 它是这样的:

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// 准备
			prepareRefresh();

			// 调用obtainFreshBeanFactory()方法是为了告知子类构建一个新的BeanFactory
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			//对BeanFactory做一些准备的工作
			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();
			}
		}
	}

ConfigurableApplicationContext.refresh()方法是SpringMVC初始化的关键,他的API定义是这样的:

Load or refresh the persistent representation of the configuration, which might an XML file, properties file, or relational database schema.

As this is a startup method, it should destroy already created singletons if it fails, to avoid dangling resources. In other words, after invocation of that method, either all or no singletons at all should be instantiated.

划重点就是:从XML或者properties文件这样的配置中,装载一个唯一的环境,它是一个启动方法,要么成功,环境初始化完成,要么失败,完全退出。如此重要的一个方法,值得我们深入研究。

首先来的是一个prepareRefresh()方法,做的事情是校验context启动的条件是否完备,设置context启动时间,装载必须的配置属性为后续正式的初始化动作做准备。

然后,是关键之一,构造一个BeanFactory:

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

这个方法的底层是调用由其定义,但实际上由子类AbstractRefreshableApplicationContext实现的refreshBeanFactory()方法:

	@Override
	protected final void refreshBeanFactory() throws BeansException {
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
                        //这里实现的是DefaultListableBeanFactory,没错,就是之前讨论过的那个基础的BeanFactoty实现类
			DefaultListableBeanFactory beanFactory = createBeanFactory();
                        //设id
			beanFactory.setSerializationId(getId());
                        //设置BeanFactory的属性,比如是否允许循环依赖
			customizeBeanFactory(beanFactory);
                        //通过XmlBeanDefinitionReader读取xml配置装载BeanDefinition
			loadBeanDefinitions(beanFactory);
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}

如果有心,看到loadBeanDefinitions()你也许要会心一笑。哈,这里不就使我们之前通过main()方法直接初始化一个BeanFactory的时候,XmlBeanDefinitionReader里面也定义了的一个同名方法么?没错,这里的loadBeanDefinitions虽然不由XmlBeanDefinitionReader定义,但是做的事情却是类似的,读取配置文件,装载BeanDefinition。装载BeanDefinition的过程,就是注册Bean的过程,它通过读取比如xml配置,把bean的信息装入当前环境,等到需要用的时候,再由BeanFactory定义的getBean()方法取出Bean实例来使用。不同的地方在于,我们之前自己写的main()方法虽然也是初始化了一个BeanFactory,可它比较简陋,SpringMVC的启动过程则要复杂得多,你可以通过配置,实例化一些更强大的Bean,添加各种高级属性,比如AOP等等。怎么实现的?继续往下看。

完成refreshBeanFactory()方法之后,context就有了一个BeanFactory,它包含了我们在applicationConfigLocation定义的所有bean的信息,即BeanDefinitionMap:

输入图片说明

下面一句就是:

prepareBeanFactory(beanFactory);

从方法名可以看出,它所做的事情是准备BeanFactory,啥意思?可以这样想:在上一句,我们在指定了context作为一个可以提供Bean的工厂,它应该有哪些bean,这些bean的属性是怎样的。但是这并不是真的在内存中拥有了这些bean的实例,要获取这些实例,还需要告诉context,这些bean从什么地方获取(指定ClassLoader)?在实例化到了什么阶段的时候,通知回调方法执行,让我们可以为bean添加附加的属性等等。这就是这个方法做的。注意!它只是在BeanFactory实例化完成后,补充了相应的信息,并没有真正的完成bean的装配。

	protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		// Tell the internal bean factory to use the context's class loader etc.
		//告知classLoader的存在
		beanFactory.setBeanClassLoader(getClassLoader());
		//设置bean表达式解析器。
		beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
		beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

		// Configure the bean factory with context callbacks.
		//设置context回调方法
		beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
		//下面的代码的作用是在装配(autowiring)的时候忽略指定类型的接口,这样做的原因是因为像Enviroment和ResourceLoader这样bean已经完成了装配?暂时做这样的理解,不知道是否准确,看官可以继续深入研究
		beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
		beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
		beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
		beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
		beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
		beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);

		// BeanFactory interface not registered as resolvable type in a plain factory.
		// MessageSource registered (and found for autowiring) as a bean.
		beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
		beanFactory.registerResolvableDependency(ResourceLoader.class, this);
		beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
		beanFactory.registerResolvableDependency(ApplicationContext.class, this);

		// Register early post-processor for detecting inner beans as ApplicationListeners.
		beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

		// Detect a LoadTimeWeaver and prepare for weaving, if found.
		if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
			beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
			// Set a temporary ClassLoader for type matching.
			beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
		}

		// Register default environment beans.
		if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
			beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
		}
		if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
			beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
		}
		if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
			beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
		}
	}

未完待续。

© 著作权归作者所有

共有 人打赏支持
DJZhu
粉丝 2
博文 26
码字总数 30529
作品 0
广州
程序员
深入理解Spring源码(一)-IOC容器的定位,载入,注册

前言:Spring源码继承,嵌套层次非常多,读起来非常容易晕,小伙伴们在看文章的时候一定要跟着文章的思路自己去源码里点一点,看一看,并且多看几次。就会越来越清晰。下面开始正题 1.Spring...

Meet相识_bfa5
05/01
0
0
Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密...

小致dad
08/03
0
0
spring源码-bean之初始化-1

  一、spring的IOC控制反转:控制反转——Spring通过一种称作控制反转(IOC)的技术促进了松耦合。当应用了IOC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建...

小不点丶
08/09
0
0
Spring IOC 容器源码分析 - 填充属性到 bean 原始对象

1. 简介 本篇文章,我们来一起了解一下 Spring 是如何将配置文件中的属性值填充到 bean 对象中的。我在前面几篇文章中介绍过 Spring 创建 bean 的流程,即 Spring 先通过反射创建一个原始的 ...

coolblog.xyz
06/11
0
0
spring源码解析bean定义加载一

前言 本文转自“天河聊技术”微信公众号 接着上次的spring上下文加载过程的源码解析 正文 上次主要介绍到刷新beanFactory,找到这个方法org.springframework.context.support.AbstractRefre...

天河2018
05/10
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

深夜胡思乱想

魔兽世界 最近魔兽世界出了新版本, 周末两天升到了满级,比之前的版本体验好很多,做任务不用抢怪了,不用组队打怪也是共享拾取的。技能简化了很多,哪个亮按哪个。 运维 服务器 产品 之间的...

Firxiao
3分钟前
0
0
MySQL 8 在 Windows 下安装及使用

MySQL 8 带来了全新的体验,比如支持 NoSQL、JSON 等,拥有比 MySQL 5.7 两倍以上的性能提升。本文讲解如何在 Windows 下安装 MySQL 8,以及基本的 MySQL 用法。 下载 下载地址 https://dev....

waylau
37分钟前
0
0
微信第三方平台 access_token is invalid or not latest

微信第三方开发平台code换session_key说的特别容易,但是我一使用就带来无穷无尽的烦恼,搞了一整天也无济于事. 现在记录一下解决问题的过程,方便后来人参考. 我遇到的这个问题搜索了整个网络也...

自由的开源
今天
0
0
openJDK之sun.misc.Unsafe类CAS底层实现

注:这篇文章参考了https://www.cnblogs.com/snowater/p/8303698.html 1.sun.misc.Unsafe中CAS方法 在sun.misc.Unsafe中CAS方法如下: compareAndSwapObject(java.lang.Object arg0, long a......

汉斯-冯-拉特
今天
2
0
设计模式之五 责任链模式(Chain of Responsibility)

一. 场景 相信我们都有过这样的经历; 我们去职能部门办理一个事情,先去了A部门,到了地方被告知这件事情由B部门处理; 当我们到了B部门的时候,又被告知这件事情已经移交给了C部门处理; ...

JackieRiver
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部