重定向带参数RedirectAttributes的原理
一、 概述:有些功能需要在重定向的时候,带上上一次请求的参数,第一个方法,可以采用重定向拼接参数方法,第二个方法,可以采用RedirectAttributes的方式,用它来添加需要重定向带上的参数,而RedirectAttributes也有2个方式。然后重定向请求返回到页面的时候,才可以得到这个请求的参数,或者用@ModelAttribute获取。
RedirectAttributes重定向带参数的2个方式:
方式1: 通过attr.addAttribute(key, value),其就是拼接请求参数的方式加到需要重定向的请求上。
方式2:通过attr.addFlashAttribute(key, value) , 其是通过session临时存储参数数据,下一次重定向时取出临时存储的参数数据。
/**
* 1、原始请求: /demo2/redirect
*/
@RequestMapping("/redirect")
public String RedirecAttr(RedirectAttributes attr){
attr.addAttribute("id", 1); // 方式1: 即拼接id=1
attr.addFlashAttribute("result", "成功"); // 方式2
return "redirect:/demo2/list";
}
/**
* 2、重定向的请求: 在浏览器显示的链接是 /demo2/list?id=1
*/
@RequestMapping("list")
public String list(int id,String result, @ModelAttribute("result") String result2){
System.out.println(id); // 可以获取参数
System.out.println(result); // 无法获取参数
System.out.println(result2); // 可以获取参数
return "/index"; // 页面${result}可以获取参数的值
}
二、源码分析
1、分析问题?
请求参数什么时候存入session? 请求参数什么时候取出session? 存入session的是什么?
2、请求参数什么时候存入session?
我们在处理方法中添加attr.addFlashAttribute(key, value),在处理方法结束后,在重定向前,会将RedirectAttributes所有的参数添加到FlashMap,让后放到OUTPUT_FLASH_MAP为key的请求域中。我们可以追踪请求流程,在RequestMappingHandlerAdapter中找到getModelAndView方法。
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
......省略
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
return mav;
}
@SuppressWarnings("unchecked")
public static Map<String, ?> getInputFlashMap(HttpServletRequest request) {
return (Map<String, ?>) request.getAttribute(DispatcherServlet.INPUT_FLASH_MAP_ATTRIBUTE);
}
public static FlashMap getOutputFlashMap(HttpServletRequest request) {
return (FlashMap) request.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE);
}
在RequestContextUtils中,我们可以看到请求域存有2个falshMap,先不探究flashMap结构,inputFlashMap就是从上一个请求重定向带的参数,outFlashMap就是包装了本次成需要重定向的参数,那么我们这里只是放到了请求域,还需要往下看找到何时通过什么放入session。我们需要跟踪重定向视图RedirectView的处理。
在RedirectView中,我们在renderMergedOutputModel()方法中可以找到将outFlashMap通过一个FlashMapManager放入session中。暂时也不看FlashMapManager的内部细节。'
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response) throws IOException {
// 从请求域取出FlashMap
FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request);
if (!CollectionUtils.isEmpty(flashMap)) {
FlashMapManager flashMapManager = RequestContextUtils.getFlashMapManager(request);
// 放入session中
flashMapManager.saveOutputFlashMap(flashMap, request, response);
}
// 重定向
sendRedirect(request, response, targetUrl, this.http10Compatible);
}
3、请求参数什么时候取出session?
请求参数取出session就是在处理方法处理前也是通过FlashMapManager从session中取出的,我们可以在DispatcherServlet的doDispatch方法中找到。
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());
retrieveAndUpdate即通过FlashMapManager从session中取出inputFlashMap,并且放到请求域中,后续在参数解析的时候会放入到ModelAttribute中,并且这里初始化了outFlashMap。
4、存入session的具体方式是什么?
现在取出和放入的时机和方式都了解了,他们都是通过FlashMapManager取出和放置FlashMap对象,那么我们就看看FlashMap类和FlashMapManager类。
FlashMap是继承了HashMap,扩展的主要功能,就是增加一个过期时间,创建的时候,设置过期时间,取的时候判断是否过期。
public void startExpirationPeriod(int timeToLive) {
this.expirationStartTime = System.currentTimeMillis();
this.timeToLive = timeToLive;
}
public boolean isExpired() {
return (this.expirationStartTime != 0 &&
(System.currentTimeMillis() - this.expirationStartTime > this.timeToLive * 1000));
}
FlashMapManager的继承结构
取参数:AbstractFlashMapManager的retrieveAndUpdate方法主要是通过模板方法retrieveFlashMaps调用子类SessionFlashMapManager的实现,获取FlashMap的列表,然后取出掉过期的,匹配出路径跟请求路径匹配的。我们住户要看retrieveAndUpdate方法如何获取sessipn中存储的上一个请求参数。
public final FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response) {
List<FlashMap> allFlashMaps = retrieveFlashMaps(request); // 模板方法,子类sessin中取
if (CollectionUtils.isEmpty(allFlashMaps)) {
return null;
}
List<FlashMap> mapsToRemove = getExpiredFlashMaps(allFlashMaps); // 过期的
FlashMap match = getMatchingFlashMap(allFlashMaps, request); // 匹配当前请求的
if (match != null) {
mapsToRemove.add(match);
}
if (!mapsToRemove.isEmpty()) {
if (logger.isDebugEnabled()) {
logger.debug("Removing FlashMap(s): " + mapsToRemove);
}
Object mutex = getFlashMapsMutex(request);
if (mutex != null) { // 如果加锁就已sesison为锁进行更新FlashMaps的操作
synchronized (mutex) {
allFlashMaps = retrieveFlashMaps(request);
if (allFlashMaps != null) {
allFlashMaps.removeAll(mapsToRemove); // 移除失效
updateFlashMaps(allFlashMaps, request, response); // 更新剩余的
}
}
}
else {
allFlashMaps.removeAll(mapsToRemove);
updateFlashMaps(allFlashMaps, request, response);
}
}
return match;
}
SessionFlashMapManager的retrieveFlashMaps方法,就是session中取FlashMap
protected List<FlashMap> retrieveFlashMaps(HttpServletRequest request) {
HttpSession session = request.getSession(false);
return (session != null ? (List<FlashMap>) session.getAttribute(FLASH_MAPS_SESSION_ATTRIBUTE) : null);
}
存参数:存参数是SessionFlashMapManager的saveOutputFlashMap方法,就是设置flashMap的路径和过期时间,存入session中。
public final void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response) {
String path = decodeAndNormalizePath(flashMap.getTargetRequestPath(), request);
flashMap.setTargetRequestPath(path); // 设置目标路径
decodeParameters(flashMap.getTargetRequestParams(), request);
flashMap.startExpirationPeriod(getFlashMapTimeout()); // 设置过期时间180秒
Object mutex = getFlashMapsMutex(request);
if (mutex != null) { // 如果有锁就以session为锁操作session
synchronized (mutex) {
List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
allFlashMaps = (allFlashMaps != null ? allFlashMaps : new CopyOnWriteArrayList<FlashMap>());
allFlashMaps.add(flashMap); // 添加
updateFlashMaps(allFlashMaps, request, response); // 更新
}
}
else {
List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
allFlashMaps = (allFlashMaps != null ? allFlashMaps : new LinkedList<FlashMap>());
allFlashMaps.add(flashMap);
updateFlashMaps(allFlashMaps, request, response);
}
}
三、结尾
spring-mvc的redirectAttributes就是利用session临时存储重定向参数,在重定向的时候,将redirectAttributes的参数复制到一个FlashMap中,然后通过FlashMapManager存储到session中,在下一次请求的时候,再次通过FlashMapManager获取跟此次请求匹配存储于session中的FlashMap,在参数解析的时候,加入到@ModelAttribute。