北仑保税区进口商品市场:spring学习笔记之DispatcherServlet源码解读

来源:百度文库 编辑:偶看新闻 时间:2024/05/05 03:30:39
spring学习笔记之DispatcherServlet源码解读
DispatcherServlet--翻译spring-framework-reference.pdf及源码解读。
最近在看spring源码,同时也看着英文版的spring-framework-reference.pdf。顺便花点时间翻译下来,一方面记录下学习路程,另一方面加深印象。
spring web MVC 框架,就像其他web MVC框架一样,是请求驱动,围绕一个核心的servlet设计,这个servlet把接收的请求(request)分别分发到不同的控制器并提供其他功能促进web应用的开发。而spring的DispatcherServlet做的事情更多。它完全整合了spring Ioc容器方便你使用spring所拥有的功能。

这幅图解释了spring web MVC DispatcherServlet对请求的处理流程。其实DispatcherServlet是采用了前端控制器(Front Controller)的设计模式,这种设计模式也被其他流行的web框架所采用。

根据类图,知道DispatcherServlet实际上是一个Servlet,因为它继承于HttpServlet,所以它需要在web.xml定义,并且在web.xml定义需要通过DispatherServlet处理的请求的URL映射。
这是标准的J2EE servlet 配置方式。下面是一个例子:
view plaincopy to clipboardprint?


example
org.springframework.web.servlet.DispatcherServlet
1


example
*.form


上面这个例子,所有以.form结尾的请求都会被名为example的DispaterServlet处理。这是使用spring MVC的第一步,一系列beans需要配置。
在spring web MVC框架,每一个DispatcherServlet都有它自己的WebApplicationContext,这些WebApplicationContext继承了一个根WebApplicationContextde定义的所有bean。这些被继承的bean可以在具体的servlet中改写作用域,也可以在一个servlet实例定义新的局部的作用域bean。
框架在启动初始化DispatcherServlet时,在WEB应用下的WEB-INF目录下寻找名为[servlet-name]-servlet.xml的文件,并创建定义的bean,这些bean如果同样在全局作用域中定义(名字相同),则覆盖全局bean.如在上一例中,则会找WEB-INF/example-servlet.xml文件。当然,配置文件的路径可以通过servlet的参数配置。
WebApplicationContext是普通的ApplicationContext的扩展,它增加了一些web应用需要的特性。比如,它解析themes与ApplicationContext是不同的,因为它会关联到ServletContext。WebApplicationContext绑定到了ServletContext,在你需要访问它时,只需要用RequestContextUtils类的静态方法就可以找到它。
DispatcherServlet有几个特殊的bean用来处理request请求和渲染适合的视图。这些bean包含在spring框架中,并可在WebApplicationContext像其他普通bean一样配置。在下面有更具体的介绍。
Bean类型
说明
Controllers
MVC架构的'C'
Handler mappings
主要负责预处理,后置处理等一系列执行处理和满足特定条件下的控制器的执行,比如URL匹配等
View resolvers
负责根据视图名称解析视图
Locale resolver
负责语言本地化
Theme resolver
负责根据应用使用解析主题,比如可以定制个性化版面
multipart file
resolver
负责在html页面提供文件上传功能
Handler exception resolver(s)
负责提供处理异常功能
DispatcherServlet服务启动,一个请求到特定的DispatcherServlet,这时Dispatcher开始处理请求。下面是DispatcherServlet处理请求的完整过程。
1.寻找WebApplicationContext并且在request作为一个属性绑定方便控制器和其他成员使用。默认的绑定KEY是  DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE.
2.本地化解析器绑定到request中。如果没有使用该解析器,则不作任何事情。
3.主题解析器绑定到request中。如果没有使用该解析器,则不做任何事情。
4.如果multipart resolver被指定,则request会检测它。如果找到,request被包装成MultipartHttpServletRequest方便之后的其他成员处理。
5.寻找合适的的处理模块。如果找到,该处理模块相关的执行链如预处理,后置处理和控制器等都会被执行为模块的渲染作准备。
6.如果一个模块返回,则会渲染一个视图。如果没有模块返回,就没有视图被渲染,因为请求可能已经处理完成。
处理请求过程中抛出异常,请求会启用在WebApplicationContext声明的异常处理解析器。在发生异常时,可以使用这些解析器定义用户的行为。
DispatcherServlet也支持返回last-modification-date。DispatcherServlet首先寻找一个合适的处理映射并检测这处理模块是否实现了LastModified接口。如果是,这个接口的方法long getLastModified(request)返回值给客户端。
你也可以定制DispatcherServlet,通过在web.xml或者servlet初始参数上添加上下文容器的参数。具体如下:
参数
说明
contextClass
实现了WebApplicationContext接口的类,用来初始上下文环境。如果没有指定,则默认XmlWebApplicationContext
contextConfigLocation
传递给context实例的字符串,说明哪里可以找到context。这个字符串可被逗号拆分从而支持多个contexts。
namespace
WebApplicationContext命名空间。默认是 [servlet-name]-servlet
现在再查看源码加深理解。
首先服务器如tomcat启动时,就会将web.xml定义的context的bean都会实例化。入口当然是
org.springframework.web.servlet.DispatcherServlet。它是一个HttpServlet,所以必然会执行init()方法。我们查看根据上述的DispatcherServlet类图查看,发现HttpServletBean才有init()方法。
view plaincopy to clipboardprint?
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader));
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.
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
在这个方法里最重要的是initServletBean()。它会调用FrameworkServlet下的initServletBean(),这个方法改写了HttpServletBean的initServletBean()。代码如下:
view plaincopy to clipboardprint?
protected final void initServletBean() throws ServletException, BeansException {
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 (BeansException 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");
}
}
我们应该注意到这个方法有修饰符final,是不能子类重写的,这就体现了“开-闭原则(open for extension,close for modification)”我们再看initWebApplicationContext() 。
view plaincopy to clipboardprint?
protected WebApplicationContext initWebApplicationContext() throws BeansException {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
// No fixed context defined for this servlet - create a local one.
WebApplicationContext parent =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
wac = createWebApplicationContext(parent);
}
if (!this.refreshEventReceived) {
// Apparently not a ConfigurableApplicationContext with refresh support:
// triggering 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;
}
FindWebApplicationContext() 方法是从 ServletContext 属性取 WebApplicationContext ,如果没有配置 ServletContext 属性的话,则返回 null 。这时,先取回根 WebApplicationContext,key 是 WebApplicationContext. ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 。
而 createWebApplicationContext(parent) 方法是真正得到我们 DispatcherServlet 对应的 WebApplicationContext 。
view plaincopy to clipboardprint?
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent)
throws BeansException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
getContextClass().getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(getContextClass())) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + getContextClass().getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(getContextClass());
wac.setParent(parent);
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.setConfigLocation(getContextConfigLocation());
wac.addApplicationListener(new SourceFilteringListener(wac, this));
postProcessWebApplicationContext(wac);
wac.refresh();
return wac;
}
在这个方法里,首先调用 getContextClass() 获取默认的 ContextClass, 就是 public static final Class DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext. class 。
view plaincopy to clipboardprint?
ConfigurableWebApplicationContext  wac  =
(ConfigurableWebApplicationContext) BeanUtils. instantiateClass (getContextClass());
这行代码就是利用 XmlWebApplicationContext 的无参数构造函数实例化 XmlWebApplicationContext , 因为 XmlWebApplicationContext 实现了 ConfigurableWebApplicationContext 接口。接下来就是填充 XmlWebApplicationContext 的属性,这些属性部分在上面有提及过。这样在这个 WebApplicationContext 就可以使用了。