当前位置:   article > 正文

SpringMVC中重定向请求时传输参数原理分析与实践_重定向视频中再次请求参数怎么弄

重定向视频中再次请求参数怎么弄

本文要解决的是重定向请求时,把数据传到目标地方。

【1】几个概念

① Redirect Attributes

默认情况下,所有模型属性都被视为在重定向URL中作为URI模板变量暴露。在其余的属性中,基本类型或基本类型的集合或基本类型的数组将自动附加为查询参数。

如果专门为重定向准备了一个模型实例,那么将基本类型属性作为查询参数附加可能是理想的结果。但是,在带注解的控制器中,模型可以包含为渲染目的添加的其他属性(例如,下拉字段值)。为了避免此类属性出现在URL中,@RequestMapping方法可以声明类型为RedirectAttributes 的参数,并使用它指定可供RedirectView使用的确切属性。如果方法确实重定向,则使用RedirectAttributes 的内容。否则,将使用model的内容。

RequestMappingHandlerAdapter 提供了一个标记叫做ignoreDefaultModelOnRedirect,可以用来声明如果控制器方法重定向时,默认的Model是否使用。RequestMappingHandlerAdapter提供了一个名为ignoreDefaultModelOnRedirect的标志,您可以使用该标志指示如果控制器方法重定向,则不应使用默认模型的内容。相反,控制器方法应该声明RedirectAttributes类型的属性,如果不这样做,则不应该将任何属性传递给RedirectView。MVC命名空间和MVC Java配置都将此标志设置为false,以保持向后兼容性。但是,对于新应用程序,我们建议将其设置为true。
在这里插入图片描述

请注意,当前请求中的URI模板变量在扩展重定向URL时自动可用,您不需要通过模型或重定向属性显式添加它们。以下示例显示如何定义重定向:

@PostMapping("/files/{path}")
public String upload(...) {
    // ...
    return "redirect:files/{path}";
}
  • 1
  • 2
  • 3
  • 4
  • 5

向重定向目标传递数据的另一种方法是使用flash属性。与其他重定向属性不同,flash属性保存在HTTP会话中(因此不会出现在URL中)。

② Flash Attributes

Flash属性为一个请求提供了一种方法来存储打算在另一个请求中使用的属性。这是重定向时最常用的 — 例如Post-Redirect-Get模式。flash属性在重定向之前(通常在会话中)临时保存,以便在重定向后可用于请求,之后会被删除。

SpringMVC有两个主要的类来支持flash属性。FlashMap类用于保存flash属性,而FlashMapManager接口的实例用于存储、检索和管理FlashMap实例。

默认支持Flash 属性,你不需要显示进行启用。但是如果不使用,它不会导致HTTP会话创建。在每个请求上,都有一个 “input” FlashMap和一个“output” FlashMap。前者具有从上一个请求(如果有)传递的属性,后者具有为后续请求保存的属性。这两个FlashMap实例都可以通过RequestContextUtils中的静态方法从SpringMVC中的任何位置访问。

public static Map<String, ?> getInputFlashMap(HttpServletRequest request) {
	return (Map<String, ?>) request.getAttribute(DispatcherServlet.INPUT_FLASH_MAP_ATTRIBUTE);
}

public static final String INPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".INPUT_FLASH_MAP";

public static FlashMap getOutputFlashMap(HttpServletRequest request) {
	return (FlashMap) request.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE);
}

public static final String OUTPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".OUTPUT_FLASH_MAP";

public static final String FLASH_MAP_MANAGER_ATTRIBUTE = DispatcherServlet.class.getName() + ".FLASH_MAP_MANAGER";
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

带注解的控制器通常不需要直接使用FlashMap。相反,@RequestMapping注解的方法可以接受RedirectAttributes 类型的参数,并使用它为重定向场景添加flash属性。通过RedirectAttributes 添加的Flash属性会自动传播到“output”FlashMap类似地,在重定向之后,“input”FlashMap中的属性会自动添加到为目标URL提供服务的控制器的Model中。

为flash 属性匹配请求

flash属性的概念存在于许多其他web框架中,并且已经证明有时会遇到并发问题。这是因为,根据定义,flash属性将存储到下一个请求。但是,“下一个”请求可能不是预期的接收者,而是另一个异步请求(例如,轮询或资源请求),在这种情况下,flash属性被过早删除。

为了减少出现此类问题的可能性,RedirectView 会使用目标重定向URL的路径和查询参数自动“标记”FlashMap实例。反过来,默认FlashMapManager在查找“input”FlashMap时将该信息与传入请求相匹配。

// DispatcherServlet#doService有如下代码来获取inputFlashMap 
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);

//具体获取inputFlashMap 方法如下
@Override
@Nullable
public final FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response) {
//从session中获取FLASH_MAPS_SESSION_ATTRIBUTE
	List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
	if (CollectionUtils.isEmpty(allFlashMaps)) {
		return null;
	}
// 获取过期的FlashMap
	List<FlashMap> mapsToRemove = getExpiredFlashMaps(allFlashMaps);
	//获取与当前请求匹配的FlashMap 
	FlashMap match = getMatchingFlashMap(allFlashMaps, request);
	if (match != null) {
		mapsToRemove.add(match);
	}
// 从allFlashMaps移除掉mapsToRemove,然后更新session的FLASH_MAPS_SESSION_ATTRIBUTE
	if (!mapsToRemove.isEmpty()) {
		Object mutex = getFlashMapsMutex(request);
		if (mutex != null) {
			synchronized (mutex) {
				allFlashMaps = retrieveFlashMaps(request);
				if (allFlashMaps != null) {
					allFlashMaps.removeAll(mapsToRemove);
					updateFlashMaps(allFlashMaps, request, response);
				}
			}
		}
		else {
			allFlashMaps.removeAll(mapsToRemove);
			updateFlashMaps(allFlashMaps, request, response);
		}
	}
// 返回当前请求匹配的FlashMap
	return match;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

这并不能完全消除并发问题的可能性,但可以通过重定向URL中已有的信息大大减少并发问题。因此,我们建议您主要在重定向场景中使用flash属性。

【2】实践实例

常见的可以使用URL传参、uriPathVariable,如/testr?name=jane/testr/{jane}。除此之外,可以考虑使用flash attributes。

一个请求在实际转发到doDispatch前,请求属性里面会有如下属性:

org.springframework.web.context.request.async.WebAsyncManager.WEB_ASYNC_MANAGER--WebAsyncManager
CharacterEncodingFilter.FILTERED--true
org.springframework.web.servlet.DispatcherServlet.CONTEXT--WebApplicationContext for namespace 'DispatcherServlet-servlet', started on Fri Dec 10 15:45:06 CST 2021
org.springframework.web.servlet.DispatcherServlet.LOCALE_RESOLVER--AcceptHeaderLocaleResolver
org.springframework.web.servlet.DispatcherServlet.OUTPUT_FLASH_MAP--FlashMap
HiddenHttpMethodFilter.FILTERED--true
org.springframework.web.servlet.DispatcherServlet.FLASH_MAP_MANAGER--SessionFlashMapManager
org.springframework.web.servlet.DispatcherServlet.THEME_RESOLVER--FixedThemeResolver
org.springframework.web.servlet.DispatcherServlet.THEME_SOURCE--WebApplicationContext for namespace 'DispatcherServlet-servlet', started on Fri Dec 10 15:45:06 CST 2021
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在这里插入图片描述
可以看到此时有一个空的outputFlashMap,但是没有inputFlashMap。

① redirectAttributes发送,model接收

如下所示,不通过URL传参,testRedirect请求重定向到testr。

// 重定向前的方法
@RequestMapping("/testRedirect")
public String testRedirect(HttpServletRequest request, RedirectAttributes redirectAttributes){
// 普通属性
    redirectAttributes.addAttribute("redirectAttributes","redirectAttributesValue");
//flash 属性
    redirectAttributes.addFlashAttribute("addFlashAttribute","redirectAttributes.addFlashAttribute-value");
    return "redirect:/testr";
}

// 重定向后的
@RequestMapping("/testr")
public String testr(HttpServletRequest request, String redirectAttributes, String addFlashAttribute,Model model){
// redirectAttributesValue
    System.out.println(request.getParameter("redirectAttributes"));
// null
    System.out.println(request.getParameter("addFlashAttribute"));
// {addFlashAttribute=redirectAttributes.addFlashAttribute-value}
    System.out.println(model);
    return "success";
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

重定向URL这里最终为:

http://localhost:8080/testr?redirectAttributes=redirectAttributesValue
  • 1

可以看到方法testRedirect中redirectAttributes.addAttribute("redirectAttributes","redirectAttributesValue");被作为URL的queryString参数。此时方法testr中request中获取不到FlashAttribute,model里面可以获取到FlashAttribute
在这里插入图片描述

② 原理分析

// 路径链如下
DispatcherServlet#doService--
DispatcherServlet#doDispatch--
AbstractHandlerMethodAdapter#handle--
RequestMappingHandlerAdapter#handleInternal--
RequestMappingHandlerAdapter#invokeHandlerMethod--
ServletInvocableHandlerMethod#invokeAndHandle--
RequestMappingHandlerAdapter#getModelAndView
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

testRedirect目标方法处理前,DispatcherServlet#doService中,此时inputFlashMapnulloutputFlashMapnew FlashMap()

默认情况下,这时是没有inputFlashMap的。除非已经有其他重定向请求使用了flashMap且没有过期并匹配当前请求。
在这里插入图片描述

② 获取mavContainer实例后放入inputFlashMap

RequestMappingHandlerAdapter#invokeHandlerMethod方法中,获取到ModelAndViewContainer实例后会从request中获取属性DispatcherServlet.INPUT_FLASH_MAP_ATTRIBUTE也就是获取inputFlashMap放入到model中。这里面默认放入的是defaultModel而非redirectModel(默认情况下redirectModelScenario是false,ignoreDefaultModelOnRedirect也是false。)

另外,这里默认为mavContainer设置ignoreDefaultModelOnRedirect=false

mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
  • 1
  • 2

③ testRedirect目标方法处理

我们先看一下方法String testRedirect(HttpServletRequest request, RedirectAttributes redirectAttributes)解析参数结果(RedirectAttributes实例是一个RedirectAttributesModelMap,该参数被RedirectAttributesMethodArgumentResolver解析):
在这里插入图片描述
RedirectAttributesModelMap 主要属性如下所示,也就是说比default model多了一个private final ModelMap flashAttributes = new ModelMap();

public class RedirectAttributesModelMap extends ModelMap implements RedirectAttributes {
	// 数据绑定器
	@Nullable
	private final DataBinder dataBinder;
	// 存放flashAttribute
	private final ModelMap flashAttributes = new ModelMap();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

RedirectAttributesMethodArgumentResolver解析RedirectAttributes参数的过程如下所示:

@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

	Assert.state(mavContainer != null, "RedirectAttributes argument only supported on regular handler methods");

	ModelMap redirectAttributes;
	// 实例化RedirectAttributesModelMap
	if (binderFactory != null) {
		DataBinder dataBinder = binderFactory.createBinder(webRequest, null, DataBinder.DEFAULT_OBJECT_NAME);
		redirectAttributes = new RedirectAttributesModelMap(dataBinder);
	}
	else {
		redirectAttributes  = new RedirectAttributesModelMap();
	}
	// 将当前redirectAttributes赋予mavContainer的RedirectModel属性
	mavContainer.setRedirectModel(redirectAttributes);
	return redirectAttributes;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

这里非常有必要说的是,如果目前方法有参数redirectAttributes ,那么在解析目标方法参数后,mavContainer中的RedirectModel就是redirectAttributes 实例对象!

然后目标方法则会分别向默认集合与flashAttributes中放入数据:

// 普通属性
redirectAttributes.addAttribute("redirectAttributes","redirectAttributesValue");
//flash 属性
redirectAttributes.addFlashAttribute("addFlashAttribute","redirectAttributes.addFlashAttribute-value");
  • 1
  • 2
  • 3
  • 4

④ testRedirect方法返回结果处理

返回结果是redirect:/testr,这里使用ViewNameMethodReturnValueHandlr处理返回结果。过程如下所示,这里会将mavContainer的RedirectModelScenario属性设置为true。

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

	if (returnValue instanceof CharSequence) {
		String viewName = returnValue.toString();
		mavContainer.setViewName(viewName);
		if (isRedirectViewName(viewName)) {
		// 这里设置RedirectModelScenario为true哦
			mavContainer.setRedirectModelScenario(true);
		}
	}
	else if (returnValue != null) {
		// should not happen
		throw new UnsupportedOperationException("Unexpected return type: " +
				returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

⑤ 获取ModelAndView

如下所示,在RequestMappingHandlerAdapter#getModelAndView方法中(此时已经调用目标方法并对返回结果做了处理),判断model是RedirectAttributes,则会读取flashAttributes 然后放入OutputFlashMap中。

@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
		ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

	modelFactory.updateModel(webRequest, mavContainer);
	if (mavContainer.isRequestHandled()) {
		return null;
	}
	ModelMap model = mavContainer.getModel();
	ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
	if (!mavContainer.isViewReference()) {
		mav.setView((View) mavContainer.getView());
	}
	if (model instanceof RedirectAttributes) {
		Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
		HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
		if (request != null) {
// request.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE);
			RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
		}
	}
	return mav;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

如下过程所示,这里会将RedirectAttributes的flash属性放入到outputFlashMap中,但是RedirectAttributes中还有普通属性没有处理哦。
在这里插入图片描述

⑥ 渲染视图

RedirectView#renderMergedOutputModel方法是AbstractView#render方法的核心方法。其会获取目标URL(会将model中的属性-值作为URL的queryString Param拼接到URL后面),也就是这里会处理RedirectAttributes的普通属性。

@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
		HttpServletResponse response) throws IOException {
// 这里很关键,会将RedirectAttributes中的普通属性拼接作为url 参数
	String targetUrl = createTargetUrl(model, request);
	targetUrl = updateTargetUrl(targetUrl, model, request, response);

	// Save flash attributes
	RequestContextUtils.saveOutputFlashMap(targetUrl, request, response);

	// Redirect
	sendRedirect(request, response, targetUrl, this.http10Compatible);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

解析targetUrl 过程如下所示:
在这里插入图片描述
我们再继续看一下RequestContextUtils.saveOutputFlashMap(targetUrl, request, response);过程:

public static void saveOutputFlashMap(String location, HttpServletRequest request, HttpServletResponse response) {
	FlashMap flashMap = getOutputFlashMap(request);
	if (CollectionUtils.isEmpty(flashMap)) {
		return;
	}

	UriComponents uriComponents = UriComponentsBuilder.fromUriString(location).build();
	flashMap.setTargetRequestPath(uriComponents.getPath());
	flashMap.addTargetRequestParams(uriComponents.getQueryParams());

	FlashMapManager manager = getFlashMapManager(request);
	Assert.state(manager != null, "No FlashMapManager. Is this a DispatcherServlet handled request?");
	// 这里又会调用FlashMapManager 的saveOutputFlashMap方法
	manager.saveOutputFlashMap(flashMap, request, response);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

FlashMapManagersaveOutputFlashMap方法如下所示,这里很重要的一步是会获取sessionFlashMap-List<FlashMap> allFlashMaps(FLASH_MAPS_SESSION_ATTRIBUTE),然后将outputFlashMap保存更新进去。给重定向后的请求使用。

@Override
public final void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response) {
	if (CollectionUtils.isEmpty(flashMap)) {
		return;
	}

	String path = decodeAndNormalizePath(flashMap.getTargetRequestPath(), request);
	// 设置目标请求地址
	flashMap.setTargetRequestPath(path);
// 设置过期时间,默认是180
	flashMap.startExpirationPeriod(getFlashMapTimeout());

	Object mutex = getFlashMapsMutex(request);
	if (mutex != null) {
		synchronized (mutex) {
		// 从session中获取属性FLASH_MAPS_SESSION_ATTRIBUTE对应的值
			List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
			allFlashMaps = (allFlashMaps != null ? allFlashMaps : new CopyOnWriteArrayList<>());
			// 放入flashMap
			allFlashMaps.add(flashMap);
			// 将FLASH_MAPS_SESSION_ATTRIBUTE -- allFlashMaps  放入session中
			updateFlashMaps(allFlashMaps, request, response);
		}
	}
	else {
		List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
		allFlashMaps = (allFlashMaps != null ? allFlashMaps : new ArrayList<>(1));
		allFlashMaps.add(flashMap);
		updateFlashMaps(allFlashMaps, request, response);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

详细过程如下图所示
在这里插入图片描述

⑦ 新请求从session中获取inputFlashMap

核心代码如下,从session中获取到inputFlashMap(然后会从session移除掉哦)。如果inputFlashMap不为null,则放入request属性中。然后放入OUTPUT_FLASH_MAP_ATTRIBUTEFLASH_MAP_MANAGER_ATTRIBUTE

if (this.flashMapManager != null) {
	FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
	// 如果inputFlashMap  不为null,则放入request中一个不可修改的inputFlashMap
	if (inputFlashMap != null) {
		request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
	}
	// 放入outputFlashMap
	request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
	// 放入flashMapManager
	request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这里面我们看一下retrieveAndUpdate方法。这个方法很有意思,首先从session中获取FLASH_MAPS_SESSION_ATTRIBUTE属性对应的值对象我们姑且称之为sessionFlashMap。然后获取sessionFlashMap中过期的ExpiredFlashMaps以及与当前请求匹配的MatchingFlashMap

接下来要做的就是从sessionFlashMap移除掉ExpiredFlashMapsMatchingFlashMap,然后更新session的FLASH_MAPS_SESSION_ATTRIBUTE属性值,并将MatchingFlashMap作为方法结果返回。

@Override
@Nullable
public final FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response) {
// 获取session中的flashMap
	List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
	if (CollectionUtils.isEmpty(allFlashMaps)) {
		return null;
	}
// 获取过期的flashMap
	List<FlashMap> mapsToRemove = getExpiredFlashMaps(allFlashMaps);
	// 获取匹配当前请求的flashMap
	FlashMap match = getMatchingFlashMap(allFlashMaps, request);
	if (match != null) {
		mapsToRemove.add(match);
	}
// 移除掉mapsToRemove,然后将剩余的更新session中的flashMap
	if (!mapsToRemove.isEmpty()) {
		Object mutex = getFlashMapsMutex(request);
		if (mutex != null) {
			synchronized (mutex) {
				allFlashMaps = retrieveFlashMaps(request);
				if (allFlashMaps != null) {
					allFlashMaps.removeAll(mapsToRemove);
					updateFlashMaps(allFlashMaps, request, response);
				}
			}
		}
		else {
			allFlashMaps.removeAll(mapsToRemove);
			updateFlashMaps(allFlashMaps, request, response);
		}
	}
// 返回匹配的flashMap
	return match;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

⑧ 新请求的mavContainer放入inputFlashMap

RequestMappingHandlerAdapter#invokeHandlerMethod方法中有如下代码会从request中获取InputFlashMap然后放入mavContainerdefaultModel中。

mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
  • 1

在这里插入图片描述
那么此时再解析重定向后的方法参数,很显然将会得到如下结果:
在这里插入图片描述
至此,我们讲重定向前后参数传参分析完毕。

③ Model可以实现重定向传参的需求吗?

这里的model默认是指BindingAwareModelMap,其不能实现RedirectAttributesModelMap的功能。

如下所示,方法重定向前在model中放入modelAttr-modelAttrValue,但是方法重定向后,是接收不到该键值对的。
在这里插入图片描述
如下所示,方法重定向后的model中只有addFlashAttribute属性:

{addFlashAttribute=redirectAttributes.addFlashAttribute-value}
  • 1

因为在处理返回结果时,已经将redirectModelScenario设置为true并且redirectModel不为null,那么在getModelAndView方法中获取Model时将会获取到redirectModel也就是RedirectAttributesModelMap实例。该实例的flashAttributes数据将会放入outputFlashMap中,而该实例非flashAttributes数据将会用来构造ModelAndView实例。

ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());

// 获取model的代码如下
public ModelMap getModel() {
	if (useDefaultModel()) {
		return this.defaultModel;
	}
	else {
		if (this.redirectModel == null) {
			this.redirectModel = new ModelMap();
		}
		return this.redirectModel;
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

为啥redirectAttributes.addAttribute("redirectAttributes","redirectAttributesValue");同样能把属性带过去呢?因为其将作为URL的query String Params拼接到URL后面带过去。

如下所示RedirectView的属性中exposeModelAttributes 、expandUriTemplateVariables 默认为true。

private static final Pattern URI_TEMPLATE_VARIABLE_PATTERN = Pattern.compile("\\{([^/]+?)\\}");


private boolean contextRelative = false;

private boolean http10Compatible = true;

private boolean exposeModelAttributes = true;

@Nullable
private String encodingScheme;

@Nullable
private HttpStatus statusCode;

private boolean expandUriTemplateVariables = true;

private boolean propagateQueryParams = false;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

这两个属性有什么作用呢?我们可以细探究一下RedirectView解析目标URL的过程

protected final String createTargetUrl(Map<String, Object> model, HttpServletRequest request)
		throws UnsupportedEncodingException {

	// Prepare target URL.
	StringBuilder targetUrl = new StringBuilder();
	// 获取请求url
	String url = getUrl();
	Assert.state(url != null, "'url' not set");

// 拼接contextPath
	if (this.contextRelative && getUrl().startsWith("/")) {
		// Do not apply context path to relative URLs.
		targetUrl.append(getContextPath(request));
	}
	targetUrl.append(getUrl());

	String enc = this.encodingScheme;
	if (enc == null) {
		enc = request.getCharacterEncoding();
	}
	if (enc == null) {
		enc = WebUtils.DEFAULT_CHARACTER_ENCODING;
	}
// 尝试用model的数据对url上的占位符进行解析替代
	if (this.expandUriTemplateVariables && StringUtils.hasText(targetUrl)) {
		Map<String, String> variables = getCurrentRequestUriVariables(request);
		targetUrl = replaceUriTemplateVariables(targetUrl.toString(), model, variables, enc);
	}
	// 是否将当前请求参数拼接到URL后面,propagateQueryParams默认false
	if (isPropagateQueryProperties()) {
		appendCurrentQueryParams(targetUrl, request);
	}
	// 是否将model里面属性暴露出来--拼接到URL后面
	if (this.exposeModelAttributes) {
		appendQueryProperties(targetUrl, model, enc);
	}
// /testr?redirectAttributes=redirectAttributesValue
	return targetUrl.toString();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/431076
推荐阅读
相关标签
  

闽ICP备14008679号