假装看源码之springmvc (二) 重定向带参数RedirectAttributes的原理

   重定向带参数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。

猜你喜欢

转载自blog.csdn.net/shuixiou1/article/details/112972229