【Spring MVC源码解析】(二)请求处理流程

一、概览

本篇来看看,一个web用户请求是如何被Spring mvc(DispatchServlet)处理的,先上图:

具体步骤:

第一步:发起请求到前端控制器(DispatcherServlet)

第二步:前端控制器请求HandlerMapping查找 Handler (可以根据xml配置、注解进行查找)

第三步:处理器映射器HandlerMapping向前端控制器返回Handler,HandlerMapping会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象,多个HandlerInterceptor拦截器对象),通过这种策略模式,很容易添加新的映射策略

第四步:前端控制器调用处理器适配器去执行Handler

第五步:处理器适配器HandlerAdapter将会根据适配的结果去执行Handler

第六步:Handler执行完成给适配器返回ModelAndView

第七步:处理器适配器向前端控制器返回ModelAndView (ModelAndView是springmvc框架的一个底层对象,包括 Model和view)

第八步:前端控制器请求视图解析器去进行视图解析 (根据逻辑视图名解析成真正的视图(jsp)),通过这种策略很容易更换其他视图技术,只需要更改视图解析器即可

第九步:视图解析器向前端控制器返回View

第十步:前端控制器进行视图渲染 (视图渲染将模型数据(在ModelAndView对象中)填充到request域)

第十一步:前端控制器向用户响应结果

MVC 九大组件

MultipartResolver(多文件上传组件)

  其实这是一个大家很熟悉的组件,MultipartResolver 用于处理上传请求,通过将普通的Request 包装成MultipartHttpServletRequest 来实现。MultipartHttpServletRequest可以通过getFile() 直接获得文件,如果是多个文件上传,还可以通过调用getFileMap得到Map<FileName, File> 这样的结构。

LocaleResolver(本地语言环境组件)

  在LocaleResolver用于从request 中解析出Locale, 在中国大陆地区,Locale 当然就会是zh-CN 之类,用来表示一个区域。这个类也是i18n 的基础。

ThemeResolver(模板主题处理组件)

  从名字便可看出,这个类是用来解析主题的。Spring MVC 中一套主题对应一个properties 文件,里面存放着跟当前主题相关的所有资源,如图片,css 样式等。创建主题非常简单,只需准备好资源,然后新建一个"主题名.properties" 并将资源设置进去,放在classpath 下,便可以在页面中使用了。Spring MVC 中跟主题有关的类有ThemeResolver, ThemeSource 和Theme。ThemeResolver 负责从request 中解析出主题名, ThemeSource 则根据主题名找到具体的主题, 其抽象也就是Theme, 通过Theme 来获取主题和具体的资源。

HandlerMappings(处理器映射组件)

  HandlerMapping 是用来查找Handler 的,也就是处理器,具体的表现形式可以是类也可以是方法。比如,标注了@RequestMapping 的每个method 都可以看成是一个Handler,由Handler 来负责实际的请求处理。HandlerMapping 在请求到达之后,它的作用便是找到请求相应的处理器Handler 和Interceptors。

HandlerAdapters (处理器适配器)

  从名字上看,这是一个适配器。因为Spring MVC 中Handler 可以是任意形式的,只要能够处理请求便行, 但是把请求交给Servlet 的时候,由于Servlet 的方法结构都是如doService(HttpServletRequest req, HttpServletResponse resp) 这样的形式,让固定的Servlet 处理方法调用Handler 来进行处理,这一步工作便是HandlerAdapter 要做的事。

HandlerExceptionResolvers(异常处理组件)

  从这个组件的名字上看,这个就是用来处理Handler 过程中产生的异常情况的组件。具体来说,此组件的作用是根据异常设置ModelAndView, 之后再交给render()方法进行渲染, 而render() 便将ModelAndView 渲染成页面。不过有一点,HandlerExceptionResolver 只是用于解析对请求做处理阶段产生的异常,而渲染阶段的异常则不归他管了,这也是Spring MVC 组件设计的一大原则分工明确互不干涉。

RequestToViewNameTranslator(视图预处理器组件)

  这个组件的作用,在于从Request 中获取viewName. 因为ViewResolver 是根据ViewName 查找View, 但有的Handler 处理完成之后,没有设置View 也没有设置ViewName, 便要通过这个组件来从Request 中查找viewName。

ViewResolvers(视图转换器)

  视图解析器,相信大家对这个应该都很熟悉了。因为通常在SpringMVC 的配置文件中,都会配上一个该接口的实现类来进行视图的解析。这个组件的主要作用,便是将String类型的视图名和Locale 解析为View 类型的视图。这个接口只有一个resolveViewName()方法。从方法的定义就可以看出,Controller 层返回的String 类型的视图名viewName,最终会在这里被解析成为View。View 是用来渲染页面的,也就是说,它会将程序返回的参数和数据填入模板中,最终生成html 文件。ViewResolver 在这个过程中,主要做两件大事,即,ViewResolver 会找到渲染所用的模板(使用什么模板来渲染?)和所用的技术(其实也就是视图的类型,如JSP 啊还是其他什么Blabla 的)填入参数。默认情况下,Spring MVC 会为我们自动配置一个InternalResourceViewResolver,这个是针对JSP 类型视图的。

FlashMapManager(重定向)

  说到FlashMapManager,就得先提一下FlashMap。FlashMap 用于重定向Redirect 时的参数数据传递,比如,在处理用户订单提交时,为了避免重复提交,可以处理完post 请求后redirect 到一个get 请求,这个get 请求可以用来显示订单详情之类的信息。这样做虽然可以规避用户刷新重新提交表单的问题,但是在这个页面上要显示订单的信息,那这些数据从哪里去获取呢,因为redirect 重定向是没有传递参数这一功能的,如果不想把参数写进url(其实也不推荐这么做,url 有长度限制不说,把参数都直接暴露,感觉也不安全), 那么就可以通过flashMap 来传递。只需要在redirect 之前, 将要传递的数据写入request ( 可以通过ServletRequestAttributes.getRequest() 获得) 的属性OUTPUT_FLASH_MAP_ATTRIBUTE 中,这样在redirect 之后的handler 中Spring 就会自动将其设置到Model 中,在显示订单信息的页面上,就可以直接从Model 中取得数据了。而FlashMapManager 就是用来管理FlashMap 的。

二、流程源码

FrameworkServlet

虽然在整体流程中,请求首先是被 DispatcherServlet 所处理,然后综合调度九大组件,但是实际上,FrameworkServlet 才是真正的入门。FrameworkServlet 会重写 HttpServlet 的

  • #doGet(HttpServletRequest request, HttpServletResponse response)
  • #doPost(HttpServletRequest request, HttpServletResponse response)
  • #doPut(HttpServletRequest request, HttpServletResponse response)
  • #doDelete(HttpServletRequest request, HttpServletResponse response)
  • #doOptions(HttpServletRequest request, HttpServletResponse response)
  • #doTrace(HttpServletRequest request, HttpServletResponse response)
  • #service(HttpServletRequest request, HttpServletResponse response)

等方法。而这些实现,最终会调用 #processRequest(HttpServletRequest request, HttpServletResponse response) 方法,处理请求。

@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
	// 获得请求方法
	HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
	// 处理 PATCH 请求
	if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
		processRequest(request, response);
	// 调用父类,分类处理request
	} else {
		super.service(request, response);
	}
}

service()中增加了对PATCH类型的处理,其余请求直接交给父类HttpServlet.service()处理

// HttpServlet.java

protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {
    String method = req.getMethod();

    if (method.equals(METHOD_GET)) {
        long lastModified = getLastModified(req);
        if (lastModified == -1) {
            // servlet doesn't support if-modified-since, no reason
            // to go through further expensive logic
            doGet(req, resp);
        } else {
            long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            if (ifModifiedSince < lastModified) {
                // If the servlet mod time is later, call doGet()
                // Round down to the nearest second for a proper compare
                // A ifModifiedSince of -1 will always be less
                maybeSetLastModified(resp, lastModified);
                doGet(req, resp);
            } else {
                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            }
        }
    } else if (method.equals(METHOD_HEAD)) {
        long lastModified = getLastModified(req);
        maybeSetLastModified(resp, lastModified);
        doHead(req, resp);
    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);
    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);
    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);
    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req,resp);
    } else if (method.equals(METHOD_TRACE)) {
        doTrace(req,resp);
    } else {
        //
        // Note that this means NO servlet supports whatever
        // method was requested, anywhere on this server.
        //

        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);
        
        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}

doGet & doPost & doPut & doDelete

这四个方法,都是直接调用 #processRequest(HttpServletRequest request, HttpServletResponse response) 方法,处理请求。代码如下:

// FrameworkServlet.java

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
	processRequest(request, response);
}

@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
	processRequest(request, response);
}

@Override
protected final void doPut(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
	processRequest(request, response);
}

@Override
protected final void doDelete(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
	processRequest(request, response);
}

也就说,FrameworkDispatch又将分类型的request处理,合流到processRequest()处理了。

processRequest

// FrameworkServlet.java
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
	// <1> 记录当前时间,用于计算 web 请求的处理时间
	long startTime = System.currentTimeMillis();
	// <2> 记录异常
	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());

	//将当前LocaleContext和servletRequestAttributes,设置到LocaleContextHolder和RequestAttrContextHolder中
	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();
		}
		// 打印请求日志,并且日志级别为 DEBUG 
		logResult(request, response, failureCause, asyncManager);
		//发布 ServletRequestHandledEvent 事件
		publishRequestHandledEvent(request, response, startTime, failureCause);
	}
}

processRequest()和核心语句是doService(request , response),这是一个模板方法,在DispatchServlet中实现。

DispatcherServlet

doService(HttpServletRequest request, HttpServletResponse response) 方法,DispatcherServlet 的处理请求的入口方法,整个Mvc 处理流程的顶层设计都在这里了,代码如下:

// DispatcherServlet.java

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
	// <1> 打印请求日志,并且日志级别为 DEBUG
    logRequest(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.
	// 设置 Spring 框架中的常用对象到 request 属性中
	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());

	//flashMapManager
	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 {
		//执行请求的分发
		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() 判断是否是inlcude请求,做快照备份,然后设置spring 容器常用属性,再交由doDispatch做具体的处理。

// DispatcherServlet.java

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);

			
			// 获得请求对应的 HandlerExecutionChain 对象
			mappedHandler = getHandler(processedRequest);
			if (mappedHandler == null) { 
                //如果获取不到,则根据配置抛出异常或返回 404 错误
				noHandlerFound(processedRequest, response);
				return;
			}

			
			// 获得当前 handler 对应的 HandlerAdapter 对象
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

			// 处理get、head请求的 last-modified
			String method = request.getMethod();
			boolean isGet = "GET".equals(method);
			if (isGet || "HEAD".equals(method)) {
				long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
				if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
					return;
				}
			}

			//前置处理 拦截器
			if (!mappedHandler.applyPreHandle(processedRequest, response)) {
				return;
			}

			//真正的调用 handler 方法,并返回视图
			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

			//如果需要异步,直接返回
			if (asyncManager.isConcurrentHandlingStarted()) {
				return;
			}

			// 当view为空时,根据request设置view
			applyDefaultViewName(processedRequest, mv);
			//后置处理 拦截器
			mappedHandler.applyPostHandle(processedRequest, response, mv);
		} catch (Exception ex) {
			dispatchException = ex; // <10> 记录异常
		} 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); // <10> 记录异常
		}

		//处理正常和异常的请求调用结果,渲染页面
		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 {
			// 删除上传请求的资源
			if (multipartRequestParsed) {
				cleanupMultipart(processedRequest);
			}
		}
	}
}

doDispatch()首先检查是不是上传请求,如果是上传请求,将request转换成MultipartHttpServletRequest,并设置multipartRequestParsed为true,用于最后释放资源。

然后使用getHandler方法获取处理器链HandlerExecutionChain,其中包含着与当前request相匹配的处理器handler和拦截器Interceptor

// DispatcherServlet.java

/** List of HandlerMappings used by this servlet. */
@Nullable
private List<HandlerMapping> handlerMappings;
   
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	if (this.handlerMappings != null) {
		// 遍历 HandlerMapping 数组
		for (HandlerMapping mapping : this.handlerMappings) {
			// 获得请求对应的 HandlerExecutionChain 对象
			HandlerExecutionChain handler = mapping.getHandler(request);
			// 获得到,则返回
			if (handler != null) {
				return handler;
			}
		}
	}
	return null;
}

如果获取不到,则调用 noHandlerFound根据配置抛出异常或返回 404 错误。

然后调用 getHandlerAdapter方法,获得当前 handler 对应的 HandlerAdapter 对象

// DispatcherServlet.java
/** List of HandlerAdapters used by this servlet. */
@Nullable
private List<HandlerAdapter> handlerAdapters;    

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
	if (this.handlerAdapters != null) {
		// 遍历 HandlerAdapter 数组
		for (HandlerAdapter adapter : this.handlerAdapters) {
 			// 判断是否支持当前处理器
			if (adapter.supports(handler)) {
				// 如果支持,则返回
				return adapter;
			}
		}
	}
	// 没找到对应的 HandlerAdapter 对象,抛出 ServletException 异常
	throw new ServletException("No adapter for handler [" + handler +
			"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

接下来调用 HandlerExecutionChain.applyPreHandle() 方法,拦截器的前置处理,即调用 HandlerInterceptor.preHandle() 方法。

处理完拦截器Interceptor,来到最关键的地方,让HandlerAdapter使用handler处理请求,并返回视图。这里,一般就会调用我们定义的 Controller 的方法。具体后面博文分析。

处理完请求后,执行Interceptor的后置处理postHandle,到这里请求处理的内容就完成了,接下来使用processDispatchResult处理返回结果,包括异常处理,渲染页面,触发Interceptor.afterCompletion等内容。

// DispatcherServlet.java

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
		@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
		@Nullable Exception exception) throws Exception {
	//标记,是否是生成的 ModelAndView 对象
	boolean errorView = false;

	//如果是否异常的结果
	if (exception != null) {
	    // 情况一,从 ModelAndViewDefiningException 中获得 ModelAndView 对象
		if (exception instanceof ModelAndViewDefiningException) {
			logger.debug("ModelAndViewDefiningException encountered", exception);
			mv = ((ModelAndViewDefiningException) exception).getModelAndView();
		// 情况二,处理异常,生成 ModelAndView 对象
		} else {
			Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
			mv = processHandlerException(request, response, handler, exception);
			// 标记 errorView
			errorView = (mv != null);
		}
	}

	// Did the handler return a view to render?
	if (mv != null && !mv.wasCleared()) {
		//渲染页面
		render(mv, request, response);
		//清理请求中的错误消息属性
		if (errorView) {
			WebUtils.clearErrorRequestAttributes(request);
		}
	} else {
		if (logger.isTraceEnabled()) {
			logger.trace("No view rendering, null ModelAndView returned.");
		}
	}

	if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
		// Concurrent handling started during a forward
		return;
	}

	//已完成处理 拦截器
	if (mappedHandler != null) {
		mappedHandler.triggerAfterCompletion(request, response, null);
	}
}

processDispatchResult处理异常的方式其实就是将相应的错误页面设置到view中

// DispatcherServlet.java

@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
		@Nullable Object handler, Exception ex) throws Exception {
	// Success and error responses may use different content types
    // 移除 PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 属性
	request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

	// Check registered HandlerExceptionResolvers...
	// <a> 遍历 HandlerExceptionResolver 数组,解析异常,生成 ModelAndView 对象
	ModelAndView exMv = null;
	if (this.handlerExceptionResolvers != null) {
		// 遍历 HandlerExceptionResolver 数组
		for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
			// 解析异常,生成 ModelAndView 对象
		    exMv = resolver.resolveException(request, response, handler, ex);
		    // 生成成功,结束循环
			if (exMv != null) {
				break;
			}
		}
	}
	// 情况一,生成了 ModelAndView 对象,进行返回
	if (exMv != null) {
		// ModelAndView 对象为空,则返回 null
		if (exMv.isEmpty()) {
			request.setAttribute(EXCEPTION_ATTRIBUTE, ex); // 记录异常到 request 中
			return null;
		}
		// We might still need view name translation for a plain error model...
		// 设置默认视图
		if (!exMv.hasView()) {
			String defaultViewName = getDefaultViewName(request);
			if (defaultViewName != null) {
				exMv.setViewName(defaultViewName);
			}
		}
		// 打印日志
		if (logger.isTraceEnabled()) {
			logger.trace("Using resolved error view: " + exMv, ex);
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Using resolved error view: " + exMv);
		}
		// 设置请求中的错误消息属性
		WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
		return exMv;
	}
	// 情况二,未生成 ModelAndView 对象,则抛出异常
	throw ex;
}

渲染页面具体在render()方法中执行

// DispatcherServlet.java

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
	// Determine locale for request and apply it to the response.
	// 从 request 中获得 Locale 对象,并设置到 response 中
	Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
	response.setLocale(locale);

	// 获得 View 对象
	View view;
	String viewName = mv.getViewName();
	// 情况一,使用 viewName 获得 View 对象
	if (viewName != null) {
		// We need to resolve the view name.
		view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
		if (view == null) { // 获取不到,抛出 ServletException 异常
			throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
					"' in servlet with name '" + getServletName() + "'");
		}
	// 情况二,直接使用 ModelAndView 对象的 View 对象
	} else {
		// No need to lookup: the ModelAndView object contains the actual View object.
		view = mv.getView();
		if (view == null) { // 获取不到,抛出 ServletException 异常
			throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
					"View object in servlet with name '" + getServletName() + "'");
		}
	}


	if (logger.isTraceEnabled()) {
		logger.trace("Rendering view [" + view + "] ");
	}
	try {
		// 设置响应的状态码
		if (mv.getStatus() != null) {
			response.setStatus(mv.getStatus().value());
		}
		//渲染页面
		view.render(mv.getModelInternal(), request, response);
	} catch (Exception ex) {
		if (logger.isDebugEnabled()) {
			logger.debug("Error rendering view [" + view + "]", ex);
		}
		throw ex;
	}
}

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wonder ZH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值