文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

SpringMVC 中的参数还能这么传递?涨姿势了!

2024-12-03 10:01

关注

今天来聊一个 JavaWeb 中简单的话题,但是感觉却比较稀罕,因为这个技能点,有的小伙伴们可能没听过!

1.缘起

说到 Web 请求参数传递,大家能想到哪些参数传递方式?

参数可以放在地址栏中,不过地址栏参数的长度有限制,并且在有的场景下我们可能不希望参数暴漏在地址栏中。参数可以放在请求体中,这个没啥好说的。

小伙伴们试想这样一个场景:

在一个电商项目中,有一个提交订单的请求,这个请求是一个 POST 请求,请求参数都在请求体中。当用户提交成功后,为了防止用户刷新浏览器页面造成订单请求重复提交,我们一般会将用户重定向到一个显示订单的页面,这样即使用户刷新页面,也不会造成订单请求重复提交。

大概的代码就像下面这样:

  1. @Controller 
  2. public class OrderController { 
  3.     @PostMapping("/order"
  4.     public String order(OrderInfo orderInfo) { 
  5.         //其他处理逻辑 
  6.         return "redirect:/orderlist"
  7.     } 

这段代码我相信大家都懂吧!如果不懂可以看看松哥录制的免费的 SpringMVC 入门教程(硬核!松哥又整了一套免费视频,搞起!)。

但是这里有一个问题:如果我想传递参数怎么办?

如果是服务器端跳转,我们可以将参数放在 request 对象中,跳转完成后还能拿到参数,但是如果是客户端跳转我们就只能将参数放在地址栏中了,像上面这个方法的返回值我们可以写成:return "redirect:/orderlist?xxx=xxx";,这种传参方式有两个缺陷:

那该怎么办?还有办法传递参数吗?

有!这就是今天松哥要和大家介绍的 flashMap,专门用来解决重定向时参数的传递问题。

2.flashMap

在重定向时,如果需要传递参数,但是又不想放在地址栏中,我们就可以通过 flashMap 来传递参数,松哥先来一个简单的例子大家看看效果:

首先我们定义一个简单的页面,里边就一个 post 请求提交按钮,如下:

  1.  
  2. "en"
  3.  
  4.     "UTF-8"
  5.     Title 
  6.  
  7.  
  8. action="/order"
  9.     "submit" value="提交"
  10.  
  11.  
  12.  

然后在服务端接收该请求,并完成重定向:

  1. @Controller 
  2. public class OrderController { 
  3.     @PostMapping("/order"
  4.     public String order(HttpServletRequest req) { 
  5.         FlashMap flashMap = (FlashMap) req.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE); 
  6.         flashMap.put("name""江南一点雨"); 
  7.         return "redirect:/orderlist"
  8.     } 
  9.  
  10.     @GetMapping("/orderlist"
  11.     @ResponseBody 
  12.     public String orderList(Model model) { 
  13.         return (String) model.getAttribute("name"); 
  14.     } 

首先在 order 接口中,获取到 flashMap 属性,然后存入需要传递的参数,这些参数最终会被 SpringMVC 自动放入重定向接口的 Model 中,这样我们在 orderlist 接口中,就可以获取到该属性了。

当然,这是一个比较粗糙的写法,我们还可以通过 RedirectAttributes 来简化这一步骤:

  1. @Controller 
  2. public class OrderController { 
  3.     @PostMapping("/order"
  4.     public String order(RedirectAttributes attr) { 
  5.         attr.addFlashAttribute("site""www.javaboy.org"); 
  6.         attr.addAttribute("name""微信公众号:江南一点雨"); 
  7.         return "redirect:/orderlist"
  8.     } 
  9.  
  10.     @GetMapping("/orderlist"
  11.     @ResponseBody 
  12.     public String orderList(Model model) { 
  13.         return (String) model.getAttribute("site"); 
  14.     } 

RedirectAttributes 中有两种添加参数的方式:

经过前面的讲解,现在小伙伴们应该大致明白了 flashMap 的作用了,就是在你进行重定向的时候,不通过地址栏传递参数。

很多小伙伴可能会有疑问,重定向其实就是浏览器发起了一个新的请求,这新的请求怎么就获取到上一个请求保存的参数呢?这我们就要来看看 SpringMVC 的源码了。

3.源码分析

首先这里涉及到一个关键类叫做 FlashMapManager,如下:

  1. public interface FlashMapManager { 
  2.  @Nullable 
  3.  FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response); 
  4.  void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response); 

两个方法含义一眼就能看出来:

retrieveAndUpdate:这个方法用来恢复参数,并将恢复过的的参数和超时的参数从保存介质中删除。

saveOutputFlashMap:将参数保存保存起来。

FlashMapManager 的实现类如下:

从这个继承类中,我们基本上就能确定默认的保存介质时 session。具体的保存逻辑则是在 AbstractFlashMapManager 类中。

整个参数传递的过程可以分为三大步:

第一步,首先我们将参数设置到 outputFlashMap 中,有两种设置方式:我们前面的代码 req.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE) 就是直接获取 outputFlashMap 对象然后把参数放进去;第二种方式就是通过在接口中添加 RedirectAttributes 参数,然后把需要传递的参数放入 RedirectAttributes 中,这样当处理器处理完毕后,会自动将其设置到 outputFlashMap 中,具体逻辑在 RequestMappingHandlerAdapter#getModelAndView 方法中:

  1. private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, 
  2.   ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { 
  3.  //省略... 
  4.  if (model instanceof RedirectAttributes) { 
  5.   Map flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); 
  6.   HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); 
  7.   if (request != null) { 
  8.    RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); 
  9.   } 
  10.  } 
  11.  return mav; 

可以看到,如果 model 是 RedirectAttributes 的实例的话,则通过 getOutputFlashMap 方法获取到 outputFlashMap 属性,然后相关的属性设置进去。

这是第一步,就是将需要传递的参数,先保存到 flashMap 中。

第二步,重定向对应的视图是 RedirectView,在它的 renderMergedOutputModel 方法中,会调用 FlashMapManager 的 saveOutputFlashMap 方法,将 outputFlashMap 保存到 session 中,如下:

  1. protected void renderMergedOutputModel(Map model, HttpServletRequest request, 
  2.   HttpServletResponse response) throws IOException { 
  3.  String targetUrl = createTargetUrl(model, request); 
  4.  targetUrl = updateTargetUrl(targetUrl, model, request, response); 
  5.  // Save flash attributes 
  6.  RequestContextUtils.saveOutputFlashMap(targetUrl, request, response); 
  7.  // Redirect 
  8.  sendRedirect(request, response, targetUrl, this.http10Compatible); 

RequestContextUtils.saveOutputFlashMap 方法最终就会调用到 FlashMapManager 的 saveOutputFlashMap 方法,将 outputFlashMap 保存下来。我们来大概看一下保存逻辑:

  1. public final void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response) { 
  2.  if (CollectionUtils.isEmpty(flashMap)) { 
  3.   return
  4.  } 
  5.  String path = decodeAndNormalizePath(flashMap.getTargetRequestPath(), request); 
  6.  flashMap.setTargetRequestPath(path); 
  7.  flashMap.startExpirationPeriod(getFlashMapTimeout()); 
  8.  Object mutex = getFlashMapsMutex(request); 
  9.  if (mutex != null) { 
  10.   synchronized (mutex) { 
  11.    List allFlashMaps = retrieveFlashMaps(request); 
  12.    allFlashMaps = (allFlashMaps != null ? allFlashMaps : new CopyOnWriteArrayList<>()); 
  13.    allFlashMaps.add(flashMap); 
  14.    updateFlashMaps(allFlashMaps, request, response); 
  15.   } 
  16.  } 
  17.  else { 
  18.   List allFlashMaps = retrieveFlashMaps(request); 
  19.   allFlashMaps = (allFlashMaps != null ? allFlashMaps : new ArrayList<>(1)); 
  20.   allFlashMaps.add(flashMap); 
  21.   updateFlashMaps(allFlashMaps, request, response); 
  22.  } 

其实这里的逻辑也很简单,保存之前会给 flashMap 设置两个属性,一个是重定向的 url 地址,另一个则是过期时间,过期时间默认 180 秒,这两个属性在第三步加载 flashMap 的时候会用到。然后将 flashMap 放入集合中,并调用 updateFlashMaps 方法存入 session 中。

第三步,当重定向请求到达 DispatcherServlet#doService 方法后,此时会调用 FlashMapManager#retrieveAndUpdate 方法从 Session 中获取 outputFlashMap 并设置到 Request 属性中备用(最终会被转化到 Model 中的属性),相关代码如下:

  1. protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { 
  2.  //省略... 
  3.  if (this.flashMapManager != null) { 
  4.   FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); 
  5.   if (inputFlashMap != null) { 
  6.    request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); 
  7.   } 
  8.   request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); 
  9.   request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); 
  10.  } 
  11.  //省略... 

注意这里获取出来的 outputFlashMap 换了一个名字,变成了 inputFlashMap,其实是同一个东西。

我们可以大概看一下获取的逻辑 AbstractFlashMapManager#retrieveAndUpdate:

  1. public final FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response) { 
  2.  List allFlashMaps = retrieveFlashMaps(request); 
  3.  if (CollectionUtils.isEmpty(allFlashMaps)) { 
  4.   return null
  5.  } 
  6.  List mapsToRemove = getExpiredFlashMaps(allFlashMaps); 
  7.  FlashMap match = getMatchingFlashMap(allFlashMaps, request); 
  8.  if (match != null) { 
  9.   mapsToRemove.add(match); 
  10.  } 
  11.  if (!mapsToRemove.isEmpty()) { 
  12.   Object mutex = getFlashMapsMutex(request); 
  13.   if (mutex != null) { 
  14.    synchronized (mutex) { 
  15.     allFlashMaps = retrieveFlashMaps(request); 
  16.     if (allFlashMaps != null) { 
  17.      allFlashMaps.removeAll(mapsToRemove); 
  18.      updateFlashMaps(allFlashMaps, request, response); 
  19.     } 
  20.    } 
  21.   } 
  22.   else { 
  23.    allFlashMaps.removeAll(mapsToRemove); 
  24.    updateFlashMaps(allFlashMaps, request, response); 
  25.   } 
  26.  } 
  27.  return match; 

这就是整个获取 flashMap 的方法,整体来看还是非常 easy 的,并没有什么难点。

4.小结

好啦,今天就和小伙伴们分享了一下 SpringMVC 中的 flashMap,不知道大家有没有在工作中用到这个东西?如果刚好碰到松哥前面所说的需求,用 FlashMap 真的还是蛮方便的。

本文转载自微信公众号「江南一点雨」,可以通过以下二维码关注。转载本文请联系江南一点雨公众号。

 

来源:江南一点雨内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯