spring源码阅读,从springweb开始

前言

这个系列是java spring mvc 源码阅读与分析的一个系列
阅读源码分支为

spring初始化流程

spring的初始化流程大概可以分为几个阶段

  • web容器启动
  • spring bean容器初始化 (通过ContextLoaderListener启动,是根上下文)
  • DispatcherServlet初始化(初始化另一个上下文,这个上下文是根上下文的一个子上下文)

但是这几个阶段也不是固定的
首先要科普的一点spring webmvc容器是分层的,有一个根容器,每一个DispatcherServlet拥有一个子容器

Paste_Image.png

ContextLoaderListener这一步不是必选的,使用父子容器的设计也是方便除了webmvc使用其他使用spring容器的地方。只使用自容器也是可以的。

文档中介绍ContextLoaderListener

Non-Spring MVC implementations are preferable for some projects. Many teams expect to leverage their existing investment in skills and tools, for example with JSF. If you do not want to use Spring’s Web MVC, but intend to leverage other solutions that Spring offers, you can integrate the web MVC framework of your choice with Spring easily. Simply start up a Spring root application context through its ContextLoaderListener, and access it through its ServletContext attribute (or Spring’s respective helper method) from within any action object. No "plug-ins" are involved, so no dedicated integration is necessary. From the web layer’s point of view, you simply use Spring as a library, with the root application context instance as the entry point. Your registered beans and Spring’s services can be at your fingertips even without Spring’s Web MVC. Spring does not compete with other web frameworks in this scenario. It simply addresses the many areas that the pure web MVC frameworks do not, from bean configuration to data access and transaction handling. So you can enrich your application with a Spring middle tier and/or data access tier, even if you just want to use, for example, the transaction abstraction with JDBC or Hibernate.

翻译过来大致是说

对于某些项目,非Spring MVC实现更为可取。许多团队希望利用他们现有的技能和工具投资,例如使用JSF。如果您不想使用Spring的Web MVC,但打算利用Spring提供的其他解决方案,您可以轻松地将您选择的Web MVC框架与Spring集成。通过其ContextLoaderListener简单地启动一个Spring根应用程序上下文,并通过任何动作对象中的ServletContext属性(或Spring的各自的帮助方法)访问它。没有涉及“插件”,因此不需要专门的集成。从Web层的角度来看,您只需使用Spring作为库,将根应用程序上下文实例作为入口点。即使没有Spring的Web MVC,您的注册bean和Spring的服务也可以在您的指尖。在这种情况下,Spring不会与其他Web框架竞争。它简单地解决了纯Web MVC框架从bean配置到数据访问和事务处理的许多方面。所以您可以使用Spring中间层和/或数据访问层来丰富您的应用程序,即使您只想使用JDBC或Hibernate的事务抽象。

一些重要的类与方法

org.springframework.web.context.support.XmlWebApplicationContext
这个类作文默认的webmvc 上下文类,其继承结构如下

Paste_Image.png

比较复杂,根据这个实现接口的图,可以看出来这个类可以加载资源文件(实现ResourceLoader接口)、区分上下级的bean容器(实现BeanFactory接口和HierarchicalBeanFactory接口),可以发布和接收消息(实现ApplicationEventPublisherMessageSource接口)

这样就明白这个类的大致作用了

根容器初始化

web.xml中配置listener启动
一般在web.xml中我们需要配置

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath*:applicationContext-waimai-*.xml,
            classpath:webmvc-config.xml,
        </param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

注册了一个ContextLoaderListener listener,这个
在web环境启动的生命周期中,会向 ContextLoaderListener 这个listener会接收到一条消息,接收到消息后调用 ContextLoader#initWebApplicationContext 方法。ContextLoaderListener的继承关系如下

Paste_Image.png

具体执行的是定义在父类ContextLoader中的方法
initWebApplicationContext

1、调用 initWebApplicationContext

initWebApplicationContext这个方法的核心代码如下

//如果context是空则创建一个context
if (this.context == null) {
                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);
                }
            }
        //设置根容器
            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) {
                currentContextPerThread.put(ccl, this.context);
            }

上面这段代码中可以看到两个比较关键的地方,一个是,调用createWebApplicationContext创建context。另外一个是context创建完成,需要调用configureAndRefreshWebApplicationContext来设置和刷新各种配置项

1.1 调用 createWebApplicationContext

createWebApplicationContext用来生成一个具体的context,代码如下

    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
          //决定类,如果不通过参数指定默认值则使用定义在org/springframework/web/context/ContextLoader.properties里默认值,这个
        Class<?> contextClass = determineContextClass(sc);
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                    "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        }
        return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

1.2 调用 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);
        //CONFIG_LOCATION_PARAM 的默认值是 contextConfigLocation 也就是我们在web.xml中配置的xml位置
        String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
        if (configLocationParam != null) {
            wac.setConfigLocation(configLocationParam);
        }

        // 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);
        wac.refresh();
    }

这个方法中依次做了这么几件事

  • 为bean容器(也就是XmlWebApplicationContext)设置id
  • 从web.xml中读取contextConfigLocation配置,并放到容器中
  • 读取环境配置文件并初始化
  • 通过调用customizeContext定制bean容器其他初始化,方式是通过读取globalInitializerClassescontextInitializerClasses参数配置的类,实例化这些类(这些类需要实现ApplicationContextInitializer接口)调用initialize方法
  • 调用bean容器的refresh方法。

在spring中,ApplicationContext接口明确说明,所有的上下文对象都应该是只读的(为了线程安全理所当然),但是这样的类可以被刷新,可以看到``的继承关系图中的祖先类中存在AbstractRefreshableApplicationContext,用于刷新整个上下文

2. 调用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.
            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.
                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();
            }
        }
    }

为了线程安全,这个方法是同步执行的,并且是按次序分别调用几个函数,这里简洁的代码设计很漂亮,值得学习。
我们可以看到依次执行了这几个方法

  • prepareRefresh();
  • ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
  • prepareBeanFactory(beanFactory);
  • postProcessBeanFactory(beanFactory);
  • invokeBeanFactoryPostProcessors(beanFactory);
  • registerBeanPostProcessors(beanFactory);
  • initMessageSource();
  • initApplicationEventMulticaster();
  • onRefresh();
  • registerListeners();
  • finishBeanFactoryInitialization(beanFactory);
  • finishRefresh();
    下面我们具体分析下执行过程

2.1 prepareRefresh()

第一步,执行prepareRefresh();,这一步主要配置开始时间、active flag 还有其他初始化配置文件

    protected void prepareRefresh() {
        this.startupDate = System.currentTimeMillis();
        this.closed.set(false);
        this.active.set(true);

        if (logger.isInfoEnabled()) {
            logger.info("Refreshing " + this);
        }

        // Initialize any placeholder property sources in the context environment
        initPropertySources();

        // Validate that all properties marked as required are resolvable
        // see ConfigurablePropertyResolver#setRequiredProperties
        getEnvironment().validateRequiredProperties();

        // Allow for the collection of early ApplicationEvents,
        // to be published once the multicaster is available...
        this.earlyApplicationEvents = new LinkedHashSet<>();
    }

2.2 执行ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

获取
obtainFreshBeanFactory 这个方法如下

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        //通知子类刷新bean Factory
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (logger.isDebugEnabled()) {
            logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
        }
        return beanFactory;
    }

看下子类AbstractRefreshableApplicationContext 如何执行刷refreshBeanFactory(),代码如下

    @Override
    protected final void refreshBeanFactory() throws BeansException {
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            //创建一个默认beanFactory  DefaultListableBeanFactory
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            //设置序列化id
            beanFactory.setSerializationId(getId());
            //初始化BeanFactory
            customizeBeanFactory(beanFactory);
            //重要 载入bean定义
            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方法,XmlWebApplicationContext实现的loadBeanDefinitions方法如下
载入bean定义

    @Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // Create a new XmlBeanDefinitionReader for the given BeanFactory.
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        // Configure the bean definition reader with this context's
        // resource loading environment.
        beanDefinitionReader.setEnvironment(getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

        // Allow a subclass to provide custom initialization of the reader,
        // then proceed with actually loading the bean definitions.
        initBeanDefinitionReader(beanDefinitionReader);
        loadBeanDefinitions(beanDefinitionReader);
    }

其中loadBeanDefinitions是核心方法,里面包含了读取xml文件,从xml读取配置,生成bean并且注入。这里比较复杂,之后专门来分析一下吧 [todo]

这一步完成后,bean已经完成刷新,成功从xml文件中取出内容加入容器中。

完成这一步之后,父容器初始化完成,如果配置了DispatchServlet相关配置的开始进行子容器的初始化。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,748评论 6 342
  • Spring容器高层视图 Spring 启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相...
    Theriseof阅读 2,796评论 1 24
  • 今年是我的毕业年。 其实在真正面临毕业之前,总有人不断地问我:你毕业后打算做什么? 我的答案是:还不知道。 我的确...
    木签阅读 188评论 0 1
  • 中国人情社会几千年来一直是很微妙的变化发展着,如今微信朋友圈体现的淋漓尽致。作为一个主体的我,每天早晨第一件事情...
    思想聚焦的原创阅读 451评论 0 5