Spring MVC 原理探秘 - 容器的创建过程 (3)

前面说了业务容器的创建过程,业务容器是通过 ContextLoaderListener。那 Web 容器是通过什么创建的呢?答案是通过 DispatcherServlet。我在上一篇文章介绍 HttpServletBean 抽象类时,说过该类覆写了父类 HttpServlet 中的 init 方法。这个方法就是创建 Web 容器的入口,那下面我们就从这个方法入手。如下:

// -☆- org.springframework.web.servlet.HttpServletBean public final void init() throws ServletException { if (logger.isDebugEnabled()) {...} // 获取 ServletConfig 中的配置信息 PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); if (!pvs.isEmpty()) { try { /* * 为当前对象(比如 DispatcherServlet 对象)创建一个 BeanWrapper, * 方便读/写对象属性。 */ 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()) {...} throw ex; } } // 进行后续的初始化 initServletBean(); if (logger.isDebugEnabled()) {...} } protected void initServletBean() throws ServletException { }

上面的源码主要做的事情是将 ServletConfig 中的配置信息设置到 HttpServletBean 的子类对象中(比如 DispatcherServlet),我们并未从上面的源码中发现创建容器的痕迹。不过如果大家注意看源码的话,会发现 initServletBean 这个方法稍显奇怪,是个空方法。这个方法的访问级别为 protected,子类可进行覆盖。HttpServletBean 子类 FrameworkServlet 覆写了这个方法,下面我们到 FrameworkServlet 中探索一番。

// -☆- org.springframework.web.servlet.FrameworkServlet protected final void initServletBean() throws ServletException { getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'"); if (this.logger.isInfoEnabled()) {...} long startTime = System.currentTimeMillis(); try { // 初始化容器 this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } catch (ServletException ex) { this.logger.error("Context initialization failed", ex); throw ex; } catch (RuntimeException ex) { this.logger.error("Context initialization failed", ex); throw ex; } if (this.logger.isInfoEnabled()) {...} } protected WebApplicationContext initWebApplicationContext() { // 从 ServletContext 中获取容器,也就是 ContextLoaderListener 创建的容器 WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; /* * 若下面的条件成立,则需要从外部设置 webApplicationContext。有两个途径可以设置 * webApplicationContext,以 DispatcherServlet 为例: * 1. 通过 DispatcherServlet 有参构造方法传入 WebApplicationContext 对象 * 2. 将 DispatcherServlet 配置到其他容器中,由其他容器通过 * setApplicationContext 方法进行设置 * * 途径1 可参考 AbstractDispatcherServletInitializer 中的 * registerDispatcherServlet 方法源码。一般情况下,代码执行到此处, * this.webApplicationContext 为 null,大家可自行调试进行验证。 */ if (this.webApplicationContext != null) { wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { if (cwac.getParent() == null) { // 设置 rootContext 为父容器 cwac.setParent(rootContext); } // 配置并刷新容器 configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { // 尝试从 ServletContext 中获取容器 wac = findWebApplicationContext(); } if (wac == null) { // 创建容器,并将 rootContext 作为父容器 wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { onRefresh(wac); } if (this.publishContext) { String attrName = getServletContextAttributeName(); // 将创建好的容器设置到 ServletContext 中 getServletContext().setAttribute(attrName, wac); if (this.logger.isDebugEnabled()) {...} } return wac; } protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) { // 获取容器类型,默认为 XmlWebApplicationContext.class Class<?> contextClass = getContextClass(); if (this.logger.isDebugEnabled()) {...} 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); wac.setConfigLocation(getContextConfigLocation()); // 配置并刷新容器 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 { // 生成默认 id wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(getServletContext().getContextPath()) + 'http://www.likecs.com/' + getServletName()); } } wac.setServletContext(getServletContext()); wac.setServletConfig(getServletConfig()); wac.setNamespace(getNamespace()); wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig()); } // 后置处理,子类可以覆盖进行一些自定义操作。在 Spring MVC 未使用到,是个空方法。 postProcessWebApplicationContext(wac); applyInitializers(wac); // 刷新容器 wac.refresh(); }

以上就是创建 Web 容器的源码,下面总结一下该容器创建的过程。如下:

从 ServletContext 中获取 ContextLoaderListener 创建的容器

若 this.webApplicationContext != null 条件成立,仅设置父容器和刷新容器即可

尝试从 ServletContext 中获取容器,若容器不为空,则无需执行步骤4

创建容器,并将 rootContext 作为父容器

设置容器到 ServletContext 中

到这里,关于 Web 容器的创建过程就讲完了。总的来说,Web 容器的创建过程和业务容器的创建过程大致相同,但是差异也是有的,不能忽略。

3.总结

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpgxsw.html