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

一、概述

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

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

  • 如果是结合web容器tomcat,需要在web.xml中配置标签。
    
        org.springframework.web.context.ContextLoaderListener
    
  • 在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九大组件的初始化。

你可能感兴趣的:(SpringMVC源码阅读笔记----初始化)