springMVC启动过程源码解析(一)
博客专区 > DJZhu 的博客 > 博客详情
springMVC启动过程源码解析(一)
DJZhu 发表于11个月前
springMVC启动过程源码解析(一)
  • 发表于 11个月前
  • 阅读 28
  • 收藏 1
  • 点赞 0
  • 评论 0

移动开发云端新模式探索实践 >>>   

之前的文章讨论过,一个简单的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());
		}
	}

未完待续。

标签: Spring Java EE
  • 打赏
  • 点赞
  • 收藏
  • 分享
共有 人打赏支持
粉丝 2
博文 26
码字总数 30442
×
DJZhu
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: