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获取值的两种方式
至此,介绍完,如果有什么错误,或者建议,可以在下方留言,共同讨论。