上一篇文章(1)(2)分析了Spring是如何调用和执行控制器方法,以及处理返回结果的,如今咱们就分析下Spring如何解析返回的结果生成响应的视图。html
View ---View接口表示一个响应给用户的视图,例如jsp文件,pdf文件,html文件等,它的定义以下
java
public interface View { //HttpServletRequest中的属性名,其值为响应状态码 String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus"; //HttpServletRequest中的属性名,前一篇文章用到了该变量,它的对应值是请求路径中的变量,及@PathVariable //注解的变量 String PATH_VARIABLES = View.class.getName() + ".pathVariables"; //该视图的ContentType String getContentType(); //渲染该视图 void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response); }
该接口只有两个方法定义,分别代表该视图的ContentType和如何被渲染。Spring中提供了丰富的视图支持,几乎包含全部你想获得的,而且Spring的视图拓展性很好,你能够轻松实现本身的视图。下面是View的一些实现类(不是所有)ios
ViewResolver --- ViewResolver接口定义了如何经过view 名称来解析对应View实例的行为,它的定义至关简单:web
public interface ViewResolver { View resolveViewName(String viewName, Locale locale) throws Exception; }
该接口只有一个方法,经过view name 解析出View。一样Spring提供了丰富的ViewResolver实现用来解析不一样的View:spring
上一篇文章咱们分析了处理器方法如何被调用以及获取了返回值,可是Spring是如何处理返回值并响应给客户呢?这就是这节要分析的,根据返回值解析出对应的视图。浏览器
private ModelAndView invokeHandleMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); modelFactory.initModel(webRequest, mavContainer, requestMappingMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request); chain.addDelegatingCallable(getAsyncCallable(mavContainer, modelFactory, webRequest)); chain.setAsyncWebRequest(createAsyncWebRequest(request, response)); chain.setTaskExecutor(this.taskExecutor); //上一篇文章分析到这里,调用了处理器方法并处理了返回值 requestMappingMethod.invokeAndHandle(webRequest, mavContainer); if (chain.isAsyncStarted()) { return null; } //这里是根据返回值返回ModelAndView了 return getModelAndView(mavContainer, modelFactory, webRequest); }
上面的代码在上一篇文章中已经分析到了invokeAndHandle方法,该方法调用了处理器方法,并处理了返回值,剩下的就是如何将返回值呈现给用户了,咱们看getModelAndView的实现:缓存
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { //主要是同步model属性,而且将BindingResult添加到model中来 modelFactory.updateModel(webRequest, mavContainer); //是否直接处理请求,如@ResponseBody if (mavContainer.isRequestHandled()) { return null; } ModelMap model = mavContainer.getModel(); ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model); if (!mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); }//若是model是RedirectAttributes,进行flashAttributes的处理 //即将flashAttribute属性添加到request的Output FlashMap中,以被重定向后的request获取 if (model instanceof RedirectAttributes) { Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; }
上面的代码是根据方法执行完后生成的model和视图名等信息生成ModelAndView对象,该对象维护了一个View和Model的对应关系,以便在View中能够访问Model的属性。mvc
上面的代码还有一个对RedirectAttributes的处理,这里咱们来分析下是个什么回事?咱们知道request中的属性只能在request范围内访问到,一旦执行重定向,重定向后的request并访问不到前面设置的属性了,虽然放到Session中能够在不一样的request中共享这些属性,可是有时候放到Session中显得没有必要,毕竟不少属性只须要在“某次操做”中有用(重定向操做对用户来讲实际上是一次操做,由于重定向是浏览器执行的,对用户透明的。
app
所以为了解决这个问题,Spring引入了RedirectAttributes概念,即添加到RedirectAttributes中的属性,在重定向后依旧能够获取到,而且获取到之后,这些属性就会失效,新的request便没法获取了,这样就方便了开发者,一样也节省了内错占用。框架
那Spring是怎么实现的呢?这里牵扯到了FlashMap这一律念,Spring会默认为每个请求添加两个FlashMap属性,一个是InputFlashMap,另外一个是OutputFlashMap,其中InputFlashMap便包含了上一个请求在重定向到该请求前设置的属性值,也就是上一个请求的OutputFlashMap,看下面的图方便理解:
下面是DispatcherServlet中doService中的代码片断,在调用doDispatch前便设置了InputFlashmap和OutputFlashMap:
//尝试获取该request的InputFlashMap FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap != null) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } //设置该请求的OutputFlashMap request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); //设置该请求的FlashMapManager,用来管理InputFlashMap和OutputFlashMap request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
了解了FlashMap的概念咱们继续往下看,前面咱们已经获取到了请求的ModelAndView对象,这时invokeHandleMethod执行完毕将控制权交给了doDispatch,咱们看怎么处理ModelAndView:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncChain.isAsyncStarted()) {///异步调用,暂不关心 mappedHandler.applyPostHandleAsyncStarted(processedRequest, response); return; }//若是ModelAndView中没有设置视图名,则设置默认视图(大体是prefix/请求路径/suffix) applyDefaultViewName(request, mv); //执行拦截器的后处理器 mappedHandler.applyPostHandle(processedRequest, response, mv); //处理分派结果,响应用户 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
重点就在最后一行,咱们继续追踪:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) { boolean errorView = false; //出现异常,进行异常处理,暂不关心 if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } } // 若是返回View须要渲染? if (mv != null && !mv.wasCleared()) { //惊醒视图的渲染,咱们主题 render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { } //调用拦截器的afterComplete if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, null); } }
上面的代码咱们着重看render方法是怎样实现的,这是咱们今天的主题啊:
protected void render(ModelAndView mv,HttpServletRequest request,HttpServletResponse response){ // 肯定当前请求的Locale,并设置Response Locale locale = this.localeResolver.resolveLocale(request); response.setLocale(locale); View view;//ModelAndView中的View还只是名称,须要解析成View对象 if (mv.isReference()) { view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); if (view == null) { throw new ServletException( "Could not resolve view with name '"); } } else {//直接获取视图对象 view = mv.getView(); if (view == null) { throw new ServletException("ModelAndView [" + mv + "] "); } } //委托视图对象进行渲染 view.render(mv.getModelInternal(), request, response); }
上面的代码涉及了两个重要步骤,视图名的解析和视图的渲染,这一小节咱们来说解视图名的解析,也就是ViewResolver了:
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception { for (ViewResolver viewResolver : this.viewResolvers) { View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } return null; }
咱们查看resolveViewName方法,发现其中有一个viewResolvers实例变量,若是你看过前面的几篇文章,你获取会记得handlerMappings, handlerAdapters等变量,不错他们是一伙的,都是在DispatcherServlet初始化时完成设置的,而且咱们能够在配置文件中定义咱们本身的HandleMappings, HandlerAdapters,ViewResolvers等(这里不讲解怎样设置了),可是若是咱们不设置的话Spring也会为咱们设置一些默认值:
org.springframework.web.servlet.HandlerMapping = org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping org.springframework.web.servlet.HandlerAdapter= org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter org.springframework.web.servlet.HandlerExceptionResolver= org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\ 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
上面代码片断来自Spring MVC包中的DispatcherServlet.properties属性文件中,这里Spring为咱们默认设置了诸多处理器,解析器等,能够看出在咱们不进行ViewResolver设置的状况下,默认实现是InternalResourceViewResolver。由第一节的ViewResolver继承层次图咱们知道,InternalResourceViewResolver继承自UrlBasedViewResolver, 而UrlBasedViewResolver继承自AbstractCachingViewResolver,其实这就是Spring的聪明之处,为了提升性能,Spring中充斥着缓存策略,这不,在试图解析中也使用了缓存。这样只需在第一次解析时完成整个的视图建立工做,后续的请求只需从缓存中索取便可了。
这里的InternalResourceViewResolver主要是用来支持Jsp文件的,换句话说,若是你的系统中只用到了jsp文件而没有模板引擎等框架,这个ViewResolver就够你用了,你也就无需在配置文件中画蛇添足的写上该ViewResolver了。下面咱们就来看它的实现吧:
public View resolveViewName(String viewName, Locale locale) throws Exception { //若是没有被缓存呢,只能建立了 if (!isCache()) { return createView(viewName, locale); } else {//检索缓存中的视图对象 Object cacheKey = getCacheKey(viewName, locale); synchronized (this.viewCache) { View view = this.viewCache.get(cacheKey); if (view == null && (!this.cacheUnresolved || !this.viewCache.containsKey(cacheKey))) { // Ask the subclass to create the View object. view = createView(viewName, locale); if (view != null || this.cacheUnresolved) { this.viewCache.put(cacheKey, view); } } return view; } } }
方法很简单,咱们接着看是怎样建立视图的:
protected View createView(String viewName, Locale locale) throws Exception { // 当前ViewResolver没法解析该视图名,返回null if (!canHandle(viewName, locale)) { return null; } // view名称以redirect:开头,即重定向视图解析 if (viewName.startsWith(REDIRECT_URL_PREFIX)) { String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length()); RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative() , isRedirectHttp10Compatible()); return applyLifecycleMethods(viewName, view); } // view名称以forward:开头,即转发视图解析 if (viewName.startsWith(FORWARD_URL_PREFIX)) { String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length()); return new InternalResourceView(forwardUrl); } // 正常状况下,让父类建立吧 return super.createView(viewName, locale); }
建立视图时,Spring会检查视图名,有三种状况redirect视图,forward视图,普通视图,进行了不一样处理。对于redirect视图,spring获取redirectURL并建立了RedirectView对象,而后执行了一下bean实例的生命周期方法,没什么实质性东西,咱们不关心。对于转发视图,建立了InternalResourceView对象,上面说的这两种对象的渲染过程咱们过会会降到的。这里你们先记住。第三种状况呢,又交给了父类处理,咱们继续看看吧:
protected View createView(String viewName, Locale locale) throws Exception { return loadView(viewName, locale); } @Override protected View loadView(String viewName, Locale locale) throws Exception { AbstractUrlBasedView view = buildView(viewName); View result = applyLifecycleMethods(viewName, view); return (view.checkResource(locale) ? result : null); }
父类的createView方法又委托给了loadView,而loadView是抽象的由子类实现,好吧,我只能说这个地方真饶。咱们继续看loadView中有一个buildView方法,看着不错哦:
protected AbstractUrlBasedView buildView(String viewName) throws Exception { //根据ViewClass实例化该Class AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils .instantiateClass(getViewClass()); //设置视图的url,prefix/viewName/suffix view.setUrl(getPrefix() + viewName + getSuffix()); String contentType = getContentType(); if (contentType != null) {//设置ContentType view.setContentType(contentType); }//设置请求上下文属性 view.setRequestContextAttribute(getRequestContextAttribute()); view.setAttributesMap(getAttributesMap()); if (this.exposePathVariables != null) {//设置是否暴露PathVariable view.setExposePathVariables(exposePathVariables); } return view; }
上面的代码又出来个ViewClass, prefix,suffix,他们又是个什么东西呢?其实咱们知道在配置InternalResourceViewResolver时能够指定一个viewClass,prefix,suffix,没错,就是他们,先说prefix,suffix,咱们看到了它会分别添加到viewName的先后,组成视图的URL。那个viewClass呢就是视图的class对象类型了。咱们看InternalResourceViewResolver的构造器:
public InternalResourceViewResolver() { Class viewClass = requiredViewClass(); if (viewClass.equals(InternalResourceView.class) && jstlPresent) { viewClass = JstlView.class; } setViewClass(viewClass); }
会发如今咱们没有指定的状况下默认是JstlView哦。根据第一季中的图片咱们能够知道它继承自InternalResourceView。到此为止呢咱们的视图对象已经建立完毕。
咱们这里只解析了Spring默认状况下的InternalResourceViewResolver的解析过程,默认状况下解析的视图类型是JstlView。若是是Redirect的话则是RedirectView。
视图解析出来了,下面就是要将视图渲染给用户显示了。这里咱们依旧只讲解默认的JstlView的渲染过程,固然还有RedirectView的。
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); prepareResponse(request, response); renderMergedOutputModel(mergedModel, request, response); } protected Map<String, Object> createMergedOutputModel(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) { @SuppressWarnings("unchecked") //若是须要保留PathVariable Map<String, Object> pathVars = this.exposePathVariables ? (Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null; //联合动态和静态属性 int size = this.staticAttributes.size(); size += (model != null) ? model.size() : 0; size += (pathVars != null) ? pathVars.size() : 0; Map<String, Object> mergedModel = new HashMap<String, Object>(size); mergedModel.putAll(this.staticAttributes); if (pathVars != null) { mergedModel.putAll(pathVars); } if (model != null) { mergedModel.putAll(model); } // Expose RequestContext? if (this.requestContextAttribute != null) { mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel)); } return mergedModel; }
上面代码是AbstractView中的方法,也就是全部视图都会执行的操做,就是将静态属性和动态生成的属性合并,咱们重点看
renderMergedOutputModel方法,子类会覆盖该方法,实现不一样的逻辑。咱们来看JstlView和RedirectView的实现,首先JstlView :
protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request,HttpServletResponse response){ //肯定执行请求转发的request对象 HttpServletRequest requestToExpose = getRequestToExpose(request); //将model中的属性暴露为请求属性表中 exposeModelAsRequestAttributes(model, requestToExpose); //暴露MessageResource exposeHelpers(requestToExpose); //肯定转发的路径,也就是View的URL,但会检查是否会进入死循环,即跟当前请求同一个路径 String dispatcherPath = prepareForRendering(requestToExpose, response); //生成RequestDispatcher对象 RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath); if (rd == null) { throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +"]"); } //include操做 if (useInclude(requestToExpose, response)) { response.setContentType(getContentType()); rd.include(requestToExpose, response); } else { //执行转发,暴露属性到转发请求中 exposeForwardRequestAttributes(requestToExpose); rd.forward(requestToExpose, response); } }
方法看着很长其实思路比较简单,主要就是调用了RequestDispatcher的include 或forward的方法,将请求转发到指定URL。JstlView的视图渲染相对简单,咱们来看RedirectView的渲染:
protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws IOException { //获取重定向的路径,也就是前面生成RedirectView时设置的URL,但会进行相对路径的处理 String targetUrl = createTargetUrl(model, request); //调用用户注册的RequestDataValueProcessor的process方法,一般用不到,无论 targetUrl = updateTargetUrl(targetUrl, model, request, response); //哈哈,这里就是咱们上面讲到的FlashMap的处理啦,是怎样实现的呢? //咱们知道前面将RedirectAttributes的属性都设置到了当前请求的OutputFlashMap中了,这里再取出来。 //设置flashMap的目标请求路径,用来比对下次请求的路径,若是匹配,将其中的属性设置到请求属性表中 FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request); if (!CollectionUtils.isEmpty(flashMap)) { UriComponents uriComponents = UriComponentsBuilder.fromUriString(targetUrl).build(); flashMap.setTargetRequestPath(uriComponents.getPath()); flashMap.addTargetRequestParams(uriComponents.getQueryParams()); } //将flashMap交由FlashMapManager管理。 FlashMapManager flashMapManager = RequestContextUtils.getFlashMapManager(request); flashMapManager.saveOutputFlashMap(flashMap, request, response); //返回结果,设置响应头304. sendRedirect(request, response, targetUrl.toString(), this.http10Compatible); }
到此为止,咱们的试图解析,渲染过程就彻底分析完了,获取到目前为止有点晕,其实好好思考下,Spring在视图解析,和渲染这块给了咱们足够的拓展空间。
Spring对视图的支持至关完善,默认的JSP不用说,PDF,Excel, 等,还包括主流的模板引擎,像FreeMarker, Tiles等,能够参考第一张图片。固然你彻底也能够实现本身的View,以及ViewResolver,来解析自定义的视图。不过应该没多大必要。