Spring 跨重定向请求传递数据
在处理完POST请求后, 通常来讲一个最佳实践就是执行一下重定向。除了其他的一些因素外,这样做能够防止用户点击浏览器的刷新按钮或后退箭头时,客户端重新执行危险的POST请求。
在控制器方法返回的视图名称中,我们借助了“ redirect:” 前缀的力量。当控制器方法返回的String 值 以“ redirect:” 开头 的 话, 那么 这个 String 不是 用来 查找 视图 的, 而是 用来 指导 浏览器 进行 重定向 的 路径。 我们 可以 回头 看一下 程序 清单 5. 17, 可以看到 processRegistration() 方法 返回 的“ redirect: String” 如下 所示:
return "redirect:/ spitter/" + spitter. getUsername();
“redirect:” 前缀 能够 让 重定向 功能 变得 非常 简单。 你 可能 会想 Spring 很难 再让 重定向 功能 变得 更简单 了。 但是, 请 稍等: Spring 为重 定向 功能 还 提供 了 一些 其他 的 辅助 功能。 具体 来讲, 正在 发起 重定向 功能 的 方法 该 如何 发送 数据 给 重定向 的 目标 方法 呢? 一般 来讲, 当 一个 处理器 方法 完成 之后, 该 方法 所指 定的 模型 数据 将会 复制 到 请求 中, 并作 为 请求 中的 属性, 请求 会 转发( forward) 到 视图 上进 行 渲染。 因为 控制器 方法 和 视图 所 处理 的 是 同一个 请求, 所以 在 转发 的 过程中, 请求 属性 能够 得以 保存。
但是, 当 控制器 的 结果是 重定向 的 话, 原始 的 请求 就 结束 了, 并且 会 发起 一个 新的 GET 请求。 原始 请求 中 所带 有的 模型 数据 也就 随着请求一起 消亡 了。 在 新的 请求 属性 中, 没有 任何 的 模型 数据, 这个 请求 必须 要 自己 计算 数据。
图 1 模型 的 属性 是以 请求 属性 的 形式 存放 在 请求 中的, 在 重定向后无法存活
显然, 对于 重定向 来说, 模型 并不能 用来 传递 数据。 但是 我们 也有 一些 其他 方案, 能够 从 发起 重定向 的 方法 传递 数据 给 处理 重定向 方法 中: 使用 URL 模板 以 路径 变量 和/ 或 查询 参数 的 形式 传递 数据; 通过 flash 属性 发送 数据。
首先, 我们 看一下 Spring 如何 帮助 我们 通过 路径 变量 和/ 或 查询 参数 的 形式 传递 数据。
通过URL模板进行重定向
通过 路径 变量 和 查询 参数 传递 数据 看起来 非常 简单。 例如, 在 程序 清单 5. 19 中, 我们 以 路径 变量 的 形式 传递 了 新 创建 Spitter 的 username。 但是 按照 现在 的 写法, username 的 值 是 直接 连接 到 重定向 String 上 的。 这 能够 正常 运行, 但是 还 远远 不能 说 没有 问题。 当 构建 URL 或 SQL 查询 语句 的 时候, 使用 String 连接 是 很 危险 的。
return "redirect:/ spitter/{ username}";
除了 连接 String 的 方式 来 构建 重定向 URL, Spring 还 提供 了 使用 模板 的 方式 来 定义 重定向 URL。 例如, 在 程序 清单 5. 19 中, processRegistration() 方法 的 最后 一行 可以 改写 为 如下 的 形式:
@RequestMapping( value="/ register", method= POST)
public String processRegistration( Spitter spitter, Model model) {
spitterRepository. save( spitter);
model. addAttribute(" username", spitter. getUsername());
return "redirect:/ spitter/{ username}";
}
现在, username 作为 占位符 填充 到了 URL 模板 中, 而 不是 直接 连接 到 重定向 String 中, 所以 username 中 所有 的 不安全 字符 都会 进行 转义。 这样 会 更加 安全, 这里 允许 用户 输入 任何 想要 的 内容 作为 username, 并 会 将其 附加 到 路径 上。
除此之外, 模型 中 所有 其他 的 原始 类型 值 都可以 添加 到 URL 中 作为 查询 参数。 作为 样 例, 假设 除了 username 以外, 模型 中 还要 包含 新 创建 Spitter 对象 的 id 属性, 那 processRegistration() 方法 可以 改写 为 如下 的 形式:
@RequestMapping( value="/ register", method= POST)
public String processRegistration( Spitter spitter, Model model) {
spitterRepository. save( spitter);
model. addAttribute(" username", spitter. getUsername());
model. addAttribute(" spitterId", spitter. getId());
return "redirect:/ spitter/{ username}";
}
所 返回 的 重定向 String 并没有 太大 的 变化。 但是, 因为 模型 中的 spitterId 属性 没有 匹配 重定向 URL 中的 任何 占位符, 所以 它 会 自动 以 查询 参数 的 形式 附加 到 重定向 URL 上。
如果 username 属 性的 值 是 habuma 并且 spitterId 属 性的 值 是 42, 那么 结果 得到 的 重定向 URL 路径 将会 是“/ spitter/ habuma? spitterId= 42”。
通过 路径 变量 和 查询 参数 的 形式 跨 重定向 传递 数据 是 很 简单 直接 的 方式, 但它 也有 一定 的 限制。 它 只能 用来 发送 简单 的 值, 如 String 和 数字 的 值。 在 URL 中, 并没有 办法 发送 更为 复杂 的 值, 但这 正是 flash 属性 能够 提供 帮助 的 领域。
使用 flash 属性
假设 我们 不 想在 重定向 中 发送 username 或 ID 了, 而是 要 发送 实际 的 Spitter 对象。 如果 我们 只 发送 ID 的 话, 那么 处理 重定向 的 方法 还需 要从 数据库 中 查找 才能 得到 Spitter 对象。 但是, 在 重定向 之前, 我们 其实 已经 得到 了 Spitter 对象。 为什么 不 将其 发送 给 处理 重定向 的 方法, 并将 其 展现 出来 呢?
Spitter 对象 要比 String 和 int 更为 复杂。 因此, 我们 不能 像 路径 变量 或 查询 参数 那么 容易 地 发送 Spitter 对象。 它 只能 设置 为 模型 中的 属性。
但是, 正如 我们 前面 所 讨论 的 那样, 模型 数据 最终 是以 请求 参数 的 形式 复制 到 请求 中的, 当 重定向 发生 的 时候, 这些 数据 就会 丢失。 因此, 我们 需要 将 Spitter 对象 放到 一个 位置, 使其 能够 在 重定向 的 过程中 存活 下来。
有个 方案 是将 Spitter 放到 会话 中。 会话 能够 长期存在, 并且 能够 跨 多个 请求。 所以 我们 可以 在 重定向 发生 之前 将 Spitter 放到 会话 中, 并在 重定 向后, 从 会话 中将 其 取出。 当然, 我们 还要 负责 在 重定向 后 在 会话 中将 其 清理 掉。
实际上, Spring 也 认为 将 跨 重定向 存活 的 数据 放到 会话 中 是一 个 很不 错的 方式。 但是, Spring 认为 我们 并不 需要 管理 这些 数据, 相反, Spring 提供 了 将 数据 发送 为 flash 属性( flash attribute) 的 功能。 按照 定义, flash 属性 会 一直 携带 这些 数据 直到 下一 次 请求, 然后 才会 消失。
Spring 提供 了 通过 RedirectAttributes 设置 flash 属性 的 方法, 这是 Spring 3. 1 引入 的 Model 的 一个 子 接口。 RedirectAttributes 提供 了 Model 的 所有 功能, 除此之外, 还有 几个 方法 是 用来 设置 flash 属性 的。
具体 来讲, RedirectAttributes 提供 了 一组 addFlashAttribute() 方法 来 添加 flash 属性。 重新 看一下 processRegistration() 方法, 我们 可以 使用 addFlashAttribute() 将 Spitter 对象 添加 到 模型 中:
@RequestMapping( value="/ register", method= POST)
public String processRegistration( Spitter spitter, RedirectAttributes model) {
spitterRepository. save( spitter);
model. addAttribute(" username", spitter. getUsername());
model. addFlashAttribute(" spitter", spitter);
return "redirect:/ spitter/{ username}";
}
在这里, 我们 调用 了 addFlashAttribute() 方法, 并将 spitter 作为 key, Spitter 对象 作为 值。 另外, 我们 还可以 不 设置 key 参数, 让 key 根据 值 的 类型类型 自行 推断 得出:
model. addFlashAttribute( spitter);
因为 我们 传递 了 一个 Spitter 对象 给 addFlashAttribute() 方法, 所以 推断 得到 的 key 将会 是 spitter。
在 重定向 执行 之前, 所有 的 flash 属性 都会 复制 到会 话中。 在 重定 向后, 存在 会话 中的 flash 属性 会被 取出, 并从 会话 转移 到 模型 之中。 处理 重定向 的 方法 就能 从 模型 中 访问 Spitter 对象 了, 就 像 获取 其他 的 模型 对象 一样。 图 2阐述 了 它是 如何 运行 的。
图 2 flash 属性 保存 在 会话 中, 然后 再放 到 模型 中, 因此 能够 在 重定向 的 过程中 存活
为了 完成 flash 属性 的 流程, 如下 展现 了 更新 版本 的 showSpitterProfile() 方法, 在 从 数据库 中 查找 之前, 它 会 首先 从 模型 中 检查 Spitter 对象:
@RequestMapping( value="/{ username}", method= GET)
public String showSpitterProfile( @PathVariable String username, Model model) {
if (!model. containsAttribute(" spitter")) {
model. addAttribute( spitterRepository. findByUsername( username));
}
return "profile";
}
可以 看到, showSpitterProfile() 方法 所做 的 第一 件事 就是 检查 是否 存有 key 为 spitter 的 model 属性。 如果 模型 中 包含 spitter 属性, 那就 什么 都不 用 做了。 这里 面 包含 的 Spitter 对象 将会 传递 到 视图 中进 行 渲染。 但是 如果 模型 中 不 包含 spitter 属 性的 话, 那么 showSpitterProfile() 将会 从 Repository 中 查找 Spitter, 并将 其 存放 到 模型 中。
摘自:[美] Craig Walls 沃尔斯. Spring实战(第4版)