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等。