继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

死磕Spring源码-Spring MVC

损失函数
关注TA
已关注
手记 70
粉丝 1533
获赞 2735

Spring IoC是Spring的核心,所以IoC也是Spring MVC的基础,Spring MVC 会建立起一个IOC容器体系,IoC的启动过程与web容器启动的过程是集成在一起的,它们的启动由ContextLoaderListener监听器负责完成。建立IOC容器体系后,DispatcherServlet作为Spring MVC处理web请求的转发器也建立起来,从而完成响应HTTP请求的准备工作。

IOC容器的启动(ContextLoaderListener)

IoC容器的启动过程就是建立上下文的过程,由ContextLoaderListener启动的上下文为根上下文,在根上下文的基础上,还有一个与Web MVC相关的上下文作为根上下文的子上下文来保存DispatcherServlet所需要的MVC对象,构成一个上下文体系。这个上下文体系的建立与初始化是由ContextLoader来完成的。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
     public ContextLoaderListener() {
     }
     public ContextLoaderListener(WebApplicationContext context) {
          super(context);
     }
     @Override
     public void contextInitialized(ServletContextEvent event) {
          initWebApplicationContext(event.getServletContext());
     }
     @Override
     public void contextDestroyed(ServletContextEvent event) {
          closeWebApplicationContext(event.getServletContext());
          ContextCleanupListener.cleanupAttributes(event.getServletContext());
     }
}

在ContextLoaderListener中实现了ServletContextListener接口,这个接口里的函数会结合Web容器的生命周期被调用(contextInitialized,服务器启动时被调用;contextDestroyed,服务器关闭时被调用)。因为ServletContextListener是ServletContext的监听者,如果ServletContext发生变化,会触发预先设计好的动作,比如初始化和销毁。比如初始化:

ContextLoaderListener.contextInitialized
@Override
public void contextInitialized(ServletContextEvent event) {
    initWebApplicationContext(event.getServletContext());
}
//初始化WebApplicationContext根上下文
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    //根上下文作为唯一实例存在,用ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE标志位来实现
    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 {
        if (this.context == null) {
            //创建根上下文
            this.context = createWebApplicationContext(servletContext);
        }
        if (this.context instanceof ConfigurableWebApplicationContext) {
            //转型为需要产生的IoC容器
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    // 载入根上下文的双亲上下文
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        //设置ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE标志位
   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);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                    WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
        }
        if (logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
        }

        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;
    }
}

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    //判断使用什么样的类作为Web容器中的IoC容器
    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);
}

protected Class<?> determineContextClass(ServletContext servletContext) {
    String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    //如果在ServletContext中配置了CONTEXT_CLASS_PARAM,则使用这个class反射根上下文。
    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 {//如果没有配置CONTEXT_CLASS_PARAM,则使用默认的contextClass来反射根上下文
        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);
        }
    }
}

//设置ServletContext以及配置各类参数,最终调用refresh方法来初始化容器
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
        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);
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }

    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }

    customizeContext(sc, wac);
    wac.refresh();
}

ContextLoaderListener初始化根上下文过程图

图片描述

Spring MVC的启动(DispatcherServlet)

在完成ContextLoaderListener的初始化后,Web容器开始初始化DispatcherServlet。DispatcherServlet会建立自己的上下文来持有Spring MVC的Bean对象,在建立自己持有的IOC容器时,会将根上下文作为自己持有的上下文的双亲上下文,建立了这个上下文后将该上下文保存到ServletContext中,供以后检索和使用。DispatcherServlet的启动过程也就是Spring MVC的启动过程。

DispatcherServlet的继承关系

图片描述

DispatcherServlet通过继承FrameworkServlet和HttpServletBean来继承HttpServlet,这样就可以使用Servlet API来对HTTP请求进行响应,成为Spring MVC的前端处理器。

DispatcherServlet的工作分为两部分:一个是初始化部分,通过调用initStrategies方法完成Spring MVC的初始化;一个是对HTTP请求进行响应,作为一个servlet,Web容器会调用Servlet的doGet和doPost方法来完成响应动作。

DispatcherServlet的启动与初始化

DispatcherServlet的启动与Servlet的启动是相似的,在Servlet初始化时会调用init方法,在DispatcherServlet的基类HttpServletBean的初始化也由init开始。DispatcherServlet持有一个以自己的Servlet名称命名的IoC容器,这个容器作为根上下文的子上下文存在,在Web容器的上下文体系中,一个根上下文可以作为多个Servlet子上下文的双亲上下文。

一般情况下controller相关的bean或者对应的注解扫描配置在子上下文中,而service、dao的bean或者对应的注解扫描配置在根上下文中。

建立MVC的上下文代码逻辑:

HttpServletBean.init()
@Override
public final void init() throws ServletException {
    if (logger.isDebugEnabled()) {
        logger.debug("Initializing servlet '" + getServletName() + "'");
    }

    // 获取Servlet的初始化参数,配置Bean属性
    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) {
        logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
        throw ex;
    }

    // 具体初始化过程
    initServletBean();

    if (logger.isDebugEnabled()) {
        logger.debug("Servlet '" + getServletName() + "' configured successfully");
    }
}

@Override
protected final void initServletBean() throws ServletException {
    getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
    if (this.logger.isInfoEnabled()) {
        this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
    }
    long startTime = System.currentTimeMillis();
    //初始化上下文
    try {
        this.webApplicationContext = initWebApplicationContext();
        initFrameworkServlet();
    }
    catch (ServletException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }
    catch (RuntimeException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }

    if (this.logger.isInfoEnabled()) {
        long elapsedTime = System.currentTimeMillis() - startTime;
        this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
                elapsedTime + " ms");
    }
}

//这里的初始化上下文和ContextLoaderListner不太一样
protected WebApplicationContext initWebApplicationContext() {
    //使用ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE来获取根上下文
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    if (this.webApplicationContext != null) {
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    // 设置根上下文为双亲上下文
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        wac = findWebApplicationContext();
    }
    //如果以上都没有能获取MVC的IoC容器,则创建一个
    if (wac == null) {
        wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        onRefresh(wac);
    }

    if (this.publishContext) {
        //把建立好的子上下文存放到ServletContext中去,使用的属性名就是当前servlet名
        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 + "]");
        }
    }

    return wac;
}

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");
    }
    //实例化需要的具体上下文对象,这个上下文对象也是XmlWebApplicationContext
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    //设置双亲上下文
    wac.setParent(parent);
    wac.setConfigLocation(getContextConfigLocation());

    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        if (this.contextId != null) {
            wac.setId(this.contextId);
        }
        else {
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
        }
    }

    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }

    postProcessWebApplicationContext(wac);
    applyInitializers(wac);
    //通过refresh方法来调用容器初始化过程
    wac.refresh();
}

DispatcherServlet.onRefresh
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

//启动整个Spring MVC框架的初始化
protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

MVC框架的初始化是在initStrategies方法中完成的,初始化了支持国际化的LocaleResolver、request映射的HandlerMappings、视图生成的ViewResolvers等。

//初始化HandlerMapping
private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;
    //detectAllHandlerMappings这里默认为true,即默认从所有的IoC容器中获取HandlerMapping
    if (this.detectAllHandlerMappings) {
        // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
            // We keep HandlerMappings in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    else {//否则从当前的IoC容器中通过getBean获取所有Bean名称为“handlerMapping”的HandlerMapping
        try {
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerMapping later.
        }
    }

    // 如果没有handlerMappings,则从配置文件中读取
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
        }
    }
}

initHandlerMappings是对HandlerMappings的初始化,这些Map的作用是为HTTP请求找到相应的Controller控制器,以完成功能逻辑。IoC容器初始化后,容器里有各种各样的Bean,initHandlerMappings会从IoC容器中通过getBean来获取handlerMapping,handlerMapping包含了MVC中Controller的定义和配置,同时handlerMappings变量就已经获取了在BeanDefinition配置好的映射关系。加载的HandlerMapping被放在一个List中并已经排好序。

initHandlerMappings方法调用栈

图片描述

图片描述

参考文献:

《Spring技术内幕 深入解析Spring架构与设计原理》

打开App,阅读手记
12人推荐
发表评论
随时随地看视频慕课网APP

热门评论

恩,感觉写的不错,哎太菜了没怎么看很懂 懵懂,

上下文说的太官方了,不是很懂

查看全部评论