SpringMVC源码 3.1 DispatchServlet初始化

本文深入剖析了SpringMVC中DispatchServlet的初始化过程。从Servlet生命周期开始,详细讲解了DispatchServlet如何通过init()方法进行配置,包括属性注入、初始化SpringMVC的IoC容器,以及WebApplicationContext的创建和配置。通过对contextConfigLocation等参数的解析,展示了DispatchServlet如何准备并加载SpringMVC的配置文件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SpringMVC源码 3.1 DispatchServlet初始化


上文总结:
前面两篇笔记主要写了ContextLoaderListener在SpringMVC中启动的流程,已经通过ContextLoader完成了对WebApplicationContext(Spring容器)的创建。
还有Spring 在 web.xml中需要的配置相关的东西。
在Spring中,ContextLoaderListener只是一个辅助功能,用于创建WebApplicationContext。而真正的逻辑实现其实是在DispatchServlet中,DispatchServlet是Servlet接口的实现类。

关于Servlet的东西可以参考关于 Servlet XXX的笔记

节选Servlet的总结:
Servlet是运行在java 应用服务器上的程序,不能独立运行,其操作完全由Servlet容器控制。
负责对http请求做相应。根据在web.xml对<servlet>的配置,一个servlet可以映射到一个或者多个请求中。

Servlet的基本信息保存在各自的ServletConfig中,在初始化时由servlet容器通过init()方法传给servlet。

servlet主要负责三件事:
1.读取Servlet容器(tomcat)转发来的request信息。
2.在内部进行逻辑操作。
3.对操作结果写入response中,返回给Servlet容器(tomcat)

servlet的声明周期:
1.实例化(Servlet容器根据在web.xml配置的serlvet进行实例化,并将对应的信息保存在ServletConfig中)
2.初始化(Servlet容器调用init()方法,对其进行初始化)
3.提供服务(Servlet容器接收到请求时,根据映射指到对应的servlet程序,调用service()方法,进行服务)
4.销毁(Servlet容器关闭或者重启时调用destory()方法进行销毁)
5.垃圾回收。
注意:init()、service()、destory()这些都是由Servlet容器进行调用的

Servlet容器为了减少生产Servlet实例的开销,对Servlet采用单例多线程的机制:在servlet的生命周期中,只存在一个实例。Servlet容器的调度线程,从线程池获取一个线程去执行service()方法。在开发过程需要注意线程安全。
对于Tomcat可以在server.xml中通过<Connector>元素设置线程池中线程的数目。

1.DispatchServlet的初始化
通过上面对Servlet的描述中可以了解到,在servlet初始的时候,Servlet容器会调用init方法,所以首先我们需要找到DispatchServlet的init()方法。在父类HttpServletBean中找到了init()方法。
DispatchServlet继承关系:DispatchServlet<-FrameworkServlet<-HttpServletBean<-HttpServlet。前面三个都是spring中的类,HttpServlet是javax Servlet API中的抽象类。

HttpServletBean.init()
@Override
public final void init() throws ServletException {
     if (logger.isDebugEnabled()) {
           logger.debug("Initializing servlet '" + getServletName() + "'");
     }

     // Set bean properties from init parameters.
     try {
           //解析init-param中的参数到PropertyValues对象中
           PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
           //将当前的Servlet(DispatchServlet)类转化为BeanWrapper,从而能够以Spring的方式对init-param的值进行注入
           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) {
           logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
           throw ex;
     }

     // Let subclasses do whatever initialization they like.
     // FramworkServlet进行了实现,加载DispatchServlet的spring-servletxml文件,初始化SpringMVC IoC容器。
    initServletBean();

     if (logger.isDebugEnabled()) {
           logger.debug("Servlet '" + getServletName() + "' configured successfully");
     }
}

DispatchServlet的参数初始化过程:
1.获取当前Servlet的ServletConfig,将其封装成PropertyValues。也就是web.xml中的<servlet>的<init-param>下的参数。主要的参数是contextConfigLocation,用于确定SpringMVC Ioc的配置文件路径,(<context-param>中的contextConfigLocation中的参数是用于确定Spring Ioc容器的配置文件)。
2.将当前Servlet(也就是DispatchServlet)实例,转化为BeanWrapper类型实例,方便使用Spring提供的注入功能进行对应属性的注入。
3.注册相对于Resource的属性编辑器。
4.通过BeanWrapper的setPropertyVaules方法,进行属性注入,通过BeanWrapper可以将PropertyValues中的参数,以Setter注入到DispatchServlet的字段中。其实我们最常用的属性注入无非是contextConfigLocation,contextAttribute,contextClass,nameSpace等属性。
上面一大堆,说白了就是把ServletConfig中的参数,对应到DispatchServlet的定义的字段上。下面这些属性都可以配置在web.xml中
/** ServletContext attribute to find the WebApplicationContext in */
private String contextAttribute; 用于通过这个属性 去获取servletContext中获取对应key的WebApplicationContext。在initWebApplicationCOntext()方法中使用
/** WebApplicationContext implementation class to create */
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS; 通过这个属性去创建对应类型WebApplicationContext,在createWebApplicationContext()方法使用,默认为XMLWebApplicationContext.class。
/** WebApplicationContext id to assign */
private String contextId;          在configureAndRefreshWebApplicationContext()使用,确定contextId
/** Namespace for this servlet */
private String namespace;
/** Explicit context config location */
private String contextConfigLocation; 配置文件路径,在createWebApplicationContext()方法中会将其set到WebApplicationContext中,然后初始化spring环境的时候会加载配置文件路径。如果不存在会报错
完成了参数的初始,开始对servletBean进行初始化。
如果没有对应的参数,例如contextConfigLocation,在初始化参数的时候不会报错,但是会在创建WebApplicationContext的时候抛出异常。

FrameWorkServlet.initServletBean()
在ContextLoader的时候已经完成了一个WebApplicationContext的实例,创建了SpringIoC的容器。而这个函数中是对容器的补充,创建一个SpringMVC IoC容器。可以看出FrameworkServlet中对这个方法做了重写。函数中增加了时间戳来计算初始化的执行时间。
关键的初始化内容通过initWebApplicationContext()实现。并且添加了一个initFrameworkServlet()方法用于扩展,这个暂时是个空的方法。
@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 (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()) {
           long elapsedTime = System.currentTimeMillis() - startTime;
           this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
                     elapsedTime + " ms");
     }
}


2.WebApplicationContext的初始化

FramworkServlet.initWebApplicationContext()
protected WebApplicationContext initWebApplicationContext() {
     //从ServletContext取出父Context。在ContextLoader创建WebApplicationContext的时候,
     //在最后通过servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);存入ServletContext中。
     //这里通过sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)
     WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
     WebApplicationContext wac = null;

     if (this.webApplicationContext != null) {
           // A context instance was injected at construction time -> use it 
           //如果context实例在构造函数中被注入
           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);
                }
           }
     }
     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
           // 通过contextAttribute属性从ServletContext中加载WebApplicationContext
           wac = findWebApplicationContext();
     }
     if (wac == null) {
           // No context instance is defined for this servlet -> create a local one
           // 新建一个WebApplicationContext
           wac = createWebApplicationContext(rootContext);
     }

     if (!this.refreshEventReceived) {
           // Either the context is not a ConfigurableApplicationContext with refresh
           // support or the context injected at construction time had already been
           // refreshed -> trigger initial onRefresh manually here.
           onRefresh(wac);
     }

     if (this.publishContext) {
           // Publish the context as a servlet context attribute.
           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;
}

寻找或创建对应的WebApplicationContext实例
WebApplicationContext实例的寻找及创建包括以下几个步骤:
(1):通过构造函数的注入进行初始化
(2):通过contextAttribute进行初始化,
通过在web.xml文件中配置Servlet参数“contextAttribute”来查找ServletContext中对应的属性。默认为null,即不从ServletContext中去取。可以设置为WebApplicationContext.class.getName()+".ROOT"。也就是在ContextLoader中创建的,并以WebApplicationContext.class.getName()+".ROOT"为key存入ServletContext中的。
(3):重新创建WebApplicationContext实例
FramworkServlet.createWebApplicationContext ()
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
     return createWebApplicationContext((ApplicationContext) parent);
}

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
     //获取servlet的初始化参数“contextClass”,如果在web.xml没有配置,则默认为XmlWebApplicationContext.class
     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");
     }
     //通过反射方式实例化 contextClass
     ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

     wac.setEnvironment(getEnvironment());
     //parent是ContextLoader中创建的实例
     wac.setParent(parent);
     //获取contextConfigLocation属性,配置在servlet初始化参数中
     wac.setConfigLocation(getContextConfigLocation());

     //初始化spring环境包括加载配置文件。
     configureAndRefreshWebApplicationContext(wac);

     return wac;
}

3.configureAndRefreshWebApplicationContext

无论是通过构造函数注入还是重新创建,都会需要调用configureAndRefreshWebApplicationContext方法,对已经创建的WebApplicationContext实例进行配置和刷新。

FramworkServlet.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());
           }
     }

     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();
}
只要是Spring的ApplicationContext的使用,到最后都免不了使用公共父类AbstractApplicationContext提供的refresh()方法进行配置文件的加载


DispatchServlet中初始化已经差不多了,还剩下最后的刷新,onRefresh方法。在这个方法中主要初始了一些DispatchServlet中需要使用的一些组件。
例如各种Resolver、HandlerMapping、HandlerAdapter、HandlerExceptionResolvers等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值