关于spring:SpringMVC-初始化流程分析

33次阅读

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

@[toc]
框架源码是咱们 Coding 升级中的必修课,SSM 应该算是小伙伴们日常接触最多的框架了,这其中 SpringMVC 初始化流程相对来说要简略一些,因而明天松哥就先来和大家剖析一下 SpringMVC 初始化流程。

即便你没看过 SpringMVC 的源码,预计也据说过:DispatcherServlet 是 SpringMVC 的大脑,它负责整个 SpringMVC 的调度工作,是 SpringMVC 中最最外围的类,SpringMVC 整个顶层架构设计都体现在这里,所以搞明确 DispatcherServlet 的源码,基本上 SpringMVC 的工作原理也就了然于胸了。

然而 DispatcherServlet 继承自 FrameworkServlet,FrameworkServlet 又继承自 HttpServletBean,如下图:

因而咱们的剖析就从 HttpServletBean 开始。

1.HttpServletBean

HttpServletBean 继承自 HttpServlet,它负责将 init-param 中的参数注入到以后 Servlet 实例的属性中,同时也为子类提供了减少 requiredProperties 的能力,须要留神的是 HttpServletBean 并不依赖于 Spring 容器。

大家晓得,HttpServlet 的初始化是从 init 办法开始的,所以咱们就先从 HttpServletBean 的 init 办法开始看起:

@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 的所有配置并转为 PropertyValues,而后通过 BeanWrapper 批改指标 Servlet 的相干属性。BeanWrapper 是 Spring 中提供一个工具,应用它能够批改一个对象的属性,像上面这样:

public class Main {public static void main(String[] args) {User user = new User();
        BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(user);
        beanWrapper.setPropertyValue("username", "itboyhub");
        PropertyValue pv = new PropertyValue("address", "www.itboyhub.com");
        beanWrapper.setPropertyValue(pv);
        System.out.println("user =" + user);
    }
}

最终输入:

user = User{username='itboyhub', address='www.itboyhub.com'}

所以后面的 bw 实际上就代表以后 DispatcherServlet 对象。

通过 BeanWrapper 批改指标 Servlet 的相干属性时,有一个 initBeanWrapper 办法是空办法,开发者如有须要能够在子类中实现该办法,并且实现一些初始化操作。

属性配置实现后,最终调用 initServletBean 办法进行 Servlet 初始化,然而该办法也是一个空办法,在子类中实现。

这就是 HttpServletBean 所做的事件,比较简单,加载 Servlet 相干属性并设置给以后 Servlet 对象,而后调用 initServletBean 办法持续实现 Servlet 的初始化操作。

2.FrameworkServlet

从后面的介绍可知,FrameworkServlet 初始化的入口办法就是 initServletBean,因而咱们就从 FrameworkServlet#initServletBean 办法开始看起:

@Override
protected final void initServletBean() throws ServletException {
    // 省略...
    try {this.webApplicationContext = initWebApplicationContext();
        initFrameworkServlet();}
    catch (ServletException | RuntimeException ex) {// 省略...}
}

这个办法本来挺长的,然而抛开日志打印异样抛出,剩下的外围代码其实就两行:

  1. initWebApplicationContext 办法用来初始化 WebApplicationContext。
  2. initFrameworkServlet 办法用来初始化 FrameworkServlet,然而这个办法是一个空办法,没有具体的实现。原本子类能够重写该办法做一些初始化操作,然而实际上子类并没有重写该办法,所以这个办法咱们就暂且疏忽之,不去剖析了。

那么这里最为重要的其实就是 initWebApplicationContext 办法了,咱们一起来看下:

protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    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) {wac = findWebApplicationContext();
    }
    if (wac == null) {wac = createWebApplicationContext(rootContext);
    }
    if (!this.refreshEventReceived) {synchronized (this.onRefreshMonitor) {onRefresh(wac);
        }
    }
    if (this.publishContext) {String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }
    return wac;
}

这里的逻辑也比拟清晰:

  1. 首先获取 rootContext。在默认状况下,Spring 会将容器设置为 ServletContext 的一个属性,属性的 key 为 org.springframework.web.context.WebApplicationContext.ROOT,所以依据这个 key 就能够调用 ServletContext#getAttribute 办法获取到 rootContext 了。
  2. 获取 WebApplicationContext 实例,也就是给 wac 变量赋值的过程,这里存在三种可能性:1. 如果曾经通过构造方法给 webApplicationContext 赋值了,则间接将其赋给 wac 变量,同时,如果须要设置 parent 就设置,须要刷新就刷新。这种形式实用于 Servlet3.0 当前的环境,因为从 Servlet3.0 开始,才反对间接调用 ServletContext.addServlet 办法去注册 Servlet,手动注册的时候就能够应用本人提前准备好的 WebApplicationContext 了,这块松哥在我录制的 Spring Boot 视频中也讲过,感兴趣的小伙伴能够在公众号后盾回复 vhr 查看视频详情;2. 如果第一步没能胜利给 wac 赋值,那么调用 findWebApplicationContext 办法尝试去 ServletContext 中查找 WebApplicationContext 对象,找到了就赋值给 wac;3. 如果第二步没能胜利给 wac 赋值,那么调用 createWebApplicationContext 办法创立一个 WebApplicationContext 对象并赋值给 wac,一般来说都是通过这种形式创立的 WebApplicationContext。这三套组合拳下来,wac 必定是有值了。
  3. 当 ContextRefreshedEvent 事件没有触发时,调用 onRefresh 办法实现容器刷新(因为第一种和第三种获取 WebApplicationContext 的形式最终都会调用 configureAndRefreshWebApplicationContext 办法,而后公布事件,再将 refreshEventReceived 变量标记为 true,所以实际上只有第二种形式获取 wac 实例的时候,这里才会刷新,具体能够看下文剖析)。
  4. 最初将 wac 保留到到 ServletContext 中。保留的时候会依据 publishContext 变量的值来决定是否保留,publishContext 能够在 web.xml 中配置 Servlet 时通过 init-param 进行配置,保留的目标是为了不便获取。

下面的这些步骤中,通过 createWebApplicationContext 办法创立 WebApplicationContext 对象须要和大家细说下,因为个别状况下就是通过这种形式创立的 WebApplicationContext。咱们来看一下相干的办法:

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;
}
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());
        }
    }
    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    wac.addApplicationListener(new SourceFilteringListener(wac, new 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();}

这里一共波及到两个办法:

createWebApplicationContext

首先获取到创立类型,并查看创立类型,没问题的话调用 instantiateClass 办法实现创立工作,而后给创立好的 wac 对象配置各种属性,配置的 configLocation 就是咱们在 web.xml 文件中配置的 SpringMVC 配置文件门路,默认的文件门路是 /WEB-INF/[servletName]-servlet.xml

configureAndRefreshWebApplicationContext

configureAndRefreshWebApplicationContext 办法次要也是配置 & 刷新 WebApplicationContext,在这个办法里会调用 addApplicationListener 为 wac 增加一个监听器,监听的是 ContextRefreshedEvent 事件,当收到该事件后,会调用 FrameworkServlet 的 onApplicationEvent 办法,并在该办法中调用 onRefresh 办法实现刷新,刷新之后,会将 refreshEventReceived 变量标记为 true。

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

这就是 FrameworkServlet#initServletBean 办法的大抵工作逻辑。这里波及到了 onRefresh 办法,然而这是一个空办法,在子类 DispatcherServlet 中实现了,所以接下来咱们就来看 DispatcherServlet。

3.DispatcherServlet

这里咱们就不废话了,间接来看 onRefresh 办法,如下:

@Override
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 的内容其实很简略,就是九个组件的初始化。九个的初始化流程比拟相似,这里咱们以常见的视图解析器的初始化办法 initViewResolvers 为例,来一起看看初始化流程:

private void initViewResolvers(ApplicationContext context) {
    this.viewResolvers = null;
    if (this.detectAllViewResolvers) {
        // Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
        Map<String, ViewResolver> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {this.viewResolvers = new ArrayList<>(matchingBeans.values());
            // We keep ViewResolvers in sorted order.
            AnnotationAwareOrderComparator.sort(this.viewResolvers);
        }
    }
    else {
        try {ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
            this.viewResolvers = Collections.singletonList(vr);
        }
        catch (NoSuchBeanDefinitionException ex) {// Ignore, we'll add a default ViewResolver later.}
    }
    // Ensure we have at least one ViewResolver, by registering
    // a default ViewResolver if no other resolvers are found.
    if (this.viewResolvers == null) {this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
        if (logger.isTraceEnabled()) {logger.trace("No ViewResolvers declared for servlet'" + getServletName() +
                    "': using default strategies from DispatcherServlet.properties");
        }
    }
}

一开始的 viewResolvers 变量是一个汇合,解析进去的视图解析器对象都将放入这个汇合中。

首先判断 detectAllViewResolvers 变量是否为 true,如果为 true,则间接去查找 Spring 容器中的所有视图解析器,将查找后果赋值给 viewResolvers,而后进行排序。默认状况下 detectAllViewResolvers 变量的值为 true,如果有须要,能够在 web.xml 中进行配置,像上面这样:

<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-servlet.xml</param-value>
    </init-param>
    <init-param>
        <param-name>detectAllViewResolvers</param-name>
        <param-value>false</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

如果 detectAllViewResolvers 的值为 false,那么接下来就会去 Spring 容器中查找一个名为 viewResolver 的视图解析器,此时查找到的就是一个独自的视图解析器。

一般来说,咱们并不需要在 web.xml 中去配置 detectAllViewResolvers 的值,视图解析器有多少个就加载多少个。

举个简略例子,咱们在 SpringMVC 的配置文件中可能像上面这样配置视图解析器:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

默认状况下,这个 bean 的 id 有没有都行,如果有,取什么值都能够,反正最终都是通过类型而不是 id 去查找的视图解析器。然而如果你在 web.xml 中将 detectAllViewResolvers 批改为 false,那么这个 bean 的 id 取值就比拟重要了,就肯定要是 viewResolver。

如果在 Spring 容器中通过这两种形式(通过类型查找或通过 id 查找)都没有找到 ViewResolver 实例,那么会调用 getDefaultStrategies 办法去获取一个默认的 ViewResolver 实例。默认实例的获取形式如下:

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {if (defaultStrategies == null) {
        try {
            // Load default strategy implementations from properties file.
            // This is currently strictly internal and not meant to be customized
            // by application developers.
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }
        catch (IOException ex) {throw new IllegalStateException("Could not load'" + DEFAULT_STRATEGIES_PATH + "':" + ex.getMessage());
        }
    }
    String key = strategyInterface.getName();
    String value = defaultStrategies.getProperty(key);
    if (value != null) {String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
        List<T> strategies = new ArrayList<>(classNames.length);
        for (String className : classNames) {
            try {Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                Object strategy = createDefaultStrategy(context, clazz);
                strategies.add((T) strategy);
            }
            catch (ClassNotFoundException ex) {
                throw new BeanInitializationException("Could not find DispatcherServlet's default strategy class ["+ className +"] for interface ["+ key +"]", ex);
            }
            catch (LinkageError err) {
                throw new BeanInitializationException(
                        "Unresolvable class definition for DispatcherServlet's default strategy class [" +
                        className + "] for interface [" + key + "]", err);
            }
        }
        return strategies;
    }
    else {return Collections.emptyList();
    }
}

这段代码其实也比较简单,就是通过反射去获取默认的视图解析器。

首先给 defaultStrategies 赋值,defaultStrategies 的值实际上就是从 DispatcherServlet.properties 文件中加载到的,咱们来看下这个文件内容:

能够看到,这里一共定义了 8 个默认的键值对,有的值是一个,有的值是多个。后面 initStrategies 办法中一共要初始化九个组件,这里默认只定义了 8 个,少了一个 MultipartResolver,这也好了解,并非所有的我的项目都有文件上传,而且即便有文件上传,用哪一个具体的 MultipartResolver 也不好确定,还是要开发者本人决定。

defaultStrategies 其实加载到的就是这 8 个键值对,其中视图解析器对应的是 org.springframework.web.servlet.view.InternalResourceViewResolver,通过反射创立该类的实例,当 Spring 容器中不存在任何视图解析器的时候,默认的视图解析器即此。

这就是 initViewResolvers 的工作流程,另外 8 个也和它差不多,惟一不同的是 initMultipartResolver,如下:

private void initMultipartResolver(ApplicationContext context) {
    try {this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
    }
    catch (NoSuchBeanDefinitionException ex) {this.multipartResolver = null;}
}

能够看到,它只是依据 bean 的名字去查找 bean 实例,没有去查找默认的 MultipartResolver。

说到这里,松哥和大家多说一句 SpringMVC 配置中的小细节,

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">
</bean>

下面这个对于视图解析器和文件上传解析器的配置,不晓得小伙伴们有没有留神过,视图解析器的 id 可有可无,而文件上传解析器的 id 必须是 multipartResolver,回顾咱们下面的源码剖析,你就晓得为啥了!

4. 小结

好啦,这就是松哥和小伙伴们分享的 SpringMVC 的初始化流程,次要波及到了 HttpServletBean、FrameworkServlet 以及 DispatcherServlet 三个实例,HttpServletBean 次要是加载 Servlet 配置的各种属性并设置到 Servlet 上;FrameworkServlet 则次要是初始化了 WebApplicationContext;DispatcherServlet 则次要是初始化了本身的九个组件。

这只是初始化的流程,那么当申请到来之后,申请的流程又是怎么样的呢?这个松哥下篇文章来和大家分享~好啦,明天就先和小伙伴们聊这么多。

正文完
 0