【Spring】DispatcherServlet的启动和初始化

来源:互联网 发布:单片机是什么专业 编辑:程序博客网 时间:2024/05/21 17:21

使用过SpringMVC的都知道DispatcherServlet,下面介绍下该Servlet的启动与初始化。作为Servlet,DispatcherServlet的启动与Serlvet的启动过程是相联系的。在Serlvet的初始化过程程中,Serlvet的init方法会被调用,以进行初始化。DispatcherServlet的基类HttpServletBean中的这个初始化过程源码如下:

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方法进行具体的初始化工作    initServletBean();    if (logger.isDebugEnabled()) {        logger.debug("Servlet '" + getServletName() + "' configured successfully");    }}// initServletBean这个初始化工作位于FrameworkServlet类中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");    }}protected WebApplicationContext initWebApplicationContext() {    // 调用WebApplicationContextUtils来得到根上下文,它保存在ServletContext中    // 使用它作为当前MVC上下文的双亲上下文    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();    }    if (wac == null) {        wac = createWebApplicationContext(rootContext);    }    if (!this.refreshEventReceived) {        onRefresh(wac);    }    // 把当前建立的上下文存到ServletContext中,使用的属性名是跟当前Servlet名相关的    if (this.publishContext) {        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;}

在初始化开始时,需要读取配置在ServletContext中的Bean属性参数,这些属性参数设置在web.xml的Web容器初始化参数中。

接着会执行DispatcherServlet持有的IOC容器的初始化过程,在这个过程中,一个新的上下文会被建立起来,这个DispatcherServlet持有的上下文被设置为根上下文的子上下文。可以这么理解,根上下文是和web应用相对应的一个上下文,而DispatcherServlet持有的上下文是和Servlet对应的一个上下文。在一个web应用中可以容纳多个Servlet存在;对应的,对于应用在web容器中的上下文体系,一个根上下文可以作为许多Serlvet上下文的双亲上下文。对这点的理解有助于在web环境中IOC容器的Bean设置和检索有所帮助,因为在向IOC容器getBean时,IOC容器会先向其双亲上下文去getBean,换句话说就是在根上下文中定义的Bean是可以被各个Servlet持有的上下文得到和共享的。DispatcherServlet持有的上下文被建立后,也需要和其他 IOC容器一样完成初始化操作,这个初始化操作就是通过refresh方法完成的,最后DispatcherServlet再给自己持有的上下文命名并设置到web窗口的上下文中(即ServletContext),这个名称和在web.xml中设置的DispatcherServlet的Servlet名称有关,进而保证这个上下文在web环境上下文体系中的唯一性。

上面代码执行后,这个Servlet的上下文就建立起来了,具体取得根上下文的过程是在WebApplicationContextUtils中实现的,源码如下:

public static WebApplicationContext getWebApplicationContext(ServletContext sc) {    // ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE这个属性代表的根上下文    // 在ContextLoaderListener初始化的过程中被建立,并设置到ServletContext中    return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);}

这个根上下文是ContextLoader设置到ServletContext中的,使用属性ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,同时对这个IOC容器的Bean配置文件,ContextLoader也进行了设置,默认的位置是在/WEB-INF/applicationContext.xml文件中,由于这个根上下文是DispatcherServlet建立的上下文的双亲上下文,所以根上下文中管理的bean是可以被DispatcherServlet的上下文使用的,反过来则不行,通过getBean向IOC容器获取bean时,会先到其双亲IOC容器尝试获取。

回到FrameworkServlet继续看DispatcherServlet的上下文是怎样建立的,源码如下:

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");    }        // 实例化需要的具体上下文对象,并为这个上下文对象设置属性    // 这里使用的是DEFAULT_CONTEXT_CLASS,这个DEFAULT_CONTEXT_CLASS被设置为XmlWebApplicationContext.class,    // 所以在DispatcherServlet中使用的IOC容器是XmlWebApplicationContext    ConfigurableWebApplicationContext wac =            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);    wac.setEnvironment(getEnvironment());    // 设置双亲上下文(也就是根上下文)    wac.setParent(parent);    wac.setConfigLocation(getContextConfigLocation());    configureAndRefreshWebApplicationContext(wac);    return wac;}

建立DispatcherServlet的上下文,需要把根上下文作为参数传递给它,再使用反射实例化上下文对象,并为它设置参数,按默认配置的话这个上下文对象就是XmlWebApplicationContext对象,这个类型是在DEFAULT_CONTEXT_CLASS参数中设置好并允许BeanUtils使用的,实例化结束之后,还需要为这个上下文对象设置好一些基本的配置,这些配置包括它的双亲上下文、Bean定义配置的文件位置等,完成配置后就通过调用IOC容器的refresh方法来完成IOC容器的最终初始化。

通过上面web容器一系列的操作后,在这个上下文体系建立和初始化完毕的基础上,Spring MVC就可以发挥作用了。此时DispatcherServlet就持有一个以自己的Servlet名称命名的IOC容器,这个IOC容器建立后,意味着DispatcherServlet拥有自己的Bean定义空间,这为使用各个独立的XML文件来配置MVC中各个Bean创建了条件,初始化完成后,Spring MVC的具体实现和普通的Spring应用程序的实现并没有太多差别,在DispatcherServlet的初始化过程中,以对HandlerMapping的初始化调用作为触发点,可以看下图Spring MVC模块初始化的方法调用关系图,

这个调用关系最初由HttpServletBean的init方法触发,这个HttpServletBean是HttpServlet的子类,接着会在HttpServletBean的子类FrameworkServlet中对IOC容器完成初始化,在这个初始化方法中会调用DispatcherServlet的initStrategies方法,在这个initStrategies方法中,启动整个Spring MVC框架的初始化工作。

从上面的方法调用关系图也可以看出对MVC的初始化是在DispatcherServlet的initStrategies中完成的,包括对各种MVC框架的实现元素,比如国际化支持LocalResolver、视图生成的ViewResolver等的初始化工作,源码如下:

protected void initStrategies(ApplicationContext context) {    initMultipartResolver(context);    initLocaleResolver(context);    initThemeResolver(context);    initHandlerMappings(context);    initHandlerAdapters(context);    initHandlerExceptionResolvers(context);    initRequestToViewNameTranslator(context);    initViewResolvers(context);    initFlashMapManager(context);}

上面各个初始化方法的名称应该比较好理解,这里以常见的HandlerMapping为例来说明initHandlerMappings()实现,Mapping映射的作用就是为HTTP请求找到相应的Controller控制器,HandlerMappings完成对MVC中Controller的定义和配置,DispatcherServlet中HandlerMappings初始化过程源码如下:

private void initHandlerMappings(ApplicationContext context) {    this.handlerMappings = null;    // 这里导入所有的HandlerMapping Bean,这些Bean可以在当前的DispatcherServlet的IOC    // 容器中,也可能在其双亲上下文中,这个detectAllHandlerMappings的默认值设为true,    // 即默认地从所有的IOC容器中获取    if (this.detectAllHandlerMappings) {        Map<String, HandlerMapping> matchingBeans =                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);        if (!matchingBeans.isEmpty()) {            this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());            OrderComparator.sort(this.handlerMappings);        }    }    else {        try {            // 可以根据名称从当前的IOC容器中通过getBean获取HandlerMapping            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);            this.handlerMappings = Collections.singletonList(hm);        }        catch (NoSuchBeanDefinitionException ex) {        }    }    // 如果没找到HandlerMappings,那么需要为Servlet设置默认的HandlerMappings,    // 这些默认的值可以设置在DispatcherServlet.properties中    if (this.handlerMappings == null) {        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);        if (logger.isDebugEnabled()) {            logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");        }    }}

在HandlerMapping初始化的过程中,把在Bean配置文件中配置好的handlerMapping从IOC容器中取得。经过上面的读取过程,handlerMappings变量就已经获取了在BeanDefinition中配置好的映射关系。其他的初始化过程和handlerMappings比较类似,都是从IOC容器中读入配置,所以说MVC初始化过程是建立在IOC容器已经初始化完成的基础之上的。执行完其他的各个初始化操作后,整个初始化过程就基本完成了。

 

阅读全文
0 0
原创粉丝点击