关于java:Spring-MVC-五-Spring-MVC的配置和DispatcherServlet初始化过程

2次阅读

共计 9862 个字符,预计需要花费 25 分钟才能阅读完成。

明天的内容是 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 四:Context 层级

正文完
 0