明天的内容是SpringMVC的初始化过程,其实也就是DispatcherServilet的初始化过程。

Special Bean Types

DispatcherServlet委托如下一些非凡的bean来解决申请、并渲染正确的返回。这些非凡的bean是Spring MVC框架治理的bean、依照Spring框架的约定解决相干申请,个别状况下是框架内置的,咱们当然也能够定制或扩大他们的性能。

这些非凡bean包含:

  1. HandlerMapping:依据肯定的规定把申请映射到对应的HandlerMapping去解决,HandlerMapping能够蕴含一系列拦截器,进行前置或后置解决。框架默认提供了RequestMappingHandlerMapping(解决@RequestMapping注解办法的)和SimpleUrlHandlerMapping两个HandlerMapping。
  2. HandlerAdapter:HandlerMapping匹配到申请之后,调用HandlerAdapter具体解决申请。
  3. HandlerExceptionResolver:产生异样后的异样处理器。
  4. ViewResolver:解决返回
  5. LocaleResolver, LocaleContextResolver:本地化处理器
  6. ThemeResolver:Theme渲染处理器
  7. MultipartResolver:Multipart处理器,文件上传下载的解决。
  8. FlashMapManager:跨申请存储和获取“input”和“output”的处理器

Web MVC Config

DispatcherServlet初始化过程中会依据WebApplicationContext的配置(xml或注解形式,后面两篇文章剖析过)实现上述非凡bean的初始化,如果DispatcherServlet在WebApplicationContext中没有发现相应的配置,则采纳DispatcherServlet.properties文件中的默认配置实现初始化。

DispatcherServlet.properties文件在Spring web mvc包下:

咱们猜测Spring MVC框架是通过DispatcherServlet的init办法实现上述各非凡bean的初始化的,上面咱们要详细分析一下具体的初始化过程。

Servlet Config

通过注解形式、或通过xml形式初始化DispatcherServlet的具体方法,后面两篇文章曾经做过剖析,此处不在赘述。

DispatcherServlet的初始化

家喻户晓,Servlet容器(比方Tomcat)会通过调用Servlet的init办法实现Servlet的初始化。

咱们接下来看一下DispatcherServlet的初始化过程,也就是DispatcherServlet的init办法。

先来看一眼DispatcherServlet的类构造:

init办法在他的父类HttpServletBean中:

    @Override    public final void init() throws ServletException {        // 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();    }

下面的代码是对以后Servlet属性的解决,与咱们的指标无关,初始化逻辑在最上面的办法initServletBean中,在他的子类(也是DispatcherServlet的间接父类)FrameworkServlet中:

    protected final void initServletBean() throws ServletException {        ...省略局部代码        try {            this.webApplicationContext = initWebApplicationContext();            initFrameworkServlet();        }        catch (ServletException | RuntimeException ex) {            logger.error("Context initialization failed", ex);            throw ex;        }

该办法中有很多打印log的代码,疏忽掉,剩下的就是两个办法的调用:一个是创立webApplicationContext的,一个是initFrameworkServlet,这个initFrameworkServlet是空办法,所以,DispatcherServlet的初始化逻辑,要害就在这个initWebApplicationContext()办法中。

initWebApplicationContext办法很长,咱们分段剖析一下。

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

首先获取以后ServletContext的RootContext,无关RootContext,参见后面的文章 Spring MVC 四:Context层级。

而后:

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

判断如果DispatcherServlet对象创立的时候,如果在构造方法中曾经初始化过WebApplicationContext了,那么就应用该WebApplicationContext,设置下面获取到的RootContext为以后WebApplicationContext的父容器。并且判断该Context是否曾经刷新过,如果没有刷新过的话,调用configureAndRefreshWebApplicationContext办法配置并刷新该Context。

后面文章Spring MVC 三 :基于注解配置中咱们剖析过DispatcherServlet的创立过程,的确在创立的时候就通过构造函数的参数传过来曾经创立好的ServletContext了:

protected void registerDispatcherServlet(ServletContext servletContext) {        String servletName = getServletName();        Assert.hasLength(servletName, "getServletName() must not return null or empty");        WebApplicationContext servletAppContext = createServletApplicationContext();        Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");        FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);        Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");        dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());   ...省略代码

所以如果是通过注解形式配置的话,会通过createServletApplicationContext()办法创立ServletContext:

    @Override    protected WebApplicationContext createServletApplicationContext() {        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();        Class<?>[] configClasses = getServletConfigClasses();        if (!ObjectUtils.isEmpty(configClasses)) {            context.register(configClasses);        }        return context;    }

最终创立的ServletContext是AnnotationConfigWebApplicationContext。

所以如果通过注解形式配置,那就是要走到下面这段逻辑中来的。

否则,如果不是通过注解、而是通过xml配置,也就是说DispactherServlet创立的时候并没有ServletContext,会走到上面的逻辑中:

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

如果wac为空(DispatcherServlet创立的时候没有设置),那么就判断容器中是否曾经注册进来了,如果曾经注册了的话,那么Spring framework就会认为其父容器曾经设置过了,也做过初始化以及refresh了,间接拿过去用就OK。(咱们的利用如果不被动注册的话,就不会有注册进来的Context,所以这段代码就跑不到)。

而后看上面的代码,如果没有发现,就调用createWebApplicationContext创立,createWebApplicationContext办法在创立WebApplicationContext之后,也会设置其父容器为RootContext,之后也会调用configureAndRefreshWebApplicationContext配置和刷新容器,走到和下面第一步(通过注解形式配置,DispatcherServlet创立的时候曾经通过结构器设置了一个Context)统一的逻辑中了。

createWebApplicationContext:

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {        Class<?> contextClass = getContextClass();        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);        String configLocation = getContextConfigLocation();        if (configLocation != null) {            wac.setConfigLocation(configLocation);        }        configureAndRefreshWebApplicationContext(wac);        return wac;    }

首先调用getContextClass()办法获取contextClass:

    public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;    private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;public Class<?> getContextClass() {        return this.contextClass;    }

能够看到,如果不是通过注解形式启动、而是通过xml配置形式启动的话,创立的ServletContext应该就是这个XmlWebApplicationContext。

创立ServletContext之后,与xml配置形式一样:设置父容器,而后调用configureAndRefreshWebApplicationContext办法配置及刷新容器。

接下来咱们看configureAndRefreshWebApplicationContext办法。

configureAndRefreshWebApplicationContext

目前为止,咱们后面的猜想:通过DispatcherServlet的init办法初始化各个非凡bean。尚未的到证实 --- 在DispatcherServlet的init办法中,咱们尚未看到相干的初始化代码。

不过代码还没剖析完,还有一个configureAndRefreshWebApplicationContext,咱们持续剖析。

代码比拟长,咱们还是分段剖析:

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

为WebApplicationContext设置Id,无关紧要,持续看上面的代码:

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

设置ServletContext、ServletConfig、以及namespace,之后新增了一个监听器: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();    }

设置环境变量,以及获取初始化参数,最初调用WebApplicationContext的refresh办法。

仍然没有看到DispatcherServlet对非凡bean的初始化!而且当初的代码逻辑是转到了ApplicationContext中,是Spring Framework的内容、并不是Spring MVC的内容。

别急,马上就要摸到开关了!

目前的代码的确是转悠到Spring Framework中来了。所以说Spring全家桶,不论是Spring MVC、还是SpringBoot、还是Spring Security,通通都是以Spring Framework为根底的。把握Spring Framework是把握Spring全家桶的根底。

ApplicationContext的refresh办法咱们很相熟了,是Spring Framework的要害办法,在AbstractApplicationContext类中实现,该办法最初会调用到finishRefresh()办法:

finishRefresh()办法最初会公布ContextRefreshedEvent事件。

没错,后面代码剖析过程中,咱们的确是在WebApplicationContext容器中注册了一个针对该事件的监听器ContextRefreshListener:

    private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {        @Override        public void onApplicationEvent(ContextRefreshedEvent event) {            FrameworkServlet.this.onApplicationEvent(event);        }    }

该监听器是定义在FrameworkServlet中的一个外部类,其onApplicationEvent办法会调用到FrameworkServlet的onApplicationEvent办法,这样,通过监听机制,代码逻辑就再次转回到了DispatcherServlet(确切说是他的父类FrameworkServlet)中来了:

    public void onApplicationEvent(ContextRefreshedEvent event) {        this.refreshEventReceived = true;        synchronized (this.onRefreshMonitor) {            onRefresh(event.getApplicationContext());        }    }

最终会调用到DispatcherServlet中来:

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

查看DispatcherServlet代码咱们会发现,这个initStrategies正式咱们要找的办法,办法参数Context是通过事件传递过去的,因而,DispatcherSerlet在进行初始化的时候能够持有ApplicationContext对象,而后,得心应手地实现Spring MVC非凡bean的初始化。

篇幅起因,对于DispatcherServlet的具体初始化过程,咱们前面剖析。

上一篇 Spring MVC 五 - Spring MVC的配置和DispatcherServlet初始化过程