Spring 实战(第四版)
  • Spring 实战(第 4 版)
  • 第一部分 Spring 的核心
  • 第 1 章 Spring 之旅
    • 1.1 简化 Java 开发
      • 1.1.1 激发 POJO 的潜能
      • 1.1.2 依赖注入
      • 1.1.3 应用切面
      • 1.1.4 使用模板消除样式代码
    • 1.2 容纳你的 Bean
      • 1.2.1 使用应用上下文
      • 1.2.2 bean 的生命周期
    • 1.3 俯瞰 Spring 风景线
      • 1.3.1 Spring 模块
      • 1.3.2 Spring Portfolio
    • 1.4 Spring 的新功能
      • 1.4.1 Spring 3.1 新特性
      • 1.4.2 Spring 3.2 新特性
      • 1.4.3 Spring 4.0 新特性
    • 1.5 小结
  • 第 2 章 装配 Bean
    • 2.1 Spring 配置的可选方案
    • 2.2 自动化装配 bean
      • 2.2.1 创建可被发现的 bean
      • 2.2.2 为组件扫描的 bean 命名
      • 2.2.3 设置组件扫描的基础包
      • 2.2.4 通过为 bean 添加注解实现自动装配
      • 2.2.5 验证自动装配
    • 2.3 通过 Java 代码装配 bean
      • 2.3.1 创建配置类
      • 2.3.2 声明简单的 bean
      • 2.3.3 借助 JavaConfig 实现注入
    • 2.4 通过 XML 装配 bean
      • 2.4.1 创建 XML 配置规范
      • 2.4.2 声明一个简单的 <bean>
      • 2.4.3 借助构造器注入初始化 bean
      • 2.4.4 设置属性
    • 2.5 导入和混合配置
      • 2.5.1 在 JavaConfig 中引用 XML 配置
      • 2.5.2 在 XML 配置中引用 JavaConfig
    • 2.6 小结
  • 第 3 章 高级装配
    • 3.1 环境与 profile
      • 3.1.1 配置 profile bean
      • 3.1.2 激活 profile
    • 3.2 条件化的 bean
    • 3.3 处理自动装配的歧义性
      • 3.3.1 标示首选的 bean
      • 3.3.2 限定自动装配的 bean
    • 3.4 bean 的作用域
      • 3.4.1 使用会话和请求作用域
      • 3.4.2 在 XML 中声明作用域代理
    • 3.5 运行时值注入
      • 3.5.1 注入外部的值
      • 3.5.2 使用 Spring 表达式语言进行装配
    • 3.6 小结
  • 第 4 章 面向切面的 Spring
    • 4.1 什么是面向切面编程
      • 4.1.1 定义 AOP 术语
      • 4.1.2 Spring 对 AOP 的支持
    • 4.2 通过切点来选择连接点
      • 4.2.1 编写切点
      • 4.2.2 在切点中选择 bean
    • 4.3 使用注解创建切面
      • 4.3.1 定义切面
      • 4.3.2 创建环绕通知
      • 4.3.3 处理通知中的参数
      • 4.3.4 通过注解引入新功能
    • 4.4 在 XML 中声明切面
      • 4.4.1 声明前置和后置通知
      • 4.4.2 声明环绕通知
      • 4.4.3 为通知传递参数
      • 4.4.4 通过切面引入新的功能
    • 4.5 注入 AspectJ 切面
    • 4.6 小结
  • 第二部分 Web 中的 Spring
  • 第 5 章 构建 Spring Web 应用程序
    • 5.1 Spring MVC 起步
      • 5.1.1 跟踪 Spring MVC 的请求
      • 5.1.2 搭建 Spring MVC
      • 5.1.3 Spittr 应用简介
    • 5.2 编写基本的控制器
      • 5.2.1 测试控制器
      • 5.2.2 定义类级别的请求处理
      • 5.2.3 传递模型数据到视图中
    • 5.3 接受请求的输入
      • 5.3.1 处理查询参数
      • 5.3.2 通过路径参数接受输入
    • 5.4 处理表单
      • 5.4.1 编写处理表单的控制器
      • 5.4.2 校验表单
    • 5.5 小结
  • 第 6 章 渲染 Web 视图
    • 6.1 理解视图解析
    • 6.2 创建 JSP 视图
      • 6.2.1 配置适用于 JSP 的视图解析器
      • 6.2.2 使用 Spring 的 JSP 库
    • 6.3 使用 Apache Tiles 视图定义布局
      • 6.3.1 配置 Tiles 视图解析器
    • 6.4 使用 Thymeleaf
      • 6.4.1 配置 Thymeleaf 视图解析器
      • 6.4.2 定义 Thymeleaf 模板
    • 6.5 小结
  • 第 7 章 Spring MVC 的高级技术
    • 7.1 Spring MVC 配置的替代方案
      • 7.1.1 自定义 DispatcherServlet 配置
      • 7.1.2 添加其他的 Servlet 和 Filter
      • 7.1.3 在 web.xml 中声明 DispatcherServlet
    • 7.2 处理 multipart 形式的数据
      • 7.2.1 配置 multipart 解析器
      • 7.2.2 处理 multipart 请求
    • 7.3 处理异常
      • 7.3.1 将异常映射为 HTTP 状态码
      • 7.3.2 编写异常处理的方法
    • 7.4 为控制器添加通知
    • 7.5 跨重定向请求传递数据
      • 7.5.1 通过 URL 模板进行重定向
      • 7.5.2 使用 flash 属性
    • 7.6 小结
  • 第 8 章 使用 Spring Web Flow
    • 8.1 在 Spring 中配置 Web Flow
      • 8.1.1 装配流程执行器
      • 8.1.2 配置流程注册表
      • 8.1.3 处理流程请求
    • 8.2 流程的组件
      • 8.2.1 状态
      • 8.2.2 转移
      • 8.2.3 流程数据
    • 8.3 组合起来:披萨流程
      • 8.3.1 定义基本流程
      • 8.3.2 收集顾客信息
      • 8.3.3 构建订单
      • 8.3.4 支付
    • 8.4 保护 Web 流程
    • 8.5 小结
  • 第 9 章 保护 Web 应用
    • 9.1 Spring Security 简介
      • 9.1.1 理解 Spring Security 的模块
      • 9.1.2 过滤 Web 请求
      • 9.1.3 编写简单的安全性配置
    • 9.2 选择查询用户详细信息的服务
      • 9.2.1 使用基于内存的用户存储
      • 9.2.2 基于数据库表进行认证
      • 9.2.3 基于 LDAP 进行认证
      • 9.2.4 配置自定义的用户服务
    • 9.3 拦截请求
      • 9.3.1 使用 Spring 表达式进行安全保护
      • 9.3.2 强制通道的安全性
      • 9.3.3 防止跨站请求伪造
    • 9.4 认证用户
      • 9.4.1 添加自定义的登录页
      • 9.4.2 启用 HTTP Basic 认证
      • 9.4.3 启用 Remember-me 功能
      • 9.4.4 退出
    • 9.5 保护视图
      • 9.5.1 使用 Spring Security 的 JSP 标签库
      • 9.5.2 使用 Thymeleaf 的 Spring Security 方言
    • 9.6 小结
  • 第三部分 后端中的 Spring
  • 第 10 章 通过 Spring 和 JDBC 征服数据库
    • 10.1 Spring 的数据访问哲学
      • 10.1.1 了解 Spring 的数据访问异常体系
      • 10.1.2 数据访问模板化
    • 10.2 配置数据源
      • 10.2.1 使用 JNDI 数据源
      • 10.2.2 使用数据源连接池
      • 10.2.3 基于 JDBC 驱动的数据源
      • 10.2.4 使用嵌入式的数据源
      • 10.2.5 使用 profile 选择数据源
    • 10.3 在 Spring 中使用 JDBC
      • 10.3.1 应对失控的 JDBC 代码
      • 10.3.2 使用 JDBC 模板
    • 10.4 小结
  • 第 11 章 使用对象-关系映射持久化数据
    • 11.1 在 Spring 中集成 Hibernate
      • 11.1.1 声明 Hibernate 的 Session 工厂
      • 11.1.2 构建不依赖于 Spring 的 Hibernate 代码
    • 11.2 Spring 与 Java 持久化 API
      • 11.2.1 配置实体管理器工厂
      • 11.2.2 编写基于 JPA 的 Repository
    • 11.3 借助 Spring Data 实现自动化的 JPARepository
      • 11.3.1 定义查询方法
      • 11.3.2 声明自定义查询
      • 11.3.3 混合自定义的功能
    • 11.4 小结
  • 第 12 章 使用 NoSQL 数据库
    • 12.1 使用 MongoDB 持久化文档数据
      • 12.1.1 启用 MongoDB
      • 12.1.2 为模型添加注解,实现 MongoDB 持久化
      • 12.1.3 使用 MongoTemplate 访问 MongoDB
      • 12.1.4 编写 MongoDB Repository
    • 12.2 使用 Neo4j 操作图数据
      • 12.2.1 配置 Spring Data Neo4j
      • 12.2.2 使用注解标注图实体
      • 12.2.3 使用 Neo4jTemplate
      • 12.2.4 创建自动化的 Neo4j Repository
    • 12.3 使用 Redis 操作 key-value 数据
      • 12.3.1 连接到 Redis
      • 12.3.2 使用 Redis Template
      • 12.3.3 使用 key 和 value 的序列化器
    • 12.4 小结
  • 第 13 章 缓存数据
    • 13.1 启用对缓存的支持
      • 13.1.1 配置缓存管理器
    • 13.2 为方法添加注解以支持缓存
      • 13.2.1 填充缓存
      • 13.2.2 移除缓存条目
    • 13.3 使用 XML 声明缓存
    • 13.4 小结
  • 第 14 章 保护方法应用
    • 14.1 使用注解保护方法
      • 14.1.1 使用 @Secured 注解限制方法调用
      • 14.1.2 在 Spring Security 中使用 JSR-250 的 @RolesAllowed 注解
    • 14.2 使用表达式实现方法级别的安全性
      • 14.2.1 表述方法访问规则
      • 14.2.2 过滤方法的输入和输出
    • 14.3 小结
  • 第四部分 Spring 集成
  • 第 15 章 使用远程服务
    • 15.1 Spring 远程调用概览
    • 15.2 使用 RMI
      • 15.2.1 导出 RMI 服务
      • 15.2.2 装配 RMI 服务
    • 15.3 使用 Hessian 和 Burlap 发布远程服务
      • 15.3.1 使用 Hessian 和 Burlap 导出 bean 的功能
      • 15.3.2 访问 Hessian/Burlap 服务
    • 15.4 使用 Spring 的 HttpInvoker
      • 15.4.1 将 bean 导出为 HTTP 服务
      • 15.4.2 通过 HTTP 访问服务
    • 15.5 发布和使用 Web 服务
      • 15.5.1 创建基于 Spring 的 JAX-WS 端点
      • 15.5.2 在客户端代理 JAX-WS 服务
    • 15.6 小结
  • 第 16 章 使用 Spring MVC 创建 REST API
    • 16.1 了解 REST
      • 16.1.1 REST 的基础知识
      • 16.1.2 Spring 是如何支持 REST 的
    • 16.2 创建第一个 REST 端点
      • 16.2.1 协商资源表述
      • 16.2.2 使用 HTTP 信息转换器
    • 16.3 提供资源之外的其他内容
      • 16.3.1 发送错误信息到客户端
      • 16.3.2 在响应中设置头部信息
    • 16.4 编写 REST 客户端
      • 16.4.1 了解 RestTemplate 的操作
      • 16.4.2 GET 资源
      • 16.4.3 检索资源
      • 16.4.4 抽取响应的元数据
      • 16.4.5 PUT 资源
      • 16.4.6 DELETE 资源
      • 16.4.7 POST 资源数据
      • 16.4.8 在 POST 请求中获取响应对象
      • 16.4.9 在 POST 请求后获取资源位置
      • 16.4.10 交换资源
    • 16.5 小结
  • 第 17 章 Spring 消息
    • 17.1 异步消息简介
      • 17.1.1 发送消息
      • 17.1.2 评估异步消息的优点
    • 17.2 使用 JMS 发送消息
      • 17.2.1 在 Spring 中搭建消息代理
      • 17.2.2 使用 Spring 的 JMS 模板
      • 17.2.3 创建消息驱动的 POJO
      • 17.2.4 使用基于消息的 RPC
    • 17.3 使用 AMQP 实现消息功能
      • 17.3.1 AMQP 简介
      • 17.3.2 配置 Spring 支持 AMQP 消息
      • 17.3.3 使用 RabbitTemplate 发送消息
      • 17.3.4 接收 AMQP 消息
    • 17.4 小结
  • 第 18 章 使用 WebSocket 和 STOMP 实现消息功能
    • 18.1 使用 Spring 的低层级 WebSocket API
    • 18.2 应对不支持 WebSocket 的场景
    • 18.3 使用 STOMP 消息
      • 18.3.1 启用 STOMP 消息功能
      • 18.3.2 处理来自客户端的 STOMP 消息
      • 18.3.3 发送消息到客户端
    • 18.4 为目标用户发送消息
      • 18.4.1 在控制器中处理用户的消息
      • 18.4.2 为指定用户发送消息
    • 18.5 处理消息异常
    • 18.6 小结
  • 第 19 章 使用 Spring 发送 Email
    • 19.1 配置 Spring 发送邮件
      • 19.1.1 配置邮件发送器
      • 19.1.2 装配和使用邮件发送器
    • 19.2 构建丰富内容的 Email 消息
      • 19.2.1 添加附件
      • 19.2.2 发送富文本内容的 Email
    • 19.3 使用模板生成 Email
      • 19.3.1 使用 Velocity 构建 Email 消息
      • 19.3.2 使用 Thymeleaf 构建 Email 消息
    • 19.4 小结
  • 第 20 章 使用 JMX 管理 SpringBean
    • 20.1 将 Spring bean 导出为 MBean
      • 20.1.1 通过名称暴露方法
      • 20.1.2 使用接口定义 MBean 的操作和属性
      • 20.1.3 使用注解驱动的 MBean
      • 20.1.4 处理 MBean 冲突
    • 20.2 远程 MBean
      • 20.2.1 暴露远程 MBean
      • 20.2.2 访问远程 MBean
      • 20.2.3 代理 MBean
    • 20.3 处理通知
      • 20.3.1 监听通知
    • 20.4 小结
  • 第 21 章 借助 Spring Boot 简化 Spring 开发
    • 21.1 Spring Boot 简介
      • 21.1.1 添加 Starter 依赖
      • 21.1.2 自动配置
      • 21.1.3 Spring Boot CLI
      • 21.1.4 Actuator
    • 21.2 使用 Spring Boot 构建应用
      • 21.2.1 处理请求
      • 21.2.2 创建视图
      • 21.2.3 添加静态内容
      • 21.2.4 持久化数据
      • 21.2.5 尝试运行
    • 21.3 组合使用 Groovy 与 Spring Boot CLI
      • 21.3.1 编写 Groovy 控制器
      • 21.3.2 使用 Groovy Repository 实现数据持久化
      • 21.3.3 运行 Spring Boot CLI
    • 21.4 通过 Actuator 获取了解应用内部状况
    • 21.5 小结
Powered by GitBook
On this page

Was this helpful?

  1. 第 16 章 使用 Spring MVC 创建 REST API
  2. 16.2 创建第一个 REST 端点

16.2.1 协商资源表述

你可以回忆一下在第 5 章中(以及图 5.1 所示),当控制器的处理方法完成时,通常会返回一个逻辑视图名。如果方法不直接返回逻辑视图名(例如方法返回 void),那么逻辑视图名会根据请求的 URL 判断得出。DispatcherServlet 接下来会将视图的名字传递给一个视图解析器,要求它来帮助确定应该用哪个视图来渲染请求结果。

在面向人类访问的 Web 应用程序中,选择的视图通常来讲都会渲染为 HTML。视图解析方案是个简单的一维活动。如果根据视图名匹配上了视图,那这就是我们要用的视图了。

当要将视图名解析为能够产生资源表述的视图时,我们就有另外一个维度需要考虑了。视图不仅要匹配视图名,而且所选择的视图要适合客户端。如果客户端想要 JSON,那么渲染 HTML 的视图就不行了 —— 尽管视图名可能匹配。

Spring 的 ContentNegotiatingViewResolver 是一个特殊的视图解析器,它考虑到了客户端所需要的内容类型。按照其最简单的形式,ContentNegotiatingViewResolver 可以按照下述形式进行配置:

@Bean
public ViewResolver cnViewResilver() {
  return new ContentNegotiatingViewResolver();
}

在这个简单的 bean 声明背后会涉及到很多事情。要理解 ContentNegotiatingViewResolver 是如何工作的,这涉及内容协商的两个步骤:

  1. 确定请求的媒体类型;

  2. 找到适合请求媒体类型的最佳视图。

让我们深入了解每个步骤来了解 ContentNegotiatingViewResolver 是如何完成其任务的,首先从弄明白客户端需要什么类型的内容开始。

确定请求的媒体类型

在内容协商两步骤中,第一步是确定客户端想要什么类型的内容表述。表面上看,这似乎是一个很简单的事情。难道请求的 Accept 头部信息不是已经很清楚地表明要发送什么样的表述给客户端吗?

遗憾的是,Accept 头部信息并不总是可靠的。如果客户端是 Web 浏览器,那并不能保证客户端需要的类型就是浏览器在 Accept 头部所发送的值。Web 浏览器一般只接受对人类用户友好的内容类型(如 text/html),所以没有办法(除了面向开发人员的浏览器插件) 指定不同的内容类型。

ContentNegotiatingViewResolver 将会考虑到 Accept 头部信息并使用它所请求的媒体类型,但是它会首先查看 URL 的文件扩展名。如果 URL 在结尾处有文件扩展名的 话,ContentNegotiatingViewResolver 将会基于该扩展名确定所需的类型。如果扩展名是 “.json” 的话,那么所需的内容类型必须是 “application/json”。如果扩展名是 “.xml”,那么客户端请求的就是 “application/xml”。当然,“.html” 扩展名表明客户端所需的资源表述为 HTML(text/html)。

如果根据文件扩展名不能得到任何媒体类型的话,那就会考虑请求中的 Accept 头部信息。在这种情况下,Accept 头部信息中的值就表明了客户端想要的 MIME 类型,没有必要再去查找了。

最后,如果没有 Accept 头部信息,并且扩展名也无法提供帮助的话,ContentNegotiatingViewResolver 将会使用 “/” 作为默认的内容类型,这就意味着客户端必须要接收服务器发送的任何形式的表述。

一旦内容类型确定之后,ContentNegotiatingViewResolver 就该将逻辑视图名解析为渲染模型的 View。与 Spring 的其他视图解析器不同,ContentNegotiatingViewResolver 本身不会解析视图。而是委托给其他的视图解析器,让它们来解析视图。

ContentNegotiatingViewResolver 要求其他的视图解析器将逻辑视图名解析为视图。解析得到的每个视图都会放到一个列表中。这个列表装配完成后,ContentNegotiatingViewResolver 会循环客户端请求的所有媒体类型,在候选的视图中查找能够产生对应内容 类型的视图。第一个匹配的视图会用来渲染模型。

影响媒体类型的选择

在上述的选择过程中,我们阐述了确定所请求媒体类型的默认策略。但是通过为其设置一个 ContentNegotiationManager,我们能够改变它的行为。借助 ContentNegotiationManager 我们所能做到的事情如下所示:

  • 指定默认的内容类型,如果根据请求无法得到内容类型的话,将会使用默认值;

  • 通过请求参数指定内容类型;

  • 忽视请求的 Accept 头部信息;

  • 将请求的扩展名映射为特定的媒体类型;

  • 将 JAF(Java Activation Framework)作为根据扩展名查找媒体类型的备用方案。

有三种配置 ContentNegotiationManager 的方法:

  • 直接声明一个 ContentNegotiationManager 类型的 bean;

  • 通过 ContentNegotiationManagerFactoryBean 间接创建 bean;

  • 重载 WebMvcConfigurerAdapter 的 configureContentNegotiation() 方法。

直接创建 ContentNegotiationManager 有一些复杂,除非有充分的原因,否则我们不会愿意这样做。后两种方案能够让创建 ContentNegotiationManager 更加简单。

ContentNegotiationManager 是在 Spring 3.2 中加入的 ContentNegotiationManager 是 Spring 中相对比较新的功能,是在 Spring 3.2 中引入的。在 Spring 3.2 之前,ContentNegotiatingViewResolver 的很多行为都是通过直接设置 ContentNegotiatingViewResolver 的属性进行配置的。从 Spring 3.2 开始,ContentNegotiatingViewResolver 的大多数 Setter 方法都废弃了,鼓 励通过 ContentNegotiationManager 来进行配置。

尽管我不会在本章中介绍配置 ContentNegotiatingViewResolver 的旧方法,但是我们在创建 ContentNegotiationManager 所设置的很多属性,在 ContentNegotiatingViewResolver 中都有对应的属性。如果你使用较早版本的 Spring 的话,应该能够很容易地将新的配置方式对应到旧配置方式中。

一般而言,如果我们使用 XML 配置 ContentNegotiationManager 的话,那最有用的将会是 ContentNegotiationManagerFactoryBean。例如,我们可能希望在 XML 中配置 ContentNegotiationManager 使用 “application/json” 作为默认的内容类型:

<bean id="contentNegotiotionManager"
      class="org.springframework.http.ContentNegotiationManagerFactoryBean"
      p:defaultContentType="application/json" />

因为 ContentNegotiationManagerFactoryBean 是 FactoryBean 的实现,所以它会创建一个 ContentNegotiationManager bean。这个 ContentNegotiationManager 能够注入到 ContentNegotiatingViewResolver 的 contentNegotiationManager 属性中。

如果使用 Java 配置的话,获得 ContentNegotiationManager 的最简便方法就是扩展 WebMvcConfigurerAdapter 并重载 configureContentNegotiation() 方法。在创建 Spring MVC 应用的时候,我们很可能已经扩展了 WebMvcConfigurerAdapter。例如,在 Spittr 应用中,我们已经有了 WebMvcConfigurerAdapter 的扩展类,名为 WebConfig,所以需要做的就是重载 configureContentNegotiation() 方法。如下就是 configureContentNegotiation() 的一个实现,它设置了默认的内容类型:

@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
  configurer.defualtContentType(MediaType.APPLICATION_JSON);
}

我们可以看到,configureContentNegotiation() 方法给定了一个 ContentNegotiationConfigurer 对象。ContentNegotiationConfigurer 中的一些方法对应于 ContentNegotiationManager 的 Setter 方法,这样我们就能在 ContentNegotiationManager 创建时,设置任意内容协商相关的属性。在本例中,我们调用 defaultContentType() 方法将默认的内容类型设置为 “application/json”。

现在,我们已经有了 ContentNegotiationManager bean,接下来就需要将它注入到 ContentNegotiatingViewResolver 的 contentNegotiationManager 属性中。这需要我们稍微修改一下之前声明 ContentNegotiatingViewResolver 的 @Bean 方法:

@Bean
public ViewResolver cnViewResilver(ContentNegotiatingManager cnm) {
  ContentNegotiatingViewResolver cnvr = new ContentNegotiatingViewResolver();
  cnvr.setContentNegotiation(cnm);
  return cnvr;
}

这个 @Bean 方法注入了 ContentNegotiationManager,并使用它调用了 setContentNegotiationManager()。这样的结果就是 ContentNegotiatingView、Resolver 将会使用 ContentNegotiationManager 所定义的行为。

配置 ContentNegotiationManager 有很多的细节,在这里无法对它们进行一一介绍。如下的程序清单是一个非常简单的配置样例,当我使用 ContentNegotiatingViewResolver 的时候,通常会采用这种用法:它默认会使用 HTML 视图,但是对特定的视图名称将会渲染为 JSON 输出。

程序清单 16.2 配置 ContentNegotiationManager
@Bean
public ViewResolver cnViewResolver(ContentNegotiationManager cnm) {
  ContentNegotiatingViewResolver cnvr = new ContentNegotiatingViewResolver();
  cnvr.setContentNegotiationManager(cnm);
  return cnvr;
}

@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
  configurer.defaultContentType(MediaType.TEXT_HTML);
}
    
@Bean
public ViewResolver beanNameViewResolver() {
  return new BeanNameViewResolver();
}
    
@Bean
public View spittles() {
  return new MappingJackson2JsonView();
}

除了程序清单 16.2 中的内容以外,还应该有一个能够处理 HTML 的视图解析器(如 InternalResourceViewResolver 或 TilesViewResolver)。在大多数场景下,ContentNegotiatingViewResolver 会假设客户端需要 HTML,如 ContentNegotiationManager 配置所示。但是,如果客户端指定了它想要 JSON(通过在请求路径上使用 “.json” 扩展名或 Accept 头部信息)的话,那么 ContentNegotiatingViewResolver 将会查找能够处理 JSON 视图的视图解析器。

如果逻辑视图的名称为 “spittles”,那么我们所配置的 BeanNameViewResolver 将会解析 spittles() 方法中所声明的 View。这是因为 bean 名称匹配逻辑视图的名称。如果没有匹配的 View 的话,ContentNegotiatingViewResolver 将会采用默认的行为,将其输出为 HTML。

ContentNegotiatingViewResolver 一旦能够确定客户端想要什么样的媒体类型,接下来就是查找渲染这种内容的视图。

ContentNegotiatingViewResolver 的优势与限制

ContentNegotiatingViewResolver 最大的优势在于,它在 Spring MVC 之上构建了 REST 资源表述层,控制器代码无需修改。相同的一套控制器方法能够为面向人类的用户产生 HTML 内容,也能针对不是人类的客户端产生 JSON 或 XML。

如果面向人类用户的接口与面向非人类客户端的接口之间有很多重叠的话,那么内容协商是一种很便利的方案。在实践中,面向人类用户的视图与 REST API 在细节上很少能够处于相同的级别。如果面向人类用户的接口与面向非人类客户端的接口之间没有太多重叠的话,那么 ContentNegotiatingViewResolver 的优势就体现不出来了。

ContentNegotiatingViewResolver 还有一个严重的限制。作为 ViewResolver 的实现,它只能决定资源该如何渲染到客户端,并没有涉及到客户端要发送什么样的表述给控制器使用。如果客户端发送 JSON 或 XML 的话,那么 ContentNegotiatingViewResolver 就无法提供帮助了。

ContentNegotiatingViewResolver 还有一个相关的小问题,所选中的 View 会渲染模型给客户端,而不是资源。这里有个细微但很重要的区别。当客户端请求 JSON 格式的 Spittle 对象列表时,客户端希望得到的响应可能如下所示:

[
 {
   "id": 42,
   "latitude": 28.419489,
   "longitude": -81.581184,
   "message": "Hello World",
   "time": 140038920000 
 },
 {
   "id": 43,
   "latitude": 28.419136,
   "longitude": -81.577225,
   "message": "Blast off!",
   "time": 140047560000
 }
]

而模型是 key-value 组成的 Map,那么响应可能会如下所示:

{
  "spittleList": [
    {
      "id": 42,
      "latitude": 28.419489,
      "longitude": -81.581184,
      "message": "Hello World",
      "time": 140038920000
    },
    {
      "id": 43,
      "latitude": 28.419136,
      "longitude": -81.577225,
      "message": "Blast off!",
      "time": 140047560000
    }
  ]
}

尽管这不是很严重的问题,但确实可能不是客户端所预期的结果。因为有这些限制,我通常建议不要使用 ContentNegotiatingViewResolver。我更加倾向于使用 Spring 的消息转换功能来生成资源表述。接下来,我们看一下如何在控制器代码中使用 Spring 的消息转换器。

Previous16.2 创建第一个 REST 端点Next16.2.2 使用 HTTP 信息转换器

Last updated 5 years ago

Was this helpful?