Spring MVC Flash Attribute 解决POST/Redirect/GET模式问题缺陷

来源:互联网 发布:java运行无法加载主类 编辑:程序博客网 时间:2024/06/07 07:20

1.描述

最近需要解决项目遗留下来的表单重复提交的问题,因为提交方式为表单提交(从入行开始好像我一直使用的都是Ajax提交),第一个想到的解决方案就是POST/Redirect/GET,但是因为有强迫症不喜欢在URL中进行传值,经寻找找到了通过Spring MVC提供的闪存来解决传参问题。

方案详见:http://viralpatel.net/blogs/spring-mvc-flash-attribute-example/
译文详见:http://www.oschina.net/translate/spring-mvc-flash-attribute-example?p=1#comments

2.问题

经实践发现第一次进入重定向页面传值没有问题,当重定向页面进行F5刷新之后传递的参数就会取不到。

3.分析

为什么在页面F5刷新之后传递的参数就会取不到呢,第一反应就是存储在闪存中的值被移除,经阅读源码发现发现移除的规则为:
1).当闪存中的值在3分钟之内未进行匹配则自动移除
2).当闪存中的值被匹配之后自动移除,匹配方式为URI匹配


闪存移除数据规则代码(org.springframework.web.servlet.support.AbstractFlashMapManager)
@Override    public final FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response) {        List<FlashMap> allFlashMaps = retrieveFlashMaps(request);        if (CollectionUtils.isEmpty(allFlashMaps)) {            return null;        }        if (logger.isDebugEnabled()) {            logger.debug("Retrieved FlashMap(s): " + allFlashMaps);        }        //寻找闪存中过期的数据        List<FlashMap> mapsToRemove = getExpiredFlashMaps(allFlashMaps);        //匹配缓存中的数据(匹配规则为URL==重定向的地址)        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) {                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;    }

设置闪存数据过期时间代码(org.springframework.web.servlet.support.AbstractFlashMapManager)

    @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);        if (logger.isDebugEnabled()) {            logger.debug("Saving FlashMap=" + flashMap);        }        //设置过期时间(getFlashMapTimeout() = 180)        flashMap.startExpirationPeriod(getFlashMapTimeout());        Object mutex = getFlashMapsMutex(request);        if (mutex != null) {            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);        }    }

过期时间计算规则(org.springframework.web.servlet.FlashMap)

/**     * Start the expiration period for this instance.     * @param timeToLive the number of seconds before expiration     */    public void startExpirationPeriod(int timeToLive) {        this.expirationTime = System.currentTimeMillis() + timeToLive * 1000;    }

闪存数据匹配规则代码

/**     * Whether the given FlashMap matches the current request.     * Uses the expected request path and query parameters saved in the FlashMap.     */    protected boolean isFlashMapForRequest(FlashMap flashMap, HttpServletRequest request) {        //重定向地址        String expectedPath = flashMap.getTargetRequestPath();        if (expectedPath != null) {            //当前地址            String requestUri = getUrlPathHelper().getOriginatingRequestUri(request);            //进行匹配            if (!requestUri.equals(expectedPath) && !requestUri.equals(expectedPath + "/")) {                return false;            }        }        UriComponents uriComponents = ServletUriComponentsBuilder.fromRequest(request).build();        MultiValueMap<String, String> actualParams = uriComponents.getQueryParams();        MultiValueMap<String, String> expectedParams = flashMap.getTargetRequestParams();        for (String expectedName : expectedParams.keySet()) {            List<String> actualValues = actualParams.get(expectedName);            if (actualValues == null) {                return false;            }            for (String expectedValue : expectedParams.get(expectedName)) {                if (!actualValues.contains(expectedValue)) {                    return false;                }            }        }        return true;    }

4.结论

通过Spring MVC Flash Attribute 解决POST/Redirect/GET模式是存在页面F5刷新取不到传递的参数问题,所以此方案行不通。

5.备注

Spring MVC Flash Attribute的默认实现是存储在sesion当中的,
实现类为org.springframework.web.servlet.support.SessionFlashMapManager
可以继承抽象类org.springframework.web.servlet.support.AbstractFlashMapManager自定义实现

0 0