本文将讲解Spring MVC的工作流程,Spring MVC的入口是DispatcherServlet,下面我们来看下DispatcherServlet的继承结构。
从上图我们可以看出DispatcherServlet实际上就是一个Servlet。
DispatcherServlet初始化
既然我们知道了DispatcherServlet是一个Servlet,那么在Spring MVC初始化的时候就会调用Servlet的init方法。因此我们顺着这个继承体系结构找init方法。在子类GenericServlet中实现了Servlet接口的init方法,并且还实现了一个空的init方法提供给子类覆写。
public void init(ServletConfig config) throws ServletException {
this.config = config;
// 是一个钩子方法用来给子类覆盖的。
this.init();
}
因此我们看到这个空的init方法,我们发现在HttpServletBean中覆写了这个方法。
@Override
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()));
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.
// 注意重点来了,这又是一个模板方法提供给子类覆写
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
我们看到initServletBean这个方法,在FrameworkServlet中被覆写。在这个方法里面调用了initWebApplicationContext方法进行初始化。
@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 {
// 这里是初始化的重点,初始化ApplicationContext
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");
}
}
protected WebApplicationContext initWebApplicationContext() {
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) {
// 这里同样是个模板方法,这个就是我们的核心了,被DispatcherServlet覆写,从而进入了我们的入口,去执行真正的初始化
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;
}
从上面代码我们可以发先在一定条件下会执行onRefresh方法,而DispatcherServlet通过重写这个方法就开始真正执行初始化SpringMVC操作了。
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
// 文件上传
initMultipartResolver(context);
// 当前环境处理器,可以对不同区域的用户显示不同的结果
initLocaleResolver(context);
// 主题解析
initThemeResolver(context);
// 将请求映射到处理器
initHandlerMappings(context);
// 处理器适配器,进行多类型参数的动态匹配
initHandlerAdapters(context);
// 根据处理请求过程中的异常设置view
initHandlerExceptionResolvers(context);
// 根据viewname查找view
initRequestToViewNameTranslator(context);
// 页面渲染解析器
initViewResolvers(context);
// 用来管理FlashMap的,FlashMap主要用在redirect中传递参数
initFlashMapManager(context);
}
上面initStrategies方法初始化了SpringMVC的九大组件,至此初始化工作全部完成,详细的内容读者可以自行深入了解。
DispatcherServlet处理流程
Servlet处理请求主要是调用service方法去处理。我们发现在HttpServlet中实现了该方法,并且根据不同类型将请求路由到doGet、doHead、doPost、doPut、doDelete、doOption、doTrace,并做了doHead、doOption、doTrace的默认实现。
在 FrameworkServlet重写了service、doGet、doPost、doPut、doDelete、doOption、doTrace,在service增加了对PATCH类型的处理。并且我们发现,在这些重写的方法里面都调用了processRequest方法,毫无疑问这个就是我们要关注的重点。
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
// 注意注意注意,模板方法提供给子类覆写
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
}
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
我们发现在processRequest中有个模板方法doService,这个方法被DispatcherServlet覆写了,毫无疑问这个就是方法就是DispatcherServlet处理Htpp请求的方法。
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
}
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try {
// 前面对htpp请求进行了一大堆的属性赋值,然后调用了这个方法,那么这个方法就是真正执行请求处理的地方
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
我们发现doService方法中前面对htpp请求进行了一大堆的属性赋值,然后调用了doDispatch这个方法,那么这个方法就是真正执行请求处理的地方,至此DispatcherServlet的处理流程到此结束。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 1.检查是否是文件上传的请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// 2.取得处理当前请求的controller,这里也称为hanlder,处理器,
// 第一个步骤的意义就在这里体现了.这里并不是直接返回controller,
// 而是返回的HandlerExecutionChain请求处理器链对象,
// 该对象封装了handler和interceptors.
mappedHandler = getHandler(processedRequest);
// 如果handler为空,则返回404
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
//3. 获取处理request的处理器适配器handler adapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
// 处理 last-modified 请求头
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 4.实际的处理器处理请求,返回结果视图对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 结果视图对象的处理
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
// 请求成功响应之后的方法
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
Spring MVC处理请求过程
具体步骤:
1、发送请求,被DispatcherServlet接收到。
2、DispatcherServlet将请求交给HandlerMapping处理器映射器,HandlerMapping根据请求的url获取到对应的Handler(Controller处理器)。
3、然后HandlerAdapter处理器适配器,通过调用supports方法得到适合处理Handler的适配器,然后调用适配器的handle方法通过反射调用对应处理器的方法,并返回ModelAndView。
4、最后通过ViewResolvers视图解析器将ModelAndView解析成试图(View)放回给前端。
5、前端对View进行视图渲染。
Spring MVC中的核心角色
HandlerMapping
是一个接口,主要记录了Url和Controller的对应关系。每次处理请求就是从HandlerMapping中获取对应的Controller。该接口主要有以下几个实现类。
BeanNameUrlHandlerMapping :通过对比url和bean的name找到对应的对象
SimpleUrlHandlerMapping :也是直接配置url和对应bean,比BeanNameUrlHandlerMapping功能更多
DefaultAnnotationHandlerMapping : 主要是针对注解配置@RequestMapping的,已过时
RequestMappingHandlerMapping :取代了上面一个
HandlerAdapter
同样是一个接口,主要是根据HandlerMapping获取的Controller,找到一个可以处理Controller的HandlerAdapter,然后通过反射调用Controller的具体方法获得ModelAndView。主要的实现类有。
AnnotationMethodHandlerAdapter主要是适配注解类处理器,注解类处理器就是我们经常使用的@Controller的这类处理器
HttpRequestHandlerAdapter主要是适配静态资源处理器,静态资源处理器就是实现了HttpRequestHandler接口的处理器,这类处理器的作用是处理通过SpringMVC来访问的静态资源的请求。
SimpleControllerHandlerAdapter是Controller处理适配器,适配实现了Controller接口或Controller接口子类的处理器,比如我们经常自己写的Controller来继承MultiActionController.
SimpleServletHandlerAdapter是Servlet处理适配器,适配实现了Servlet接口或Servlet的子类的处理器,我们不仅可以在web.xml里面配置Servlet,其实也可以用SpringMVC来配置Servlet,不过这个适配器很少用到,而且SpringMVC默认的适配器没有他,默认的是前面的三种。
HttpRequestHandlerAdapter主要是适配静态资源处理器,静态资源处理器就是实现了HttpRequestHandler接口的处理器,这类处理器的作用是处理通过SpringMVC来访问的静态资源的请求。
SimpleControllerHandlerAdapter是Controller处理适配器,适配实现了Controller接口或Controller接口子类的处理器,比如我们经常自己写的Controller来继承MultiActionController.
SimpleServletHandlerAdapter是Servlet处理适配器,适配实现了Servlet接口或Servlet的子类的处理器,我们不仅可以在web.xml里面配置Servlet,其实也可以用SpringMVC来配置Servlet,不过这个适配器很少用到,而且SpringMVC默认的适配器没有他,默认的是前面的三种。