1. 程式人生 > >interface21 - web - DispatcherServlet(DispatcherServlet初始化流程)

interface21 - web - DispatcherServlet(DispatcherServlet初始化流程)

設置 功能 urn ace 執行 bean對象 sets fault servlets

前言

最近打算花點時間好好看看spring的源碼,然而現在Spring的源碼經過叠代的版本太多了,比較龐大,看起來比較累,所以準備從最初的版本(interface21)開始入手,僅用於學習,理解其設計思想,後續慢慢研究其每次版本變更的內容。。。

先從interface21的一個典型web工程例子看起,寵物診所 - petclinic,因為該工程基本涵蓋了Spring的APO、IOC、JDBC、Web MVC、事務、國際化、主題切換、參數校驗等主要功能。。。

繼上一篇,了解完ContextLoaderListener(加載Spring Web Application Context)的流程後,看看Spring mvc的關鍵控制器 - DispatcherServlet初始化流程是如何的~~~~~~~

對應的web.xml配置

    <servlet>
        <servlet-name>petclinic</servlet-name>
        <servlet-class>com.interface21.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
<servlet-name>petclinic</servlet-name> <url-pattern>*.htm</url-pattern> </servlet-mapping>

這裏有一個地方需要註意下,load-on-startup的配置,關於該Servlet參數的含義,可以先看下web-app_2_3.dtd文件中的解釋,如下,大致意思就是,如果沒配置load-on-startup或該值為負數,則servlet容器可以自由選擇什麽時候加載該servlet(實例化並執行Servlet的init方法),如果為0或正數,則必須在容器啟動時加載Servlet並執行其init方法,且數值越小的Servlet優先加載,回過頭看我們的DispatcherServlet,由於load-on-startup配置成1,所以會在啟動的時候,執行init方法,即本文要關註的DispatcherServlet初始化流程:

<!--
The load-on-startup element indicates that this servlet should be
loaded (instantiated and have its init() called) on the startup
of the web application. The optional contents of
these element must be an integer indicating the order in which
the servlet should be loaded. If the value is a negative integer,
or the element is not present, the container is free to load the
servlet whenever it chooses. If the value is a positive integer
or 0, the container must load and initialize the servlet as the
application is deployed. The container must guarantee that
servlets marked with lower integers are loaded before servlets
marked with higher integers. The container may choose the order
of loading of servlets with the same load-on-start-up value.

Used in: servlet
-->

執行時序圖(看不清的話可以點擊查看原圖)

技術分享圖片

時序圖中的各個步驟簡要分析

執行的入口在HttpServletBean類的init方法,由於DispatcherServlet的load-on-startup參數配置成1,所以在Servlet容器(tomcat)啟動時,會自動調用該Servlet的init方法。

步驟描述:

  1. 首先,執行DispatcherServlet的父類HttpServletBean的init方法;
    public final void init() throws ServletException {
            this.identifier = "Servlet with name ‘" + getServletConfig().getServletName() + "‘ ";
    
            logger.info(getIdentifier() + "entering init...");
    
            // Set bean properties
            try {
                PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), requiredProperties);
                BeanWrapper bw = new BeanWrapperImpl(this);
                bw.setPropertyValues(pvs);
                logger.debug(getIdentifier() + "properties bound OK");
    
                // Let subclasses do whatever initialization they like
                initServletBean();
                logger.info(getIdentifier() + "configured successfully");
            } catch (BeansException ex) {
                String mesg = getIdentifier() + ": error setting properties from ServletConfig";
                logger.error(mesg, ex);
                throw new ServletException(mesg, ex);
            } catch (Throwable t) {
                // Let subclasses throw unchecked exceptions
                String mesg = getIdentifier() + ": initialization error";
                logger.error(mesg, t);
                throw new ServletException(mesg, t);
            }
        }
  2. 獲取Servlet的初始化參數,創建BeanWrapperImpl 實例,設置屬性值;
  3. 執行HttpServletBean的子類FrameworkServlet的initServletBean方法;
        protected final void initServletBean() throws ServletException {
            long startTime = System.currentTimeMillis();
            logger.info("Framework servlet ‘" + getServletName() + "‘ init");
            this.webApplicationContext = createWebApplicationContext();
            initFrameworkServlet();
            long elapsedTime = System.currentTimeMillis() - startTime;
            logger.info("Framework servlet ‘" + getServletName() + "‘ init completed in " + elapsedTime + " ms");
        }
  4. 調用FrameworkServlet的createWebApplicationContext方法。
        private WebApplicationContext createWebApplicationContext() throws ServletException {
            getServletContext().log("Loading WebApplicationContext for servlet ‘" + getServletName() + "‘");
            ServletContext sc = getServletConfig().getServletContext();
            WebApplicationContext parent = WebApplicationContextUtils.getWebApplicationContext(sc);
            String namespace = getNamespace();
    
            WebApplicationContext waca = (this.contextClass != null) ?
                    instantiateCustomWebApplicationContext(this.contextClass, parent, namespace) :
                    new XmlWebApplicationContext(parent, namespace);
            logger.info("Loading WebApplicationContext for servlet ‘" + getServletName() + "‘: using context class ‘" + waca.getClass().getName() + "‘");
            waca.setServletContext(sc);
    
            if (this.publishContext) {
                // Publish the context as a servlet context attribute
                String attName = getServletContextAttributeName();
                sc.setAttribute(attName, waca);
                logger.info("Bound context of servlet ‘" + getServletName() + "‘ in global ServletContext with name ‘" + attName + "‘");
            }
            return waca;
        }
  5. 進入createWebApplicationContext方法,從ServletContext的屬性中獲取WebApplicationContext,該上下文是由ContextLoaderListener加載的
  6. 創建子上下文XmlWebApplicationContext,其父上下文為之前ContextLoaderListener加載的WebApplicationContext,關於這兩個上下文的關系,及負責加載的內容,可參考這張圖,圖片來源(http://jinnianshilongnian.iteye.com/blog/1602617/)技術分享圖片
  7. 執行子上下文XmlWebApplicationContext的waca.setServletContext方法,去加載petclinic-servlet.xml配置文件(國際化,主題,HandlerMapping、HandlerAdapter、視圖解析等相關配置),關於WebApplicationContext上下文的加載流程,可參考上一篇(Spring Web Application Context加載流程),流程都是相同的;
  8. 將該子上下文設置到ServletContext屬性中
  9. 進入DispatcherServlet類的initFrameworkServlet方法,主要執行一些初始化工作
        protected void initFrameworkServlet() throws ServletException {
            initLocaleResolver();
            initThemeResolver();
            initHandlerMappings();
            initHandlerAdapters();
            initViewResolver();
        }
  10. 國際化相關:執行initLocaleResolver方法,從上下文中獲取localeResolver bean,沒有則使用默認AcceptHeaderLocaleResolver
        private void initLocaleResolver() throws ServletException {
            try {
                this.localeResolver = (LocaleResolver) getWebApplicationContext().getBean(LOCALE_RESOLVER_BEAN_NAME);
                logger.info("Loaded locale resolver [" + this.localeResolver + "]");
            } catch (NoSuchBeanDefinitionException ex) {
                // We need to use the default
                this.localeResolver = new AcceptHeaderLocaleResolver();
                logger.info("Unable to locate locale resolver with name ‘" + LOCALE_RESOLVER_BEAN_NAME + "‘: using default [" + this.localeResolver + "]");
            } catch (BeansException ex) {
                // We tried and failed to load the LocaleResolver specified by a bean
                throw new ServletException("Fatal error loading locale resolver with name ‘" + LOCALE_RESOLVER_BEAN_NAME + "‘: using default", ex);
            }
        }
  11. 主題相關:執行initThemeResolver方法,從上下文中獲取themeResolver bean,沒有則使用默認FixedThemeResolver
        private void initThemeResolver() throws ServletException {
            try {
                this.themeResolver = (ThemeResolver) getWebApplicationContext().getBean(THEME_RESOLVER_BEAN_NAME);
                logger.info("Loaded theme resolver [" + this.themeResolver + "]");
            } catch (NoSuchBeanDefinitionException ex) {
                // We need to use the default
                this.themeResolver = new FixedThemeResolver();
                logger.info("Unable to locate theme resolver with name ‘" + THEME_RESOLVER_BEAN_NAME + "‘: using default [" + this.themeResolver + "]");
            } catch (BeansException ex) {
                // We tried and failed to load the ThemeResolver specified by a bean
                throw new ServletException("Fatal error loading theme resolver with name ‘" + THEME_RESOLVER_BEAN_NAME + "‘: using default", ex);
            }
        }
  12. 執行initHandlerMappings方法,從上下文中獲取HandlerMapping類型的bean,沒有則使用默認BeanNameUrlHandlerMapping
        private void initHandlerMappings() throws ServletException {
            this.handlerMappings = new ArrayList();
    
            // Find all HandlerMappings in the ApplicationContext
            String[] hms = getWebApplicationContext().getBeanDefinitionNames(HandlerMapping.class);
            for (int i = 0; i < hms.length; i++) {
                initHandlerMapping(hms[i]);
                logger.info("Loaded handler mapping [" + hms[i] + "]");
            }
    
            // Ensure we have at least one HandlerMapping, by registering
            // a default HandlerMapping if no other mappings are found.
            if (this.handlerMappings.isEmpty()) {
                initDefaultHandlerMapping();
                logger.info("No HandlerMappings found in servlet ‘" + getServletName() + "‘: using default");
            } else {
                // We keep HandlerMappings in sorted order
                Collections.sort(this.handlerMappings, new OrderComparator());
            }
        }
        private void initDefaultHandlerMapping() throws ServletException {
            try {
                HandlerMapping hm = new BeanNameUrlHandlerMapping();
                hm.setApplicationContext(getWebApplicationContext());
                this.handlerMappings.add(hm);
            } catch (ApplicationContextException ex) {
                throw new ServletException("Error initializing default HandlerMapping: " + ex.getMessage(), ex);
            }
        }
  13. 執行initHandlerAdapters方法,從上下文中獲取HandlerAdapter類型的bean,沒有則使用默認SimpleControllerHandlerAdapter
        private void initHandlerAdapters() throws ServletException {
            this.handlerAdapters = new ArrayList();
    
            String[] has = getWebApplicationContext().getBeanDefinitionNames(HandlerAdapter.class);
            for (int i = 0; i < has.length; i++) {
                initHandlerAdapter(has[i]);
                logger.info("Loaded handler adapter [" + has[i] + "]");
            }
    
            // Ensure we have at least one HandlerAdapter, by registering
            // a default HandlerAdapter if no other adapters are found.
            if (this.handlerAdapters.isEmpty()) {
                initDefaultHandlerAdapter();
                logger.info("No HandlerAdapters found in servlet ‘" + getServletName() + "‘: using default");
            } else {
                // We keep HandlerAdapters in sorted order
                Collections.sort(this.handlerAdapters, new OrderComparator());
            }
        }
        private void initDefaultHandlerAdapter() throws ServletException {
            try {
                HandlerAdapter ha = new SimpleControllerHandlerAdapter();
                ha.setApplicationContext(getWebApplicationContext());
                this.handlerAdapters.add(ha);
            } catch (ApplicationContextException ex) {
                throw new ServletException("Error initializing default HandlerAdapter: " + ex.getMessage(), ex);
            }
        }
  14. 執行initViewResolver方法,從上下文中獲取viewResolver bean,沒有則使用默認InternalResourceViewResolver
        private void initViewResolver() throws ServletException {
            try {
                this.viewResolver = (ViewResolver) getWebApplicationContext().getBean(VIEW_RESOLVER_BEAN_NAME);
                logger.info("Loaded view resolver [" + viewResolver + "]");
            } catch (NoSuchBeanDefinitionException ex) {
                // We need to use the default
                this.viewResolver = new InternalResourceViewResolver();
                try {
                    this.viewResolver.setApplicationContext(getWebApplicationContext());
                } catch (ApplicationContextException ex2) {
                    throw new ServletException("Fatal error initializing default ViewResolver");
                }
                logger.info("Unable to locate view resolver with name ‘" + VIEW_RESOLVER_BEAN_NAME + "‘: using default [" + this.viewResolver + "]");
            } catch (BeansException ex) {
                // We tried and failed to load the ViewResolver specified by a bean
                throw new ServletException("Fatal error loading view resolver: bean with name ‘" + VIEW_RESOLVER_BEAN_NAME + "‘ is required in servlet ‘" + getServletName() + "‘: using default", ex);
            }
        }
  15. 返回到FrameworkServlet類
  16. 返回到HttpServletBean類
  17. Servlet的init方法執行完畢

另外可以關註下該Servlet的銷毀方法,類似的,也是執行一些資源銷毀等操作,銷毀工廠創建的單例bean對象,發布ContextClosedEvent事件等;

    public void destroy() {
        getServletContext().log("Closing WebApplicationContext for servlet ‘" + getServletName() + "‘");
        getWebApplicationContext().close();
    }
    public void close() {
        logger.info("Closing application context [" + getDisplayName() + "]");

        // destroy all cached singletons in this context,
        // invoking DisposableBean.destroy and/or "destroy-method"
        getBeanFactory().destroySingletons();

        // publish respective event
        publishEvent(new ContextClosedEvent(this));
    }

interface21代碼參考

https://github.com/peterchenhdu/interface21

interface21 - web - DispatcherServlet(DispatcherServlet初始化流程)