作者:youzhibing2904
遗留问题
在关于利用maven搭建ssm的博客,我们一起来磋商下问的最多的问题中,我遗留了一个问题:Spring mvc是何时、何地、如何将Model中的属性绑定到哪个浸染域,这里的浸染域指的是Servlet的四大浸染域;不理解问题背景的可以回过分去看看我的上篇博文。
明确的解答我会放到末了,在解答问题之前,我先和大家一起来捋一捋Spring mvc的事情事理。废话不多说,开始我们神秘的探险之旅!
在讲事情事理之前,我们先看一个大略的spring mvc(ssm)示例,以及实现的效果
工程代码地址:ssm-web
工程构造与效果如上所示,我们不做过多的探究,我们打起精神往下看本篇的重点
事情事理准备 - 资源的加载与初始化
1、DispatcherServlet 静态初始化
DispatcherServlet中有如下静态块
static { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized // by application developers. try { ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException(\"大众Could not load 'DispatcherServlet.properties': \公众 + ex.getMessage()); } }
这里会将DispatcherServlet.properties中的内容读取到DispatcherServlet的属性:private static final Properties defaultStrategies中,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.AcceptHeaderLocaleResolverorg.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolverorg.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMappingorg.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapterorg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverorg.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslatororg.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolverorg.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
指定了DispatcherServlet策略接口的默认实现,后续DispatcherServlet初始化策略的时候会用到
2、interceptor定义的加载
spring启动过程中会调用InterceptorsBeanDefinitionParser的parse方法来解析出我们自定义的interceptor定义,封装成MappedInterceptor类型的bean定义,并放到spring容器中;我们可以大略的认为spring容器中已经存在了我们自定义的interceptor的bean定义
3、DispatcherServlet初始化策略:initStrategies
DispatcherServlet的继续图如下
DispatcherServlet是一个Servlet,tomcat启动过程中会调用其init方法,一串的调用后,会调用DispatcherServlet的initStrategies方法
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}实例化步骤1中的默认实现,并添补到DispatcherServlet各个属性值中
4、DefaultAnnotationHandlerMapping的拦截器初始化
DispatcherServlet.properties种指定了两个默认的HandlerMapping:BeanNameUrlHandlerMapping、DefaultAnnotationHandlerMapping,这两者的类继续图如下(我们暂时只关注DefaultAnnotationHandlerMapping)
DefaultAnnotationHandlerMapping间接实现了ApplicationContextAware,那么在DefaultAnnotationHandlerMapping实例初始化过程中,会调用setApplicationContext(ApplicationContext applicationContext)方法,一串调用后,会来到AbstractUrlHandlerMapping的initApplicationContext()
@Overrideprotected void initApplicationContext() throws BeansException { extendInterceptors(this.interceptors); detectMappedInterceptors(this.mappedInterceptors); initInterceptors();}
初始化了DefaultAnnotationHandlerMapping的拦截器:interceptor
我们来看下详细的初始化过程,看看上面的顺序是否只是我个人的臆想?
可以看到,初始化顺序便是我们上面说的,不是我个人的意淫;此时的DefaultAnnotationHandlerMapping中有我们自定义的MyInterceptor。初始化过程我们须要关注的便是上述这些,下面我们一起看看详细要求的过程
要求的处理
要求从servlet的service开始,一起到DispatcherServlet的doDispatch,如下图
/ Process the actual dispatching to the handler. 将要求分发到详细的handler,也便是我们的controller <p>The handler will be obtained by applying the servlet's HandlerMappings in order. The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters to find the first that supports the handler class. <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers themselves to decide which methods are acceptable. @param request current HTTP request @param response current HTTP response @throws Exception in case of any kind of processing failure /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 { processedRequest = checkMultipart(request); multipartRequestParsed = processedRequest != request; // Determine handler for the current request. 决定哪个handler来处理当前的要求 // mappedHandler是由handler和interceptor凑集组成的一个实行链,有点类似FilterChain mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. 决定哪个adapter来处理当前的要求 // handlerMapping是找出适配的handler,而真正回调handler的是adapter HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = \公众GET\公众.equals(method); if (isGet || \"大众HEAD\"大众.equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { String requestUri = urlPathHelper.getRequestUri(request); logger.debug(\"大众Last-Modified value for [\"大众 + requestUri + \"大众] is: \"大众 + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // handler的前置处理,也便是调用适配当前url的interceptor的preHandler方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } try { // Actually invoke the handler. 真正调用handler mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); } finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } } applyDefaultViewName(request, mv); // handler的后置处理,也便是调用适配当前url的interceptor的postHandler方法 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } // 处理handler返回的结果,会调用适配当前url的interceptor的afterCompletion方法 // 这里会将相应结果返回给要求者 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Error err) { triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); return; } // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } }}
handlerMapping详细如何找到匹配当前url的handler(一样平常而言便是我们的controller)、handlerAdapter详细如何回调真正的handler,有兴趣的可以自行去跟下,我就不跟了。我们详细看下processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 这个与我们最初的疑问有关
processDispatchResult
可以看到model中的persons会被设置到request的attributes中,然后转发要求到show_person.jsp,转发过程中request浸染域的变量仍旧有效,以是show_person.jsp中的jstl标签和el表达式能够取到persons变量,末了将show_person.jsp中的内容添补好之后的静态内容返回给要求者;至此就完成了一次要求的相应
问题解答回到我们开篇的疑问:Spring mvc是何时、何地、如何将Model中的属性绑定到哪个浸染域?想必大家已经知道答案了
Controller中的model、ModelMap的注入由spring mvc完成,这个不是要求传入的参数,用于绑定变量到Servlet浸染域;默认情形下,在DispatcherServlet调用了真正的handler之后,将结果返回给要求者的过程中,将model、modelMap中的变量设置到了request的attributes中,转发的过程中,request中的变量仍旧有效,以是show_person.jsp中能取到persons这个变量,自此疑问得到解答
总结1、Spring MVC事情事理图
图是用的别人的,详细是谁的我也不记得了(捂脸)
2、DefaultAnnotationHandlerMapping在spring3.2中被废弃,更换成了RequestMappingHandlerMapping