其他分享
首页 > 其他分享> > 【Spring】抽丝剥茧SpringMVC之FlashMap机制-不同请求间传递参数

【Spring】抽丝剥茧SpringMVC之FlashMap机制-不同请求间传递参数

作者:互联网

源码基于SpringMVC 5.2.7版本 

FlashMap提供了一种请求间传递参数的方式,其运行机制对开发者来讲几乎透明。开发者只需在请求方法处理上添加相关类型的参数即可完成跨请求的参数传递。SpringMVC提供了一种将FlashMap存储在Session属性中的FlashMapManager-SessionFlashMapManager。SessionFlashMapManager在单体应用中有效,如果应用是以集群方式部署则由于session在跨机器间无共享,所以将失效。开发者可以实现一个支持集群场景的FlashMapManager。

FlashMap

    web项目有时候需要在一个请求中保存一些属性给接下来的其它请求使用。尤其是重定向场景,也许我们需要给目标请求传递些参数。FlashMap机制就提供了这么一种方式让一个请求传递一些参数给接下来的某个请求。当然要实现这个目的还有别的方式,FlashMap是其中一种,FlashMap机制保证了参数的隐蔽性,不需要将参数传递到前端。

public final class FlashMap extends HashMap<String, Object> implements Comparable<FlashMap> {

	@Nullable
	private String targetRequestPath;

	private final MultiValueMap<String, String> targetRequestParams = new LinkedMultiValueMap<>(4);

	private long expirationTime = -1;

    ... ...
}

FlashMap可以指定绑定条件(targetRequestPath、targetRequestParams),只有匹配条件的请求才能取到该FlashMap对象。如果某个FlashMap没有指定绑定条件,那么紧急着那个请求将得到该FlashMap对象。

FlashMapManager

FlashMapManager是一个接口,提供了存储和提取FlashMap的接口规范。

public interface FlashMapManager {

	@Nullable
	FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);

	void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);

}

SessionFlashMapManager

FlashMapManager只是接口规范,FlashMap对象保存到哪里、又从哪里提取FlashMap对象。这两点依赖具体的FlashMapManager实现类。SpringMVC提供了一个实现类org.springframework.web.servlet.support.SessionFlashMapManager。顾名思义,SessionFlashMapManager就是把FlashMap对象保存到Session中,也是从Session中提取符合条件的FlashMap对象。由于session的局限性,SessionFlashMapManager在单体应用中有效,如果应用是以集群方式部署则由于session在跨机器间无共享,所以将失效。开发者可以实现一个支持集群场景的FlashMapManager。

SessionFlashMapManager将FlashMap对象放在Session属性"org.springframework.web.servlet.support.SessionFlashMapManager.FLASH_MAPS"中

public class SessionFlashMapManager extends AbstractFlashMapManager {

	private static final String FLASH_MAPS_SESSION_ATTRIBUTE = SessionFlashMapManager.class.getName() + ".FLASH_MAPS";


	/**
	 * Retrieves saved FlashMap instances from the HTTP session, if any.
	 */
	@Override
	@SuppressWarnings("unchecked")
	@Nullable
	protected List<FlashMap> retrieveFlashMaps(HttpServletRequest request) {
		HttpSession session = request.getSession(false);
		return (session != null ? (List<FlashMap>) session.getAttribute(FLASH_MAPS_SESSION_ATTRIBUTE) : null);
	}

	/**
	 * Saves the given FlashMap instances in the HTTP session.
	 */
	@Override
	protected void updateFlashMaps(List<FlashMap> flashMaps, HttpServletRequest request, HttpServletResponse response) {
		WebUtils.setSessionAttribute(request, FLASH_MAPS_SESSION_ATTRIBUTE, (!flashMaps.isEmpty() ? flashMaps : null));
	}

    ... ...
}

FlashMap运行机制

    FlashMap的存储和提取都是SpringMVC框架自动实现的,开发者只需要使用RedirectView,请求处理的方法上添加org.springframework.web.servlet.mvc.support.RedirectAttributes的参数。将需要的属性通过RedirectAttributes#addFlashAttribute添加到RedirectAttributes参数中。

FlashMap的自动存储

存储FlashMap大致经过以下过程

1 给请求添加类型为FlashMap的属性

请求刚刚进入DispatcherServlet的时候,如果DispatcherServlet的flashMapManager不为空,则创建一个FlashMap对象挂到request的属性上,属性名为"org.springframework.web.servlet.DispatcherServlet.OUTPUT_FLASH_MAP"。源码在org.springframework.web.servlet.DispatcherServlet#doService

public class DispatcherServlet extends FrameworkServlet {
    ... ...

    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {

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

    ... ...

    }

... ...
}

2 开发者在请求的处理方法上添加RedirectAttributes类型的参数

    请求的处理方法上有RedirectAttributes类型的参数时,SpringMVC就用参数解析器org.springframework.web.servlet.mvc.method.annotation.RedirectAttributesMethodArgumentResolver给该方法传一个实际类型为org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap的对象,同时设置ModelAndViewContainer的redirectModel。RedirectAttributesMethodArgumentResolver源码如下

public class RedirectAttributesMethodArgumentResolver implements HandlerMethodArgumentResolver {
    
    ... ...

    @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;
		if (binderFactory != null) {
			DataBinder dataBinder = binderFactory.createBinder(webRequest, null, DataBinder.DEFAULT_OBJECT_NAME);
			redirectAttributes = new RedirectAttributesModelMap(dataBinder);
		}
		else {
			redirectAttributes  = new RedirectAttributesModelMap();
		}
		mavContainer.setRedirectModel(redirectAttributes);
		return redirectAttributes;
	}

... ...

}

    开发只需要将要保存的属性添加通过org.springframework.web.servlet.mvc.support.RedirectAttributes#addFlashAttribute添加到参数中即可

3 HandlerAdaptor在执行完请求,将flashAttributes添加到与请求关联的FlashMap对象中

RequstMappingHandlerAdaptor处理完请求方法后,如果model是RedirectAttributes类型就会将flashAttributes加到与该请求关联的FlashMap对象中。关联的FlashMap就是DispatcherServlet在请求刚进来时创建的FlashMap对象。

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {

... ...


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

        ... ...	
        
        ModelMap model = mavContainer.getModel();
        ... ...	

		if (model instanceof RedirectAttributes) {
			Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
			HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
			if (request != null) {
				RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
			}
		}
		return mav;
	}

}

如果请求返回的view是redirect view且ModelAndViewContainer的redirectModel不为空(请求参数上有RedirectAttributes类型的参数,参数解析器就会给redirectModel赋值),ModelAndViewContainer返回的model就是RedirectAttributes类型的实例。

public class ModelAndViewContainer {
    ... ...   

    public ModelMap getModel() {
		if (useDefaultModel()) {
			return this.defaultModel;
		}
		else {
			if (this.redirectModel == null) {
				this.redirectModel = new ModelMap();
			}
			return this.redirectModel;
		}
	}
    ... ...
}

4 RedirectView在渲染的时候将请求关联的FlashMap对象存储下来

RedirectView渲染时调用org.springframework.web.servlet.support.RequestContextUtils#saveOutputFlashMap,将FlashMap存储下来

public class RedirectView extends AbstractUrlBasedView implements SmartView {
    
    ... ...

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

		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);
	}
    
    ... ...
}
public abstract class RequestContextUtils {

    ... ...

    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?");
		manager.saveOutputFlashMap(flashMap, request, response);
	}
    
    ... ...
}

 

FlashMap的自动提取

1 DispatcherServlet取出FlashMap对象并关联到request属性

请求刚进来是,如果DispatcherServlet的flashMapManager不为空,则通过flashMapManager提取FlashMap对象,并关联到request的属性上,属性名为"org.springframework.web.servlet.DispatcherServlet.INPUT_FLASH_MAP"。源码在org.springframework.web.servlet.DispatcherServlet#doService

2 HandlerAdaptor调用请求处理方法前添加到model中

RequstMappingHandlerAdaptor在调用请求的处理方法前,从request属性中取出关联的FlashMap对象,并将其中的键值对初始化到model中

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {
    ... ...
	
    @Nullable
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {	
    
        ... ...
    
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
		mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));

        ... ...

    }
}

3 开发者取属性

开发者只需要在请求的方式上添加Model的参数,就可以取到model里面所有的属性

示例

定义两个请求路径:/focuse/hello和/focuse/hello3。/focuse/hello添加完FlashMap的属性返回重定向视图"redirect:/focuse/hello3";/focuse/hello3取得FlashMap的属性然后返回

@Controller
@RequestMapping("/focuse")
public class DemoController {

    @RequestMapping("hello")
    public String hello(RedirectAttributes redirectAttributes, HttpServletRequest request) {

        redirectAttributes.addFlashAttribute("flashdemo", "'hello world!' from " + request.getRequestURI());

        return "redirect:/focuse/hello3";
    }

    @ResponseBody
    @RequestMapping("hello3")
    public String hello(ModelMap model) {
        return String.valueOf(model.getAttribute("flashdemo"));
    }

}

请求  /focuse/hello 重定向到 /focuse/hello3

/focuse/hello3输出了/focuse/hello保存下来的FlashMap

标签:...,FlashMap,请求,SpringMVC,request,抽丝剥茧,RedirectAttributes,public
来源: https://blog.csdn.net/beFocused/article/details/106929357