前言
本系列文章顺延“Spring源码解析”,是在“父容器”创建完成后,对“子容器”(SpringMVC)创建,以及请求处理的解析。
源码解读
说起 SpringMVC,DispatcherServlet 应该是最熟悉的类之一。它几乎掌控着整个请求的分发以及最终响应,我们就从它来追溯“子容器”创建的源头。
- public abstract class GenericServlet implements Servlet, ServletConfig,
- java.io.Serializable {
- // getInitParameter可获取配置的 <init-param>
- // getServletName可获取配置的 <servlet-name>
- private transient ServletConfig config;
-
- // SpringMVC启动入口
- @Override
- public void init(ServletConfig config) throws ServletException {
- this.config = config;
- // 子类实现
- this.init();
- }
- }
该类就是 DispatcherServlet 的最顶层抽象父类了,它实现了 Servlet.init 方法,通过 Servlet 规范我们可以了解到,根据 <load-on-startup> 配置的不同,调用时机也不同。
- ≥0 :会在 Servlet 被实例化后执行;
- <0:在该 Servlet 被第一次请求时才调用,不填默认走该逻辑。
- public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
-
- // GenericServlet.init调用
- @Override
- public final void init() throws ServletException {
- if (logger.isDebugEnabled()) {
- logger.debug("Initializing servlet '" + getServletName() + "'");
- }
-
- // 将 <init-param>键值对封装成 PropertyValue
- PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
- if (!pvs.isEmpty()) {
- try {
- // 将自身(DispatcherServlet)封装成 BeanWrapper,方便 Spring注入
- BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
- ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
- // 注册解析器,对于 Resource类型的属性用 ResourceEditor解析
- 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;
- }
- }
-
- // 子类扩展实现
- initServletBean();
-
- if (logger.isDebugEnabled()) {
- logger.debug("Servlet '" + getServletName() + "' configured successfully");
- }
- }
- }
这一层主要是针对配置属性相关的代码,如果看过之前“实例创建”章节,对 bw.setPropertyValues(pvs, true) 应该不陌生,就是为 Servlet 做属性填充。接下来 initServletBean 方法实现了几乎“子容器”创建的大部分工作,由 FrameworkServlet 实现:
- public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
-
- // 可用 <init-param>:contextClass指定
- private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
- // 默认 XmlWebApplicationContext
- public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
-
- @Override
- protected final void initServletBean() throws ServletException {
- getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
- if (this.logger.isInfoEnabled()) {
- this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
- }
- long startTime = System.currentTimeMillis();
-
- try {
- // 初始化容器
- this.webApplicationContext = initWebApplicationContext();
- // 空实现:可自定义扩展
- initFrameworkServlet();
- }....// 省略 catch
-
- ....// 省略日志
- }
-
- protected WebApplicationContext initWebApplicationContext() {
- // 获取父容器:通过调用 ServletContext.getAttribute(ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)
- // 设置逻辑见容器启动: ContextLoader.initWebApplicationContext
- WebApplicationContext rootContext =
- WebApplicationContextUtils.getWebApplicationContext(getServletContext());
- WebApplicationContext wac = null;
-
- // 是通过构造器传入的方式会走这个分支(见 SpringBoot)
- if (this.webApplicationContext != null) {
- wac = this.webApplicationContext;
- if (wac instanceof ConfigurableWebApplicationContext) {
- ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
- // 调用 refresh会置 active为 true
- if (!cwac.isActive()) {
- if (cwac.getParent() == null) {
- // 设置父容器
- cwac.setParent(rootContext);
- }
- configureAndRefreshWebApplicationContext(cwac);
- }
- }
- }
- if (wac == null) {
- // 查找 contextAttribute是否已绑定的上下文 key
- wac = findWebApplicationContext();
- }
- if (wac == null) {
- // 如果还没有找到上下文,创建一个
- wac = createWebApplicationContext(rootContext);
- }
-
- if (!this.refreshEventReceived) {
- // 子容器(SpringMVC),在容器刷新完毕后执行
- onRefresh(wac);
- }
-
- if (this.publishContext) {
- String attrName = getServletContextAttributeName();
- getServletContext().setAttribute(attrName, wac);
- if (this.logger.isDebugEnabled()) {
- this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
- "' as ServletContext attribute with name [" + attrName + "]");
- }
- }
-
- return wac;
- }
-
- protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
- return createWebApplicationContext((ApplicationContext) parent);
- }
-
- protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
-
- // 默认 XmlWebApplicationContext
- // 可用<init-param>:contextClass指定
- Class<?> contextClass = getContextClass();
- if (this.logger.isDebugEnabled()) {
- this.logger.debug("Servlet with name '" + getServletName() +
- "' will try to create custom WebApplicationContext context of class '" +
- contextClass.getName() + "'" + ", using parent context [" + parent + "]");
- }
- 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);
- // 获取 <servlet>中配置的 <init-param>:contextConfigLocation
- String configLocation = getContextConfigLocation();
- if (configLocation != null) {
- // 设置“mvc”配置文件所在路径
- wac.setConfigLocation(configLocation);
- }
- // 和父容器相似的套路
- configureAndRefreshWebApplicationContext(wac);
-
- return wac;
- }
-
- protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
- if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
- // 设置 ID
- if (this.contextId != null) {
- wac.setId(this.contextId);
- } else {
- wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
- ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
- }
- }
-
- // Servelt相关的设置
- wac.setServletContext(getServletContext());
- wac.setServletConfig(getServletConfig());
- wac.setNamespace(getNamespace());
-
- // 注册事件监听器:被 SourceFilteringListener包裹,仅在“子容器”(mvc)中触发
- // ContextRefreshListener:触发 FrameworkServlet.onApplicationEvent
- // onApplicationEvent进而调用:onRefresh
- wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
-
- // 任何情况下 initPropertySources都在上下文刷新时被调用
- ConfigurableEnvironment env = wac.getEnvironment();
- if (env instanceof ConfigurableWebEnvironment) {
- ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
- }
-
- // 空实现
- postProcessWebApplicationContext(wac);
-
- // ApplicationContextInitializer执行,由以下属性指定
- // <init-param>:globalInitializerClasses、contextInitializerClasses指定
- applyInitializers(wac);
-
- // 容器刷新:AbstractApplicationContext实现(同父容器)
- wac.refresh();
- }
- }
抛开一些设置属性的步骤不谈,上面的逻辑主要可以总结为:容器实例化(反射调用构造器)——>将<init-param>指定的“mvc”配置文件设置到容器,以便随后的解析——>然后注册了一个“容器刷新”事件监听器,以便回调——>调用 AbstractApplicationContext.refresh 刷新容器——>最后回调 onRefresh。
这里的 onRefresh 在 FrameworkServlet 中是空实现,由子类定制实现。来看看 DispatcherServlet 的实现吧。
- public class DispatcherServlet extends FrameworkServlet {
-
- // 在 refresh中的 finishRefresh中会发布“刷新”事件
- // 会触发 ContextRefreshListener监听器,继而调用该方法
- @Override
- protected void onRefresh(ApplicationContext context) {
- initStrategies(context);
- }
-
- // 主要初始化了 mvc中所必须的成员变量
- protected void initStrategies(ApplicationContext context) {
- // 如果定义了 MultipartResolver,实例化
- initMultipartResolver(context);
-
- // 如果定义了 LocaleResolver,实例化
- // 默认使用 AcceptHeaderLocaleResolver
- initLocaleResolver(context);
-
- // 如果定义了 ThemeResolver,实例化
- // 默认使用 FixedThemeResolver
- initThemeResolver(context);
-
- // 初始化所有的 HandlerMapping:具体处理器
- // 初始化参数 detectAllHandlerMappings设为 false可以只加载指定的bean
- initHandlerMappings(context);
-
- // 初始化所有的 HandlerAdapter
- // 初始化参数 detectAllHandlerAdapters设为 false可以只加载指定的bean
- initHandlerAdapters(context);
-
- // 初始化所有的 HandlerExceptionResolver(异常处理)
- initHandlerExceptionResolvers(context);
-
- // 如果定义了 RequestToViewNameTranslator,实例化
- // 默认使用 DefaultRequestToViewNameTranslator
- initRequestToViewNameTranslator(context);
-
- // 初始化所有的 ViewResolver
- // 默认使用 InternalResourceViewResolver
- initViewResolvers(context);
-
- // 如果定义了 FlashMapManager,实例化
- // 默认使用 DefaultFlashMapManager
- initFlashMapManager(context);
- }
- }
以上的这些就是“子容器”和“父容器”差异之处了,支持了“mvc”框架例如文件上传、请求映射、异常处理、视图解析等功能。我们会在之后的章节展开讲解。
总结
本篇主要梳理了“子容器”(SpringMVC)的初始化脉络,顶层父类留下可扩展的“口子”,让子类去实现具体的初始化逻辑。