SpringMVC源码阅读笔记----初始化

一、概述

我将初始化流程分为两部分,第一部分是Spring上下文的初始化,基于ContextLoadListener实现,第二部分是Springmvc上下文的初始化,主要发生在DispatcherServlet,applicationContext与WebApplicationContext二者是父子容器关系。

二、Spring上下文applicationContext初始化流程简析

  • 如果是结合web容器tomcat,需要在web.xml中配置<listener>标签。
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
  • 在tomcat启动时,会调用ContextLoaderListener类的contextInitialized()方法,createContextLoader()方法返回的是null,这是一个留待子类实现的方法,然后会判断contextLoader是否为null,如果为null,转化为contextLoadListener类型,然后调用initWebApplicationContext()方法,对applicationContext初始化。
    public void contextInitialized(ServletContextEvent event) {
        this.contextLoader = createContextLoader();
        if (this.contextLoader == null) {
            this.contextLoader = this;
        }
        this.contextLoader.initWebApplicationContext(event.getServletContext());
    }
  • 调用initWebApplicationContext()方法,该方法的作用是:根据CONTEXT_CLASS_PARAM和CONFIG_LOCATION_PARAM,还有servletContext属性,来构建applicationContext。

  • 由于代码有些长,一部分一部分的分析,首先检查servletContext中是否有属性名为WebApplicationContext.ROOT的属性,如果有,那么说明进行了二次初始化,需要检查你的web.xml中是否定义了两个ContextLoader*。

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!");
}
  • 如果当前servletContext为null,将context赋值为成员变量,防止servletContext被销毁时也是可以用的。
if (this.context == null) {
                this.context = createWebApplicationContext(servletContext);
            }
  • 进入createWebApplicationContext(servletContext)方法看一看,这个方法的作用是返回一个WebApplicationContext的实现类类型。
Class<?> contextClass = determineContextClass(sc);
  • 进入 determineContextClass(sc)方法。
    这个方法首先判断是否自定义了上下文,如果自定义就加载自定义的,如果没有的话,默认加载XmlWebApplicationContext,方法都是通过反射创建对象。
protected Class<?> determineContextClass(ServletContext servletContext) {
        String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
        if (contextClassName != null) {
            try {
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
            }
            catch (ClassNotFoundException ex) {
                throw new ApplicationContextException(
                        "Failed to load custom context class [" + contextClassName + "]", ex);
            }
        }
        else {
            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);
            }
        }
    }
  • 退出来,然后判断反射获得到的contextClass是否是ConfigurableWebApplicationContext类型,或者是它的子类。
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                    "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        }
  • 最后,无论是什么类型,直接强转为ConfigurableWebApplicationContext类型,同时还要判断是不是借口,如果是接口抛出异常,如果不是接口,通过构造方法构造对象。
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
  • 重新回到ContextLoader类的initWebApplicationContext()方法,这里要判断context是不是ConfigurableWebApplicationContext类型,我们在前面已经将返回的对象通过构造方法构造并且强转为ConfigurableWebApplicationContext类型了。

  • 我十分不理解这里还要进行一次强转?怀疑是它的子类吗?

  • 接下来判断context是否还是活跃的?活跃的意思是已经执行过至少一次refresh()方法,还没有关闭。

  • loadParentContext(),没看明白,加载applicationContext父上下文,没有父上下文的话返回null,官方解释是EJB相关,不了解EJB,并且官方文档提到,在纯web应用下,这个方法返回null。

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);
                }
            }
  • 进入configureAndRefreshWebApplicationContext()方法,这个方法用于给context设定id,获取到applicationContext.xml的位置,放入applicationContext中,初始化参数,执行refresh(),refresh是整个spring容器最核心的初始化方法,包括BeanFactory的创建,国际化,样式工具等,具体内容会在后面的spring源码解析中详细阐述。
public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
 
            prepareRefresh();
      
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            prepareBeanFactory(beanFactory);
    try {
          
                postProcessBeanFactory(beanFactory);
                
                invokeBeanFactoryPostProcessors(beanFactory);

                registerBeanPostProcessors(beanFactory);

                initMessageSource();

                initApplicationEventMulticaster();

                onRefresh();

                registerListeners();

                finishBeanFactoryInitialization(beanFactory);

                finishRefresh();
            }

            catch (BeansException ex) {

                destroyBeans();

                cancelRefresh(ex);

                throw ex;
            }
        }
    }

三、SpringMVC上下文初始化

  • SpringMVC的初始化,是从HttpServletBean的init()方法,这个方法的作用是将配置参数映射到此servlet的bean属性上,然后调用子类FrameWorkServlet的初始化方法initServletBean()。
public final void init() throws ServletException {
        ......
        try {
            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true);
        }
        catch (BeansException ex) {
        .......
        }
    ......
  • FrameWorkServlet的initServletBean()方法最核心的代码如下。
try {
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }

-进入initWebApplicationContext()方法中,首先获取根容器,Spring初始会根据servletContext的属性获取。

WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());

-如果webApplicationContext不为null,那么一定是通过构造方法设置的,前面我们提到的利用反射设置。下面是判断类型,是否活跃,有没有父上下文,没有的话就把rootContext设置为它的父上下文等。

        if (this.webApplicationContext != null) {
            // A context instance was injected at construction time -> use it
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                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 -> set
                        // the root application context (if any; may be null) as the parent
                        cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
  • 当webApplicationContext已经存在于servletContext中,通过配置在servletContext中attribute中获取。
if (wac == null) {
            wac = findWebApplicationContext();
        }
  • 如果webApplicationContext还没有创建,那么就创建一个,我们接下来看一看createWebApplicationContext()方法。
    protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
        Class<?> contextClass = getContextClass();
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Servlet with name '" + getServletName() +
                    "' will try to create custom WebApplicationContext context of class '" +
                    contextClass.getName() + "'" + ", using parent context [" + parent + "]");
        }
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException(
                    "Fatal initialization error in servlet with name '" + getServletName() +
                    "': custom WebApplicationContext class [" + contextClass.getName() +
                    "] is not of type ConfigurableWebApplicationContext");
        }
        ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

        wac.setEnvironment(getEnvironment());
        wac.setParent(parent);
        wac.setConfigLocation(getContextConfigLocation());

        configureAndRefreshWebApplicationContext(wac);

        return wac;
    }
  • getContextClass()方法默认使用XmlWebApplicationContext创建,然后在继续类似上面的初始化。
Class<?> contextClass = getContextClass();

回到FrameWorkServlet,这个onRefresh()方法是进入DispatcherServlet的入口。

        if (!this.refreshEventReceived) {
            // Either the context is not a ConfigurableApplicationContext with refresh
            // support or the context injected at construction time had already been
            // refreshed -> trigger initial onRefresh manually here.
            onRefresh(wac);
        }
  • 进入initStrategies()方法,进入DispatcherServlet的初始化。是Dispatcher的九大组件的初始化,大致思路都是通过getBean()获取,如果获取不到就调用默认的方法getDefaultStrategy(),详细的分析会在后面的组件源码解析中。
    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }
  • 接下来判断是否需要将webApplicationContext放入servletContext中。
    if (this.publishContext) {
            // Publish the context as a servlet context attribute.
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                        "' as ServletContext attribute with name [" + attrName + "]");
            }
        }

大致流程就是以上所述。

四、小结

大致流程就是通过监听器切入,初始化applicationContext,spring容器,webApplicationContext的初始化分为三层,HttpServletBean、FrameWorkServlet、DispatcherServlet,从servlet取出相关属性进行赋值,在FrameWorkServlet中创建WebapplicationContext,最后再DispatcherServlet完成SpringMVC九大组件的初始化。

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

推荐阅读更多精彩内容