SpringMVC源码分析(一) ———DispatcherServlet

DispatcherServlet的类结构设计

 

1、servlet

        在Spring中,ContextLoadListener只是辅助功能,用于创建WebApplicationContext类型实例,而真正的逻辑实现其实是在DispatcherServlet中进行的,DispatcherServlet是实现servlet接口的实现类。servlet主要是处理客户端的请求并将其结果发送到客户端。servlet的生命周期是由servlet的容器控制的,分为初始化、运行和销毁阶段。

public interface Servlet {

    void init(ServletConfig var1) throws ServletException;
    
    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();

(1)初始化阶段

        servelt容器创建一个ServletConfig对象,该对象包含了servlet的初始化配置信息。

servelt容器调用servlet对象的init方法进行初始化。

(2)运行阶段

        当servelt容器接受到一个请求时,servelt容器会针对这个请求创建serveltRequest 和serveltResponse对象,然后调用service方法。并把这两个参数传递给service方法。service方法通过servletRequest对象获得请求的信息,并处理该请求。再通过servletResponse对象生成这个请求的响应结果。

(3)销毁阶段

        当Web应用被终止时,servlet容器会先调用servelt对象的destrory方法,然后再销毁servlet对象,同时也会销毁与servlet对象相关联的serveltConfig对象

      2、DispatcherServlet的初始化

        servelt初始化阶段会调用init方法,所有我们首先查看在DispatcherServelt中是否重写了inti方法。我们在其父类HttpServletBean中找到了该方法。

 public final void init() throws ServletException {
        //解析int-param并封装至pvs中
      PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
      if (!pvs.isEmpty()) {
          try {
              //将当前的这个Servlet类转化为一个BeanWrapper,从而能够以Spring的方式来对init-param的值进行注入
              BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
              ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
              //注册自定义的属性编辑器,一旦遇到Resource类型的属性将会使用ResourceEditor进行解析
              bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
                
              //空实现,留给子类去实现
              this.initBeanWrapper(bw);
              //属性注入
              bw.setPropertyValues(pvs, true);
          } catch (BeansException var4) {
              if (this.logger.isErrorEnabled()) {
                  this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
              }

              throw var4;
          }
      }
      //留给子类去扩展
      this.initServletBean();
  }

DispatcherServlet的初始化过程主要是通过将当前的servlet类型实例转换为BeanWrapper类型实例,以便使用Spring中提供的注入功能进行对应属性的注入,及在web.xml文件中以初始化参数的方法配置在servlet的声明中。DispatcherServlet继承自FrameworkServlet,

public class DispatcherServlet extends FrameworkServlet {
    ······
}

FrameworkServlet类上包含对应的同名属性,Spring会保证这些参数被注入到对应的值中。属性注入主要包含以下几个步骤:

1、封装及验证初始化参数

      ServletConfigPropertyValues除了封装属性外还有对属性的验证功能。以下继续跟踪源码。

​
 public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties) throws ServletException {
            Set<String> missingProps = !CollectionUtils.isEmpty(requiredProperties) ? new HashSet(requiredProperties) : null;
            Enumeration paramNames = config.getInitParameterNames();

            while(paramNames.hasMoreElements()) {
                String property = (String)paramNames.nextElement();
                Object value = config.getInitParameter(property);
                this.addPropertyValue(new PropertyValue(property, value));
                if (missingProps != null) {
                    missingProps.remove(property);
                }
            }

            if (!CollectionUtils.isEmpty(missingProps)) {
                throw new ServletException("Initialization from ServletConfig for servlet '" + config.getServletName() + "' failed; the following required properties were missing: " + StringUtils.collectionToDelimitedString(missingProps, ", "));
            }
        }

​

从代码中可知,封装属性主要是对初始化参数进行封装,也就是servlet中配置的<init-param>

中配置的封装。当然,用户可以通过对required-Properties参数的初始化来强制验证某些属性的必要性。

2、将当前servlet实例转化成Bean-Wrapper实例

 PropertyAccessorFactory.forBeanPropertyAccess是Spring中提供的工具方法,主要用于将指定实例转化为Spring中可以处理的BeanWrapper类型的实例。

3、注册相对于Resource的属性编辑器

注册自定义的属性编辑器,一旦遇到Resource类型的属性将会使用ResourceEditor进行解析。

4、属性注入

BeanWrapper为Spring中的方法,支持Spring的自动注入。其实我们最常用的属性无非就是contextAttribute、context、namespace、contextConfigLocation等属性。

5、servletBean的初始化

在ContextLoaderListener加载的时候已经创建了WebApplicationContext实例,而在这个函数中最重要的就是这个实例进行进一步的补充初始化。继续查看源码,发现FrameworkServlet覆盖了HttpServletBean中的initServletBean函数:

protected final void initServletBean() throws ServletException {
        this.getServletContext().log("Initializing Spring " + this.getClass().getSimpleName() + " '" + this.getServletName() + "'");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("Initializing Servlet '" + this.getServletName() + "'");
        }

        long startTime = System.currentTimeMillis();

        try {
            this.webApplicationContext = this.initWebApplicationContext();
            this.initFrameworkServlet();
        } catch (RuntimeException | ServletException var4) {
            this.logger.error("Context initialization failed", var4);
            throw var4;
        }

        if (this.logger.isDebugEnabled()) {
            String value = this.enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data";
            this.logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value);
        }

        if (this.logger.isInfoEnabled()) {
            this.logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
        }

    }

上面函数设计了计时器来统计初始化的执行时间,而且提供了一个扩展方法initFrameworkServlet()用于子类的覆盖操作,而作为关键的初始化逻辑实现委托给了initWebApplicationContext()。

 3、WebApplicationContext的初始化

initWebApplicationContext函数的主要工作就是创建或刷新WebApplicationContext实例并对servlet功能所使用的变量进行初始化。

protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.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);
                    }

                    this.configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }

        if (wac == null) {
            wac = this.findWebApplicationContext();
        }

        if (wac == null) {
            wac = this.createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
            synchronized(this.onRefreshMonitor) {
                this.onRefresh(wac);
            }
        }

        if (this.publishContext) {
            String attrName = this.getServletContextAttributeName();
            this.getServletContext().setAttribute(attrName, wac);
        }

        return wac;
    }

本函数的初始化主要包含几个部分:

1、寻找或创建对应的WebApplicationContext实例。

(1)通过构造函数的注入进行初始化。

        在Web中包含SpringWeb的核心逻辑的DispatcherServlet只可被声明一次,在Spring中已经存在验证,所以这就确保了如果this.webApplicationContext !=null,则可以直接判定this.webApplicationContext已经通过构造函数初始化。

(2)通过contextApplication进行初始化

        通过在web.xml文件中配置的servlet参数contextAttribute来查找ServletContext中对应的属性,默认为WebApplicationContext.class.getName() + ".ROOT",也就是ContextLoaderListener加载时会创建WebApplicationContext实例,并将实例以WebApplicationContext.class.getName()+ ".ROOT"为key放入ServletContext中。

protected WebApplicationContext findWebApplicationContext() {
        String attrName = this.getContextAttribute();
        if (attrName == null) {
            return null;
        } else {
            WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext(), attrName);
            if (wac == null) {
                throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
            } else {
                return wac;
            }
        }
    }

(3)重新创建WebApplicationContext实例。

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
        Class<?> contextClass = this.getContextClass();
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
        } else {
            ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
            wac.setEnvironment(this.getEnvironment());
            wac.setParent(parent);
            String configLocation = this.getContextConfigLocation();
            if (configLocation != null) {
                wac.setConfigLocation(configLocation);
            }

            this.configureAndRefreshWebApplicationContext(wac);
            return wac;
        }
    }

2、configureAndRefreshWebApplicationContext

        代码显示,构造函数与单独创建,都会调configureAndRefreshWebApplicationContext方法来对已经创建的WebApplicationContext实例进行配置及刷新。追踪源码显示:

 protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            if (this.contextId != null) {
                wac.setId(this.contextId);
            } else {
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(this.getServletContext().getContextPath()) + '/' + this.getServletName());
            }
        }

        wac.setServletContext(this.getServletContext());
        wac.setServletConfig(this.getServletConfig());
        wac.setNamespace(this.getNamespace());
        wac.addApplicationListener(new SourceFilteringListener(wac, new FrameworkServlet.ContextRefreshListener()));
        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment)env).initPropertySources(this.getServletContext(), this.getServletConfig());
        }

        this.postProcessWebApplicationContext(wac);
        this.applyInitializers(wac);
        wac.refresh();
    }

3、onRefresh是FrameworkServlet类中提供的模板方法,在其子类DispatcherServlet中进行了重写,主要用于刷新Spring在Web功能实现中所必须使用的全局变量。
 

protected void onRefresh(ApplicationContext context) {
    }

4、追踪DispatcherServlet源代码:

protected void onRefresh(ApplicationContext context) {
        this.initStrategies(context);
    }

protected void initStrategies(ApplicationContext context) {
        this.initMultipartResolver(context);
        this.initLocaleResolver(context);
        this.initThemeResolver(context);
        this.initHandlerMappings(context);
        this.initHandlerAdapters(context);
        this.initHandlerExceptionResolvers(context);
        this.initRequestToViewNameTranslator(context);
        this.initViewResolvers(context);
        this.initFlashMapManager(context);
    }

这里是DispatcherServlet的9个个性化初始化方法,该方法调用了DispatcherServlet内部的9个初始化方法,分别初始化不同的组件。这里分析一下ViewResolvers方法。

        在SpringMVC方法中,当Controller将请求处理结果放入到ModelAndView中以后,DispatcherServlet会根据ModelAndView选择合适的视图进行渲染。那么在SpringMVC中是如何选择合适的VIEW呢?View对象是是如何创建的呢?答案就在ViewResolver中。ViewResolver接口定义了resolverViewName方法,根据viewName创建合适类型的View实现。那么如何配置ViewResolver呢?在Spring中,ViewResolver作为Spring Bean存在,可以在Spring配置文件中进行配置,例如下面的代码,配置了JSP相关的viewResolver。

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

5、viewResolvers属性的初始化工作在initViewResolvers中完成。

private void initViewResolvers(ApplicationContext context) {
        this.viewResolvers = null;
        if (this.detectAllViewResolvers) {
            Map<String, ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.viewResolvers = new ArrayList(matchingBeans.values());
                AnnotationAwareOrderComparator.sort(this.viewResolvers);
            }
        } else {
            try {
                ViewResolver vr = (ViewResolver)context.getBean("viewResolver", ViewResolver.class);
                this.viewResolvers = Collections.singletonList(vr);
            } catch (NoSuchBeanDefinitionException var3) {
            }
        }

        if (this.viewResolvers == null) {
            this.viewResolvers = this.getDefaultStrategies(context, ViewResolver.class);
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("No ViewResolvers declared for servlet '" + this.getServletName() + "': using default strategies from DispatcherServlet.properties");
            }
        }

    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值