SpringMvc中使用ModelAndView返回值,为什么将数据存放在request域中【源码分析】

本文深入解析SpringMVC框架中ModelAndView对象如何将数据存储在Request域对象中,通过源码分析,详细解释了从Handler执行到视图渲染的全过程,包括ModelAndView的创建、数据传递及视图解析机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SpringMvc中用ModelAndView返回值为什么数据保存到Request域对象中【源码分析】

一:SpringMvc中常用Handler的几种返回值。

  • 通常有两大类型

    1. 返回String类型

    2. 返回ModelAndView类型

    • String类型的返回
    • ModelAndView返回

在这里插入图片描述
在这里插入图片描述
下面主要解析为何通过ModelAndView中的addObject方法 数据将保存在request域对象中。

  • 因为我们可能都使用过这种方式,而且都明白数据是放在请求域中,那么底层到底是如何存放得到呢?

二:用Debug的方式来进行研究。

首先确保测试环境都搭建好。(我在这里面演示一下测试环境准备)

  1. 新建一个servlet

在这里插入图片描述

2.配置好web-xml,springmvc.xml
在这里插入图片描述

3.将断点分别打在servlet中,DispatcherServlet中

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

接着断点将进入到1043行,此时mv是null

在这里插入图片描述

在这里插入图片描述

接着进入到下面这个断点中去。我们追加进去:

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws 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);
			}
		}

		// 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) {
			// Exception (if any) is already handled..
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}

在这个方法中我们先过滤掉异常等方法,进入到render()方法:这是一个渲染方法:

在这里插入图片描述

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
		// Determine locale for request and apply it to the response.
		Locale locale =
				(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
		response.setLocale(locale);

		View view;
		String viewName = mv.getViewName();
		if (viewName != null) {
			// We need to resolve the view name.
			view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
			if (view == null) {
				throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
						"' in servlet with name '" + getServletName() + "'");
			}
		}
		else {
			// No need to lookup: the ModelAndView object contains the actual View object.
			view = mv.getView();
			if (view == null) {
				throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
						"View object in servlet with name '" + getServletName() + "'");
			}
		}

		// Delegate to the View object for rendering.
		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;
		}
	}

在该方法中首先看方法的参数部分
在这里插入图片描述

在该方法的参数部分此时将mv,传过来(这个mv包含了此次请求的视图部分也就是请求路径和数据部分),接着下一步走。走到1347行时,我们可以看到这时候开始来取出mv中的逻辑视图名部分。(在我的测试中,viewName的值为hello).

在这里插入图片描述

接着继续走,走到1350行。在上一部中 我们获取到的视图部分也就是viewName,其实就是一个hello,这只是一个逻辑名。接着在1350行,调用resolveVieName();该方法参数部分(viewName,mv.getModelnternal,locle,request),后面两个参数不是我们在意的参数,主要来看前面两个参数:

  • viewName:就是将我们刚刚获取的逻辑视图名得到作为参数传进来。
  • mv.getModelnternal():这个方法为了获取数据,此时是获取到了设置的数据

在这里插入图片描述

我们看一下具体的参数值:

在这里插入图片描述

接着继续走下去:此时我们看到在一次进入到render()方法中,执行渲染。这次渲染:主要是将数据渲染到将要响应 的页面。

在这里插入图片描述

从整个过程我们看到先在ModelAndView中寻找视图的逻辑名,如果找不到那就使用缺省的视图,如果能够找到视图的名字,那就对他进行解析得到实际的需要使用的视图对象。还有一种可能就是在ModelAndView中已经包含了实际的视图对象,这个视图对象是可以直接使用的。
不管怎样,得到一个视图对象以后,通过调用视图对象的render来完成数据的显示过程,我们可以看看具体的JstlView是怎样实现的,我们在JstlView的抽象父类 AbstractView中找到render方法:

@Override
	public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
			HttpServletResponse response) throws Exception {

		if (logger.isDebugEnabled()) {
			logger.debug("View " + formatViewName() +
					", model " + (model != null ? model : Collections.emptyMap()) +
					(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
		}

		Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
		prepareResponse(request, response);
        //    //这是实际的展现模型数据到视图的调用
		renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
	}

接着看该方法的参数部分(Map model):此时model放入到了一个集合中先把所有的数据模型进行整合放到一个Map - mergedModel里,然后调用renderMergedOutputModel();这个renderMergedOutputModel是一个模板方法,他的实现在InternalResourceView也就是JstlView的父类:

接着进入到renderMergedOutputModel方法中

@Override
protected void renderMergedOutputModel(
      Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

   // Expose the model object as request attributes.
   exposeModelAsRequestAttributes(model, request);

   // Expose helpers as request attributes, if any.
   exposeHelpers(request);

   // Determine the path for the request dispatcher.
   String dispatcherPath = prepareForRendering(request, response);

   // Obtain a RequestDispatcher for the target resource (typically a JSP).
   RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
   if (rd == null) {
      throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
            "]: Check that the corresponding file exists within your web application archive!");
   }

   // If already included or response already committed, perform include, else forward.
   if (useInclude(request, response)) {
      response.setContentType(getContentType());
      if (logger.isDebugEnabled()) {
         logger.debug("Including [" + getUrl() + "]");
      }
      rd.include(request, response);
   }

   else {
      // Note: The forwarded resource is supposed to determine the content type itself.
      if (logger.isDebugEnabled()) {
         logger.debug("Forwarding to [" + getUrl() + "]");
      }
      rd.forward(request, response);
   }
}

进入到exposeModelAsRequestAttributes():该方法传入两个参数:Map 仍然是数据部分 然后继续走进去。

在这里插入图片描述

进入:在该方法中首先遍历所有的model. 此时我们的Map类型的model 大小为1:因为在model中只有一个数据。

接着:就是讲我们的model中数据存到request中了。

首先对模型数据进行处理,exposeModelAsRequestAttributes是在AbstractView中实现的,这个方法把 ModelAndView中的模型数据和其他request数据统统放到ServletContext当中去,这样整个模型数据就通过 ServletContext暴露并得到共享使用了:

protected void exposeModelAsRequestAttributes(Map<String, Object> model,
			HttpServletRequest request) throws Exception {

		model.forEach((name, value) -> {
			if (value != null) {
				request.setAttribute(name, value);
			}
			else {
				request.removeAttribute(name);
			}
		});
	}

在这里插入图片描述

当执行完exposeModelAsRequestAttributes()方法后,我们分别查询model中的数据和request.attruibutes里面的值。

最后通过获取到转发路径,进行转发。

 // Determine the path for the request dispatcher.
   String dispatcherPath = prepareForRendering(request, response);

   // Obtain a RequestDispatcher for the target resource (typically a JSP).
   RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
  rd.forward(request, response);
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值