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獲取值的兩種方式
至此,介紹完,如果有什麼錯誤,或者建議,可以在下方留言,共同討論。