DispatcherServlet的初始化過程

本文探討Spring MVC中DispatcherServlet是如何初始化的月洛,DispatcherServlet初始化指的是init()生命周期方法被執(zhí)行兑凿,而不是DispatcherServlet被實(shí)例化的過程孩擂。

DispatcherServlet類

DispatcherServlet的類層次如下圖所示

DispatcherServlet.png

不管DispatcherServlet被如何包裝喂窟,它本質(zhì)上是一個(gè)servlet纽帖,servlet的生命周期是init -> service -> destroy抡四,因此本文從init()方法入手分析DispatcherServlet的初始化過程柜蜈。(如果你對(duì)servlet不熟悉,我建議你看一下這篇入門指南:Java Servlet完全教程

init()方法

DispatcherServlet的init()方法在父類HttpServletBean中定義指巡,其代碼如下所示:

@Override
public final void init() throws ServletException {
    if (logger.isDebugEnabled()) {
        logger.debug("Initializing servlet '" + getServletName() + "'");
    }
    // Set bean properties from init parameters.
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    if (!pvs.isEmpty()) {
        try {
            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) {
            if (logger.isErrorEnabled()) {
                logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            }
            throw ex;
        }
    }
    // Let subclasses do whatever initialization they like.
    initServletBean();
    if (logger.isDebugEnabled()) {
        logger.debug("Servlet '" + getServletName() + "' configured successfully");
    }
}
  • ServletConfigPropertyValues用于解析web.xml定義中<servlet>元素的子元素<init-param>中的參數(shù)值淑履。
    若<init-param>元素如下,則ServletConfigPropertyValues就會(huì)擁有這些參數(shù)
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/dispatcherServlet.xml</param-value>
        </init-param>
        <load-on-startup>-1</load-on-startup>
    </servlet>
    
ServletConfigPropertyValues.png
  • BeanWrapper把DispatcherServlet當(dāng)做一個(gè)Bean去處理藻雪,這也是HttpServletBean類名的含義秘噪。bw.setPropertyValues(pvs, true) 將上一步解析的servlet初始化參數(shù)值綁定到DispatcherServlet對(duì)應(yīng)的字段上;
  • init()方法是一個(gè)模板方法阔涉,initBeanWrapper和initServletBean兩個(gè)方法由子類去實(shí)現(xiàn)缆娃。

initServletBean()方法

DispatcherServlet的initServletBean()方法在父類FrameworkServlet中定義捷绒,它調(diào)用initWebApplicationContext方法初始化DispatcherServlet自己的應(yīng)用上下文:

@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();
    }
    // 省略一些代碼
}

protected void initFrameworkServlet() throws ServletException {
}

初始化servlet應(yīng)用上下文

initWebApplicationContext方法負(fù)責(zé)初始化DispatcherServlet自己的應(yīng)用上下文,其代碼如下所示:

protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    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);
            }
        }
    }
    if (wac == null) {
        // No context instance was injected at construction time -> see if one
        // has been registered in the servlet context. If one exists, it is assumed
        // that the parent context (if any) has already been set and that the
        // user has performed any initialization such as setting the context id
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        wac = createWebApplicationContext(rootContext);
    }
    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);
    }
    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 + "]");
        }
    }
    return wac;
}

該方法的概要流程如下:

  1. 獲得ContextLoaderListener創(chuàng)建的根應(yīng)用上下文贯要;
  2. 為DispatcherServlet創(chuàng)建自己的應(yīng)用上下文暖侨;
  3. 刷新DispatcherServlet自己的應(yīng)用上下文。

獲得根應(yīng)用上下文

利用WebApplicationContextUtils類的getWebApplicationContext靜態(tài)方法取得根應(yīng)用上下文崇渗,相關(guān)代碼如下:

public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
    return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}

public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
    Assert.notNull(sc, "ServletContext must not be null");
    Object attr = sc.getAttribute(attrName);
    if (attr == null) {
        return null;
    }
    if (attr instanceof RuntimeException) {
        throw (RuntimeException) attr;
    }
    if (attr instanceof Error) {
        throw (Error) attr;
    }
    if (attr instanceof Exception) {
        throw new IllegalStateException((Exception) attr);
    }
    if (!(attr instanceof WebApplicationContext)) {
        throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
    }
    return (WebApplicationContext) attr;
}

前面文章指出根應(yīng)用上下文已經(jīng)通過ContextLoaderListener被容器初始化字逗,其類型默認(rèn)是XmlWebApplicationContext類,啟動(dòng)過程中會(huì)將WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE(即org.springframework.web.context.WebApplicationContext.ROOT)和根應(yīng)用上下文通過ServletContext.setAttribute設(shè)置到應(yīng)用的ServletContext宅广。


rootContext.png

創(chuàng)建DispatcherServlet的應(yīng)用上下文

  1. 若this.webApplicationContext不為null葫掉,則說明DispatcherServlet在實(shí)例化期間已經(jīng)被注入了應(yīng)用上下文。這種情況發(fā)生在Spring Boot應(yīng)用啟動(dòng)時(shí)跟狱,由于父類FrameworkServlet實(shí)現(xiàn)了ApplicationContextAware接口俭厚,所以setApplicationContext回調(diào)函數(shù)被調(diào)用時(shí)將字段webApplicationContext設(shè)置為根應(yīng)用上下文,注意這并不是在init()初始化方法中完成的驶臊,而是在實(shí)例化DispatcherServlet的過程中完成的挪挤。
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
    if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
        this.webApplicationContext = (WebApplicationContext) applicationContext;
        this.webApplicationContextInjected = true;
    }
}
  1. 若this.webApplicationContext為null,則說明DispatcherServlet在實(shí)例化期間沒有被注入應(yīng)用上下文关翎。首先通過findWebApplicationContext方法嘗試尋找先前創(chuàng)建的應(yīng)用上下文扛门。
protected WebApplicationContext findWebApplicationContext() {
    String attrName = getContextAttribute();
    if (attrName == null) {
        return null;
    }
    WebApplicationContext wac =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
    if (wac == null) {
        throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
    }
    return wac;
}

public String getContextAttribute() {
    return this.contextAttribute;
}

該方法從ServletContext的屬性中找到該servlet初始化屬性(<init-param>元素)contextAttribute的值對(duì)應(yīng)的應(yīng)用上下文,若沒有找到則報(bào)錯(cuò)纵寝。

  • 找到的WebApplicationContext是其他servlet初始化時(shí)設(shè)置到ServletContext屬性中的论寨,具體是由initWebApplicationContext方法最后幾行做的,其代碼如下爽茴。
    String attrName = getServletContextAttributeName();
    getServletContext().setAttribute(attrName, wac);
    
    public String getServletContextAttributeName() {
        return SERVLET_CONTEXT_PREFIX + getServletName();
    }
    
    public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";
    
    可見設(shè)置的鍵值是FrameworkServlet.class.getName() + ".CONTEXT."加上servlet的<servlet-name>值葬凳。
  • 以下面的web.xml片段為例,MyServlet會(huì)被容器在啟動(dòng)的時(shí)候初始化闹啦,而dispatcher則是延遲初始化沮明,它們均是DispatcherServlet類型。MyServlet初始化時(shí)會(huì)在ServletContext中設(shè)置以org.springframework.web.servlet.FrameworkServlet.CONTEXT.MyServlet為鍵窍奋,XmlWebApplicationContext對(duì)象為值的屬性,當(dāng)dispatcher初始化時(shí)酱畅,其contextAttribute值恰是由MyServlet初始化應(yīng)用上下文時(shí)設(shè)置的鍵琳袄,因此dispatcher初始化時(shí)應(yīng)用上下文就是MyServlet初始化的應(yīng)用上下文(雖然從源碼分析如此但筆者并未在Spring MVC的文檔里找到與contextAttribute有關(guān)的資料)。
    <servlet>
        <servlet-name>MyServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/dispatcherServlet.xml</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextAttribute</param-name>
            <param-value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.MyServlet</param-value>
        </init-param>
        <load-on-startup>-1</load-on-startup>
    </servlet>
    
  1. 若第2步?jīng)]有找到之前初始化的應(yīng)用上下文那么就需要通過createWebApplicationContext方法為DispatcherServlet創(chuàng)建一個(gè)以根應(yīng)用上下文為父的應(yīng)用上下文纺酸。這個(gè)應(yīng)用上下文的類型是由父類FrameworkServlet的contextClass字段指定的窖逗,可以在web.xml中配置,默認(rèn)是XmlWebApplicationContext類型餐蔬,可以參見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");
    }
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

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

    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

具體的創(chuàng)建過程如下:

  • 實(shí)例化contextClass指定類型的應(yīng)用上下文佑附,同時(shí)將根應(yīng)用上下文設(shè)置為它的父上下文,并將<servlet>的contextConfigLocation初始化參數(shù)值設(shè)置到對(duì)應(yīng)屬性仗考;
  • 從代碼中拋出異常的條件看contextClass屬性指定的類必須實(shí)現(xiàn)ConfigurableWebApplicationContext接口音同,而不是文檔說明的WebApplicationContext接口,對(duì)此問題筆者咨詢了Spring作者秃嗜,見SPR-17414权均;
  • configureAndRefreshWebApplicationContext方法先進(jìn)一步為DispatcherServlet自己的應(yīng)用上下文設(shè)置了屬性,然后調(diào)用了各ApplicationContextInitializer實(shí)現(xiàn)類的回調(diào)函數(shù)锅锨,最后做了刷新操作實(shí)例化各單例bean叽赊,其代碼如下所示:
    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
        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
            if (this.contextId != null) {
                wac.setId(this.contextId);
            }
            else {
                // Generate default id...
                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()));
    
        // The wac environment's #initPropertySources will be called in any case when the context
        // is refreshed; do it eagerly here to ensure servlet property sources are in place for
        // use in any post-processing or initialization that occurs below prior to #refresh
        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
        }
    
        postProcessWebApplicationContext(wac);
        applyInitializers(wac);
        wac.refresh();
    }
    
    protected void postProcessWebApplicationContext(ConfigurableWebApplicationContext wac) {
    }
    
    protected void applyInitializers(ConfigurableApplicationContext wac) {
        String globalClassNames = getServletContext().getInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM);
        if (globalClassNames != null) {
            for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
                this.contextInitializers.add(loadInitializer(className, wac));
            }
        }
    
        if (this.contextInitializerClasses != null) {
            for (String className : StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS)) {
                this.contextInitializers.add(loadInitializer(className, wac));
            }
        }
    
        AnnotationAwareOrderComparator.sort(this.contextInitializers);
        for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
            initializer.initialize(wac);
        }
    }
    
    applyInitializers方法執(zhí)行各ApplicationContextInitializer的initialize回調(diào)函數(shù),這里的ApplicationContextInitializer分為兩種:
    • 全局ApplicationContextInitializer類由部署描述符中名為globalInitializerClasses的<context-param>初始化參數(shù)指定必搞;
    • DispatcherServlet自己的ApplicationContextInitializer類由部署描述符中<servlet>元素內(nèi)名為contextInitializerClasses的<init-param>初始化參數(shù)指定必指;
    • 這兩個(gè)參數(shù)的值都是由各個(gè)類名組成的以逗號(hào)、分號(hào)或空白符分隔的字符串恕洲。
  1. 最后的疑問是部署描述符web.xml中的contextClass等參數(shù)是如何被綁定到FrameworkServlet類或DispatcherServlet類的對(duì)應(yīng)字段的呢取劫?這是由上文提到的init()方法中ServletConfigPropertyValues和BeanWrapper完成的。


    contextClass.png

刷新DispatcherServlet的應(yīng)用上下文

onRefresh方法刷新DispatcherServlet自己的應(yīng)用上下文研侣,DispatcherServlet類重寫了父類FrameworkServlet的onRefresh方法谱邪,該方法調(diào)用initStrategies()方法實(shí)例化MultipartResolver、LocaleResolver庶诡、HandlerMapping惦银、HandlerAdapter和ViewResolver等組件。

@Override
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

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

以實(shí)例化HandlerMapping的initHandlerMappings方法為例末誓,其代碼如下:

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;

    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 {
        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.
        }
    }

    // Ensure we have at least one HandlerMapping, by registering
    // a default HandlerMapping if no other mappings are found.
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
        }
    }
}
  • handlerMappings是DispatcherServlet的一個(gè)List<HandlerMapping>扯俱;
  • detectAllHandlerMappings是DispatcherServlet的一個(gè)布爾值屬性,表示是否要發(fā)現(xiàn)所有的HandlerMapping喇澡,若為true則從DispatchServlet自己的應(yīng)用上下文和根應(yīng)用上下文獲得所有已實(shí)例化的HandlerMapping單例迅栅,否則只獲取名為handlerMapping的HandlerMapping單例;
  • 若不存在已實(shí)例化的HandlerMapping晴玖,那么用默認(rèn)策略實(shí)例化HandlerMapping读存。

用getDefaultStrategies方法獲取默認(rèn)策略:

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
    String key = strategyInterface.getName();
    String value = defaultStrategies.getProperty(key);
    if (value != null) {
        String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
        List<T> strategies = new ArrayList<>(classNames.length);
        for (String className : classNames) {
            try {
                Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                Object strategy = createDefaultStrategy(context, clazz);
                strategies.add((T) strategy);
            }
            catch (ClassNotFoundException ex) {
                throw new BeanInitializationException(
                        "Could not find DispatcherServlet's default strategy class [" + className +
                        "] for interface [" + key + "]", ex);
            }
            catch (LinkageError err) {
                throw new BeanInitializationException(
                        "Unresolvable class definition for DispatcherServlet's default strategy class [" +
                        className + "] for interface [" + key + "]", err);
            }
        }
        return strategies;
    }
    else {
        return new LinkedList<>();
    }
}
  • defaultStrategies是Properties類型的靜態(tài)變量,保存了策略名到默認(rèn)工廠實(shí)現(xiàn)類的映射關(guān)系呕屎,它被DispatcherServlet的靜態(tài)代碼塊所填充让簿。默認(rèn)的策略定義在spring-webmvc包下的DispatcherServlet.properties文件中,該文件內(nèi)容如下:
    # Default implementation classes for DispatcherServlet's strategy interfaces.
    # Used as fallback when no matching beans are found in the DispatcherServlet context.
    # Not meant to be customized by application developers.
    
    org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
    
    org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
    
    org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
        org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
    
    org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
        org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
        org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
    
    org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
        org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
        org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
    
    org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
    
    org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
    
    org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
    
  • 以HandlerMapping為例秀睛,獲取HandlerMapping默認(rèn)實(shí)現(xiàn)的調(diào)用是getDefaultStrategies(context, HandlerMapping.class)尔当,接著會(huì)查找以HandlerMapping.class.getName()為鍵的值,從文件中可以看到是org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping和org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping兩個(gè)工廠實(shí)現(xiàn)類蹂安;
  • 其他組件的策略同理椭迎,在此不再贅述锐帜。

總結(jié)

至此,本文完成了對(duì)DispatcherServlet的init方法的分析畜号,它已準(zhǔn)備好提供服務(wù)了缴阎,對(duì)請(qǐng)求處理的分析請(qǐng)看后續(xù)文章。

參考文獻(xiàn)

SpringMVC源碼剖析(三)- DispatcherServlet的初始化流程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末弄兜,一起剝皮案震驚了整個(gè)濱河市药蜻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌替饿,老刑警劉巖语泽,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異视卢,居然都是意外死亡踱卵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門据过,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惋砂,“玉大人,你說我怎么就攤上這事绳锅∥鞫” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵鳞芙,是天一觀的道長(zhǎng)眷柔。 經(jīng)常有香客問我,道長(zhǎng)原朝,這世上最難降的妖魔是什么驯嘱? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮喳坠,結(jié)果婚禮上鞠评,老公的妹妹穿的比我還像新娘。我一直安慰自己壕鹉,他們只是感情好剃幌,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著御板,像睡著了一般锥忿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上怠肋,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音淹朋,去河邊找鬼笙各。 笑死钉答,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的杈抢。 我是一名探鬼主播数尿,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼惶楼!你這毒婦竟也來了右蹦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤歼捐,失蹤者是張志新(化名)和其女友劉穎何陆,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體豹储,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贷盲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了剥扣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巩剖。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖钠怯,靈堂內(nèi)的尸體忽然破棺而出佳魔,到底是詐尸還是另有隱情,我是刑警寧澤晦炊,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布鞠鲜,位于F島的核電站,受9級(jí)特大地震影響刽锤,放射性物質(zhì)發(fā)生泄漏镊尺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一并思、第九天 我趴在偏房一處隱蔽的房頂上張望庐氮。 院中可真熱鬧,春花似錦宋彼、人聲如沸弄砍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽音婶。三九已至,卻和暖如春莱坎,著一層夾襖步出監(jiān)牢的瞬間衣式,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留碴卧,地道東北人弱卡。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像住册,于是被迫代替她去往敵國和親婶博。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容