• DispatcherServlet初始化过程


    前言

    我们知道在使用SpringMVC的时候,我们会在web.xml中配置如下内容,DispatcherServlet会拦截住所有的请求然后处理。

        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:application-context.xml</param-value>
        </context-param>
    
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    
        <servlet>
            <servlet-name>dispatcher</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath*:spring-mvc.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>dispatcher</servlet-name>
            <url-pattern>/*</url-pattern>
        </servlet-mapping>
    

    首先我们先看下DispatcherServlet的继承关系,然后我们再来逐步分析。

    DispatcherServlet继承关系

    Servlet

    public interface Servlet {
    
        /**
         * 容器启动时被调用(当load-on-startup为负数或者不设置时,会在第一次被使用时才调用),只会调用一次
         * 它有一个参数ServletConfig,是容器传进来的,表示的是这个Servlet的一些配置,比如DispatcherServlet配置的<init-param>
         */
        public void init(ServletConfig config) throws ServletException;
    
        /**
         * 获取Servlet的配置
         */
        public ServletConfig getServletConfig();
    
        /**
         * 最重要的一个方法,是具体处理请求的地方
         */
        public void service(ServletRequest req, ServletResponse res)
                throws ServletException, IOException;
    
        /**
         * 获取Servlet的一些信息,比如作者、版本、版权等,需要子类实现
         */
        public String getServletInfo();
    
        /**
         * 用于Servlet销毁(主要指关闭容器)时释放一些资源,只会调用一次
         */
        public void destroy();
    }
    

    ServletConfig

    public interface ServletConfig {
    
        /**
         * 返回Servlet的名字,就是<servlet-name>中配置的名字
         */
        public String getServletName();
    
        /**
         * 返回应用本身的一些配置
         */
        public ServletContext getServletContext();
    
        /**
         * 返回<init-param>配置的参数
         */
        public String getInitParameter(String name);
    
        /**
         * 返回<init-param>配置的参数的名字
         */
        public Enumeration<String> getInitParameterNames();
    }
    

    GenericServlet

    GenericServlet是Servlet的默认实现,主要做了如下几件事

    • 提供了无参的init方法,init()是一个模板方法,留给子类实现
        public void init(ServletConfig config) throws ServletException {
            this.config = config;
            this.init();
        }
        public void init() throws ServletException {
    
        }
    
    • 实现了ServletConfig的接口,可以直接调用ServletConfig中的方法,这样做的好处是如果我们想获取ServletConfig中的内容,不必先调用getServletConfig()了,比如获取ServletContext的代码
        public ServletContext getServletContext() {
            ServletConfig sc = getServletConfig();
            if (sc == null) {
                throw new IllegalStateException(
                    lStrings.getString("err.servlet_config_not_initialized"));
            }
    
            return sc.getServletContext();
        }
    

    HttpServlet

    HttpServlet是用HTTP协议实现的Servlet的基类,一般我们写的Servlet就是继承于它,我们注意到HttpServlet并没有实现init方法

    HttpServletBean

    从HttpServletBean开始我们就进入Spring的范围了,HttpServletBean重写了init方法

    public final void init() throws ServletException {
            if (logger.isDebugEnabled()) {
                logger.debug("Initializing servlet '" + getServletName() + "'");
            }
    
            // 从ServletConfig中获取初始配置,比如contextConfigLocation
            PropertyValues pvs = new HttpServletBean.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()));
                    // 模板方法,做一些初始化的工作,bw代表DispatcherServlet,但是没有子类重写
                    initBeanWrapper(bw);
                    // 把初始配置设置给DispatcherServlet,比如contextConfigLocation
                    bw.setPropertyValues(pvs, true);
                }
                catch (BeansException ex) {
                    if (logger.isErrorEnabled()) {
                        logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                    }
                    throw ex;
                }
            }
    
            // 模板方法,子类重写,做进一步初始化的工作
            initServletBean();
    
            if (logger.isDebugEnabled()) {
                logger.debug("Servlet '" + getServletName() + "' configured successfully");
            }
        }
    

    FrameworkServlet

    FrameworkServlet实现了initServletBean方法,其简化代码如下

        protected final void initServletBean() throws ServletException {
    
            try {
                // 初始化WebApplicationContext
                this.webApplicationContext = initWebApplicationContext();
                // 初始化FrameworkServlet,模板方法,并没有子类实现
                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;
            }
        }
    

    我们可以看到最重要的代码只有两句,而这两句之中最主要的是initWebApplicationContext(),下面我们就来看下initWebApplicationContext的简化代码

        protected WebApplicationContext initWebApplicationContext() {
            // 获取父WebApplicationContext,如果我们在web.xml中配置了ContextLoaderListener,那么它加载的就是父WebApplicationContext
            WebApplicationContext rootContext =
                    WebApplicationContextUtils.getWebApplicationContext(getServletContext());
            WebApplicationContext wac = null;
    
            // 如果通过构造方法传入了webApplicationContext,就使用它
            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) {
                // 从ServletContext中获取webApplicationContext,一般情况下是没有的
                wac = findWebApplicationContext();
            }
            if (wac == null) {
                // 自己创建一个webApplicationContext
                wac = createWebApplicationContext(rootContext);
            }
    
            if (!this.refreshEventReceived) {
                // 当ContextRefreshedEvent事件没有触发时调用此方法,模板方法,子类实现,是DispatcherServlet中重要的方法
                onRefresh(wac);
            }
    
            if (this.publishContext) {
                // 把webApplicationContext保存到ServletContext中
                String attrName = getServletContextAttributeName();
                getServletContext().setAttribute(attrName, wac);
            }
    
            return wac;
        }
    

    正常情况下都是自己创建一个webApplicationContext,我们看下创建的过程

        protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
            // 获取创建类型
            Class<?> contextClass = getContextClass();
            // 具体创建
            ConfigurableWebApplicationContext wac =
                    (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    
            wac.setEnvironment(getEnvironment());
            wac.setParent(parent);
            // 将设置的contextConfigLocation参数传给wac,默认传入WEB-INFO/[ServletName]-Servlet.xml
            wac.setConfigLocation(getContextConfigLocation());
            // 配置和刷新wac
            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 FrameworkServlet.ContextRefreshListener()));
    
            ConfigurableEnvironment env = wac.getEnvironment();
            if (env instanceof ConfigurableWebEnvironment) {
                ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
            }
    
            postProcessWebApplicationContext(wac);
            applyInitializers(wac);
    
            // 根据contextConfigLocation的值刷新webApplicationContext
            wac.refresh();
        }
    
    

    DispatcherServlet

    从上面的分析可以知道DispactcherServlet初始化的入口方法是onRefresh(wac),下面我们来具体看下

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

    我们可以看到onRefresh方法只是调用了initStrategies方法,而initStrategies方法内部调用了九个初始化SpringMVC组件的方法,这九个组件的初始化过程类似,我们就以initHandlerMappings为例分析下

        private void initHandlerMappings(ApplicationContext context) {
            this.handlerMappings = null;
    
            // detectAllHandlerMappings默认是true
            if (this.detectAllHandlerMappings) {
                // 从ApplicationContext中找到所有的HandlerMapping
                Map<String, HandlerMapping> matchingBeans =
                        BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
                if (!matchingBeans.isEmpty()) {
                    this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
                    AnnotationAwareOrderComparator.sort(this.handlerMappings);
                }
            } else {
                try {
                    // 从ApplicationContext中获取名称为handlerMapping的Bean
                    HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                    this.handlerMappings = Collections.singletonList(hm);
                } catch (NoSuchBeanDefinitionException ex) {
                }
            }
    
            // 如果没有HandlerMapping,则使用默认策略
            if (this.handlerMappings == null) {
                this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            }
        }
    

    默认策略其实就是根据DispatcherServlet.properties中的配置加载对应的组件,下面就是文件中具体的内容,我要说的是默认配置并不是SpringMVC的推荐配置,比如DefaultAnnotationHandlerMapping现在已经是废弃状态。

    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.annotation.DefaultAnnotationHandlerMapping
    
    org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,
        org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,
        org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
    
    org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,
        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
    

    总结

    下面用表格的形式简要总结下DispatcherServlet初始化过程

    类或接口初始化入口方法具体作用
    Servlet init(ServletConfig config) 接口定义,由Web容器调用
    GenericServlet init(ServletConfig config) 保存ServletConfig,内部调用无参的init方法
    HttpServlet - -
    HttpServletBean init() 设置contextConfigLocation的值,内部调用initServletBean()
    FrameworkServlet initServletBean() 初始化webApplicationContext,内部调用onRefresh(ApplicationContext context)
    DispatcherServlet onRefresh(ApplicationContext context) 初始化九大组件



    文章转载自:https://www.jianshu.com/p/be981b92f1d2

  • 相关阅读:
    pyqt的setObjectName()/findChild()
    pyqt的多Button的点击事件的槽函数的区分发送signal的按钮。
    分布式存储
    QTableWidget的表头颜色设置
    QListView的子项的ViewMode
    QHeaderView的点击和双击事件
    LeetCode(63):不同路径 II
    LeetCode(62):不同路径
    LeetCode(61):旋转链表
    LeetCode(60): 第k个排列
  • 原文地址:https://www.cnblogs.com/chongaizhen/p/11185783.html
Copyright © 2020-2023  润新知