关于java:Spring-MVC-五-DispatcherServlet初始化过程续

明天的内容是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初始化过程

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理