欢迎访问我的个人博客:http://zhangshuai.name/
首先在配置SpingMVC的时候,我们要么在web.xml中配置DispatcherServlet,要么采用Java config的方式继承AbstractAnnotationConfigDispatcherServletInitializer,这两种方式都离不开一个关键的类DispatcherServlet,所以来看看DispatcherServlet初始化是怎么完成的,以及它如何处理请求的。学习一个类的方式第一步当然是看看它的类图了,所以先来看看:
从这个类图中可以看到DispatcherServlet的超类主要可以分为三类
首先是图中的起点,Servlet接口:
它的三个方法与是与Servlet生命周期相关的,至于具体作用就是方法名字一样,init方法会在servlet被实例化的时候调用,且整个servlet生命周期只调用一次(可通过配置load-on-startup来设置实例化的时间,概括为不设置或者设置为负数时则在第一次请求该Servlet时实例化,否者在web容器启动时进行初始化,需要注意的是当值为正数时,值越小优先级越高)。destory当然就是在容器关闭的时候调用了,也是一次就好。service方法就是提供业务逻辑的地方了,可以多次执行,参数就是接收请求,返回结果。而在init方法中可以看到参数ServletConfig,这个就是对Servlet的一些配置,比如在web.xml中一般都会给DispatcherServlet配置contextConfigLocation,这个contextConfigLocation的值就是保存到ServletConfig中的。至此,我们就可以清楚为什么配置DispatcherServlet时要这么配置了:
<servlet>
<servlet-name>dispatcherServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:webConfig.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
那接下的自然就是ServletConfig了:
其中方法getServletName就是用于获取在web.xml中给Servlet配置的 ,方法getInitParamterNames就是获取之前配置的所有的的键,然后通过方法getInitparameter来获取到配置参数的值。最后的一个就是getServletContext了,其返回值ServletContext是java四大域对象中的一个,代表的是整个应用,可以通过这个对象在所有的Servlet中来共享数据。除此之外我们知道SpingMVC容器有个父容器(题外话:@Value不生效的一种常见原因就是没有将@PropertySource设置到合适的容器上),因此在web.xml中除了配置DispatcherServlet之外,我们往往还显示的配置它的父容器,也就是再配置一个ContextLoaderListener,而这个ContextLoaderListener我们一般也需要指定其配置文件的所在地址,就是这个父容器的contextConfigLocation,那么web.xml整体的配置就变成了:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:applicationContext.xmlparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<servlet>
<servlet-name>dispatcherServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:webConfig.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>dispatcherServletservlet-name>
<url-pattern>*.dourl-pattern>
servlet-mapping>
web-app>
至此,就能够知道平时为什么要这么去配置web.xml了。在这个文件中我们看到了两个contextConfigLocation,除了其中一个是给spring容器的,一个是给springMVC容器的,另一个区别就是spring容器的配置是写在中的,而这个值最终就是保存到ServletContext中的,而Servelet里面的是保存在ServletConfig里面的,他们的作用域就不一样了。
接下来再看看GenericServlet:
GenericServlet继承了Servlet和ServletConfig,所有它就同时拥有了上面介绍过的所有方法,就可以直接调用ServletConfig里面的方法,另外就是它拥有一个无参的init方法,还提供log方法。
再看哈HttpServlet:
HttpServlet是用Http协议实现的Servlet的基类,在图中可以看到它有许多的doXXX的方法,而这些XXX就是Http的请求方法,比如常见的get,post,put,与delete。它的Service方法为:
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest)req;
response = (HttpServletResponse)res;
} catch (ClassCastException var6) {
throw new ServletException("non-HTTP request or response");
}
this.service(request, response);
}
可以从方法上看出,它对参数做了一个强制类型转换,然后调用的service的一个重载函数
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求的方法类型
String method = req.getMethod();
//与浏览器缓存,304状态码有关,可看哈Last-Modified,If-modified-since的具体含义
long lastModified;
//根据不同的请求方法路由到不同的实现函数上,这里使用了模版模式来简化子类的实现
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}
if (ifModifiedSince < lastModified / 1000L * 1000L) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
//HTTP 501 Not Implemented 服务器错误响应码表示请求的方法不被服务器支持
resp.sendError(501, errMsg);
}
}
我们挑一个最常见的get方法来看看是什么样的:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
首先它是protected修饰符修饰的,意味着期望子类来实现具体业务逻辑,如果不进行重写的话,则直接返回客户端错误。其余的请求方法与该函数是类似的,但对doTrace和doOptions有默认的一个实现。
至此,DispactcherServlet的超类中与Sevlet规范相关的接口和类就理解清楚了,在这个部分就能够理解平时配置web.xml为什么需要这么配置了。
首先Aware是一个标记接口,其本身是没有定义方法的,它的作用是仅用于标记某个类可以通过回调的方式被Spring容器或者某个特定的框架对象通知。XXXAware可以理解为某个类可以感知XXX,如果某个类需要使用Sping容器的的XXX,就可以实现XXXAware接口,这个接口有个setXXX方法,通过这个方法就可以获取到Spring容器提供的XXX,由此可以知道Dispatchervlet是可以获取到容器的Enviroment和ApplicationContext的,而关于这两个类的具体含义会在Sping源码分析里面再做总结。而XXXCapable则表示某个类拥有一个xxx,并且可以通过getXXX将其暴露出去。而在HttpServlet中暴露的Enviroment对象是StandardServletEnvironment,它不仅包括ServletConfig,ServletContext,JNDI的参数,还有这系统参数和系统属性,这些参数和值都封装在其PropertySources下。
现在终于进入到了正题,SpringMVC自定义了三个Servlet,分别是HttpServlet,FrameworkServlet,DispatcherServlet。我们已经知道对于一个Servlet来说,它的生命周期开始于init()方法,所以看哈HttpServletBean的init()方法。
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//这是一个模版方法,在HttpServletBean中设置为空,子类重写这个方法进行一些初始化
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
//FrameworkServlet的入口方法
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
pvs中保存的是设置的值,
resourceLoader保存了servletContext对象,
BeanWrapper是Spring提供的用于操作JavaBean属性的工具,使用它可以直接的修改一个对象的属性,这整个init方法就可以概括为对DispatcherServlet的一个初始化,然后再调用initServletBean()方法。
因此,转入到FrameworkServlet,可以看到其initServletBean()方法为:
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 | 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");
}
}
除开日志之外,就只有两个方法了,一个是用于初始化WebApplicationContext,另一个是用于初始化FrameworkServlet,而由
protected void initFrameworkServlet() throws ServletException {
}
可知initFrameworkServlet是一个模版方法,在FrameworkServlet中还并没有用到它,并且DispatcherServlet也没有用到它。因此FrameworkServlet的主要作用就是用于初始化WebApplicationContext。主要完成的工作是初始化WebApplicationContext,根据情况调用onRefresh方法,将WebApplicationContext注册到ServletContext中。
进入该函数可以看到:
```java
protected WebApplicationContext initWebApplicationContext() {
//默认情况下Spring会将自己的容器设置成ServletContext的属性,因此可以从ServletContext中获取到根WebApplicationContext
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
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
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
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已经通过FrameworkServlet的构造函数直接注入到这个对象中,这时仅仅需要对其进行一些配置,配置的具体为先判断是否已经激活,如果没有的话,就设置其父容器,然后调用configureAndRefreshWebApplicationContext方法,这个方法稍后再进行介绍。
第二种是webApplicationContext已经在ServletContext中,这个和之前获取父WebApplicationContext类似,都是在ServletContext中去获取。
第三种,也是最常见的一种,是需要在Framework自己通过createWebApplicationContext方法去创建一个WebApplicationContext并对其完成初始化,
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
//默认类型为XmlWebApplicationContext
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");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
configureAndRefreshWebApplicationContext(wac);
return wac;
}
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();
}
可以看出流程还是很简单的,就是通过反射创建一个类型为contextClass的对象,然后设置一些属性,再调用configureAndRefreshWebApplicationContext来设置一些属性,就返回了。中间需要注意的就是
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
为web容器注册了一个监听器ContextRefreshListener,这个监听器是FramwworkServlet的内部类,监听的事件是ContextRefreshedEvent。其动作是:
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}
public void onApplicationEvent(ContextRefreshedEvent event) {
this.refreshEventReceived = true;
onRefresh(event.getApplicationContext());
}
可以看出当ContextRefreshedEvent事件发生时,将标志位refreshEventReceived设置为true,并调用了onRefresh方法。再回到FrameworkServlet,可以看到在获取并设置了WebApplicationContext后,根据refreshEventReceived来决定了是否调用onrefresh方法,然后根据publishContext标志判断是否将这个对象放入到ServletContext中。根据前面的流程我们知道最终一定会执行onrefresh方法的,这也是个模版方法,留给子类来实现。所以终于要进入到核心DispatcherServletle了,其onRefresh方法为:
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
可以看到DispatcherServlet的onrefresh方法只是简单的调用了initStrategies,这个方法就是用于初始化SpringMVC的九大组件:先来个简单介绍,
private void initHandlerMappings(ApplicationContext context) {
//handlerMappings是一个List,也就是说容器中可以有多个HandlerMapping
this.handlerMappings = null;
//this.detectAllHandlerMappings默认为true
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
//平时设置的@Order在这里生效
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
//从容器中获得id为"handlerMapping"的bean,且它的类型必须为HandlerMapping;
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
//如果没有这个组件那么就设置一个默认的Bean当做这个组件的默认值
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
可以看到initHandlerMappings的主要内容就是找到一个或者多个HandlerMapping的实现类,然后将其设置到属性this.handlerMappings,它会先从容器中去找,看是否有设置的Bean,如果没有的话就设置一个默认值,至于这个默认值的来源可以看到:
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
//key就是HandlerMapping的类名称
String key = strategyInterface.getName();
//defaultStrategies是一个Properties类型,这个Properties的资源来源于DispatcherServlet.properties文件
String value = defaultStrategies.getProperty(key);
if (value != null) {
//将key用逗号分割成数组
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<>(classNames.length);
for (String className : classNames) {
try {
//通过反射 根据类名创建Class对象
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
}
catch (ClassNotFoundException ex) {
throw new BeanInitializationException(
"Could not find DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]", ex);
}
catch (LinkageError err) {
throw new BeanInitializationException(
"Unresolvable class definition for DispatcherServlet's default strategy class [" +
className + "] for interface [" + key + "]", err);
}
}
return strategies;
}
else {
return new LinkedList<>();
}
}
这个方法主要就是从一个Properties对象里面根据组件名找到默认组件实现的类全名称,然后通过反射的方式创建该组件,再返回给DispatcherServlet。而这个Properties内容的来源是DispatcherServlet.properties。所以我们看看这个文件的内容,
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
这里就是很多个键值对,键是组件的接口名,值是组件的默认实现类,这里面没有MultipartResolver,因为不一定需要上传文件,上传文件的话也可以通过别的方式实现。需要注意的是这个只是默认配置,并不是最优的配置,也不是推荐的配置,其中有的类都已经被标记过时了。
至此,DispatcherServlet在容器初试它是做的初试化工作就已经全部完成了。来做个总结吧。
在前文中,我们根据DispatcherServlet的类图,和Servlet的生命周期的定义,看了其启动流程。知道了平时配置SpringMVC时为什么需要这么配置。也知道了