1. 程式人生 > >SpringMVC RedirectAttributes 實現重定向帶引數 Controller接受引數

SpringMVC RedirectAttributes 實現重定向帶引數 Controller接受引數

springmvc 在3.1版本後提供了 重定向帶引數,之前不知道,使用的是session重定向後又清除這個機智而又粗魯的方法。 在知道RedirectAttributes能做這件事後,這還能忍?立馬回去把程式碼改了,順便發個部落格


/**
 * 頁面跳轉 至上傳不成功excel 以及list頁面
 *
 * @return upload_unsuccessful檢視
 */
@GetMapping("/uploadUnsuccessful")

public String toUploadUnsuccessful(Model model,HttpServletRequest request) {
    Map<String, Object> modelMap = (Map<String, Object>) RequestContextUtils.getInputFlashMap(request);

    List<Scheme> year = schemeService.findDistinctYear();
    List<Plan> allTown = forestryService.getAllTown();
    List<Archive> byType = planService.findByType(MyConstant.UPLOAD_BANK_REBACK_FILE_TYPE);
    List<Map<String, String>> fileLists = new ArrayList<>();

    //archive的name 存放著這個檔案的批次 年度 城鎮 資訊,所以遍歷 逗號分隔
    byType.forEach(archive -> {
        String[] split = archive.getName().split(",");
        String substring = archive.getUrl().substring(archive.getUrl().lastIndexOf("/") + 1);

        Map<String, String> map = new HashMap<>(fileLists.size());
        map.put("id", archive.getId().toString());
        map.put("batch", split[1]);
        map.put("year", split[0]);
        map.put("town", split[2]);
        map.put("xlsName", substring);
        map.put("remark", "測試備註");

        fileLists.add(map);
    });


    allTown.stream().map(plan -> {
        Plan p = new Plan();
        p.setTown(plan.getTown());
        return p;
    });

    //sideBar
    model.addAttribute("sideBar_todo", true);
    model.addAttribute("sideBar_uploadUnsuccessful", true);


    //info
    model.addAttribute("scheme", year);
    model.addAttribute("towns", allTown);
    model.addAttribute("unsuccess", fileLists);

    if (modelMap!=null){
        model.addAttribute("archiveId",modelMap.get("archiveId"));
    }



    return "upload_unsuccessful";
}


@PostMapping("/unsuccessful_upload")
public String uploadUnsuccess(Model model,
                              MultipartFile upload,
                              String year,
                              String batch,
                              String town,
                              RedirectAttributes attributes

                              ) throws Exception {

    int archiveId = ImportReport.bankBackRead(upload, upload.getOriginalFilename(), year, batch, town, detailService, planService);
    /*
      匯入資料title頭的年度 批次 鎮 與頁面填寫額不一致
     */

    if (archiveId == 0) {
        model.addAttribute("errorMessage", "上傳失敗,請檢查Excel年度批次鄉鎮與頁面填寫是否一致");
        model.addAttribute("url", "uploadUnsuccessful");
        return "upload_unsuccessful_fail";
    }
    attributes.addFlashAttribute("archiveId",archiveId);

    return "redirect:/uploadUnsuccessful";
}

為什麼RedirectAttribute能做到呢?原理如下

在重定向時,程式會把我們的請求引數新增到FlashMap中,然後通過flashMapManager來儲存起來

	protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,    
	            HttpServletResponse response) throws IOException {    
	        //建立跳轉連結    
	        String targetUrl = createTargetUrl(model, request);    
	        targetUrl = updateTargetUrl(targetUrl, model, request, response);    
	        //獲取原請求所攜帶的資料    
	        FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request);    
	        if (!CollectionUtils.isEmpty(flashMap)) {    
	            UriComponents uriComponents = UriComponentsBuilder.fromUriString(targetUrl).build();    
	            flashMap.setTargetRequestPath(uriComponents.getPath());    
	            flashMap.addTargetRequestParams(uriComponents.getQueryParams());    
	            FlashMapManager flashMapManager = RequestContextUtils.getFlashMapManager(request);    
	            if (flashMapManager == null) {    
	                throw new IllegalStateException("FlashMapManager not found despite output FlashMap having been set");    
	            }    
	            //將資料儲存起來,作為跳轉之後請求的資料使用    
	            flashMapManager.saveOutputFlashMap(flashMap, request, response);    
	        }    
	        //重定向操作    
	        sendRedirect(request, response, targetUrl, this.http10Compatible);    
	    }  


FlashMapManager是一個介面,定義了儲存FlashMap和獲取FlashMap的方法。

兩個實現方法都在AbstractFlashMapManager抽象方法中:

saveOutputFlashMap方法實現如下

    
public final void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response) {
    if (!CollectionUtils.isEmpty(flashMap)) {
        String path = this.decodeAndNormalizePath(flashMap.getTargetRequestPath(), request);
        flashMap.setTargetRequestPath(path);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Saving FlashMap=" + flashMap);
        }

        flashMap.startExpirationPeriod(this.getFlashMapTimeout());
        Object mutex = this.getFlashMapsMutex(request);
        if (mutex != null) {
            synchronized(mutex) {
                List<FlashMap> allFlashMaps = this.retrieveFlashMaps(request);
                List<FlashMap> allFlashMaps = allFlashMaps != null ? allFlashMaps : new CopyOnWriteArrayList();
                ((List)allFlashMaps).add(flashMap);
                this.updateFlashMaps((List)allFlashMaps, request, response);
            }
        } else {
            List<FlashMap> allFlashMaps = this.retrieveFlashMaps(request);
            List<FlashMap> allFlashMaps = allFlashMaps != null ? allFlashMaps : new LinkedList();
            ((List)allFlashMaps).add(flashMap);
            this.updateFlashMaps((List)allFlashMaps, request, response);
        }

    }
}

updateFlashMaps的實現如下

protected void updateFlashMaps(List<FlashMap> flashMaps, HttpServletRequest request, HttpServletResponse response) {
    WebUtils.setSessionAttribute(request, FLASH_MAPS_SESSION_ATTRIBUTE, !flashMaps.isEmpty() ? flashMaps : null);
}


就是使用了session 


在dispatcher的doservice方法中   獲得session暫存域的東西 儲存至request中

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (this.logger.isDebugEnabled()) {
        String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
        this.logger.debug("DispatcherServlet with name '" + this.getServletName() + "'" + resumed + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
    }

    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
        attributesSnapshot = new HashMap();
        Enumeration attrNames = request.getAttributeNames();

        label112:
        while(true) {
            String attrName;
            do {
                if (!attrNames.hasMoreElements()) {
                    break label112;
                }

                attrName = (String)attrNames.nextElement();
            } while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));

            attributesSnapshot.put(attrName, request.getAttribute(attrName));
        }
    }

    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
    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);
    }

    try {
        this.doDispatch(request, response);
    } finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
            this.restoreAttributesAfterInclude(request, attributesSnapshot);
        }

    }

}


也就是說RedirectAttribute實現了  把值存入Session的FLASH_MAPS_SESSION_ATTRIBUTE中,然後在Dispatcher做doService的時候,就已經把Session的值複製到了Request中,然後Session的FLASH_MAPS_SESSION_ATTRIBUTE值會被清空,FLASH_MAPS_SESSION_ATTRIBUTE只是做一個暫存域(個人理解) 到了doService,會自動清空 ,所以Controller 中session看不到addAttribute值



下面的圖是我debug的結果  可見request確實存著值  且key為    DispatcherServlet.INPUT_FLASH_MAP




controller獲取值的兩種方式



至此,介紹完,如果有什麼錯誤,或者建議,可以在下方留言,共同討論。