React.js 和 Spring Data REST:第 2 部分 - 超媒体

工程 | Greg L. Turnquist | 2015 年 9 月 15 日 | ...
要查看此代码的更新,请访问我们的 React.js 和 Spring Data REST 教程

上一节中,您了解了如何使用 Spring Data REST 搭建一个后端工资服务来存储员工数据。它缺少一个关键功能,即使用超媒体控件和通过链接导航。相反,它硬编码了查找数据的路径。

您可以随意从该仓库中获取代码并跟着操作。本节基于上一节的应用,并增加了一些额外内容。

起初有数据…​然后有了 REST

越来越多的人将任何基于 HTTP 的接口称为 REST API,这让我感到沮丧。今天的例子是 SocialSite REST API。那是 RPC。它完全是 RPC…​。需要做些什么才能让 REST 架构风格明确超文本是一种约束的概念?换句话说,如果应用程序状态引擎(以及 API)不是由超文本驱动的,那么它就不能是 RESTful 的,也不能是 REST API。就这么简单。是否有什么损坏的手册需要修正?

那么,超媒体控件,即超文本,到底是什么,以及如何使用它们?要弄清楚这一点,让我们退后一步,看看 REST 的核心使命。

REST 的概念是借鉴使网络如此成功的思想并将其应用于 API。尽管网络规模庞大、性质动态,并且客户端(即浏览器)更新速度慢,但网络取得了惊人的成功。Roy Fielding 试图利用其某些约束和特性,看看这是否能为 API 的生产和消费带来类似的扩展。

其中一个约束是限制动词的数量。对于 REST,主要的动词是 GET、POST、PUT、DELETE 和 PATCH。还有其他动词,但我们在此不深入讨论。

  • GET - 获取资源的状态,而不改变系统
  • POST - 创建一个新资源,而不指定位置
  • PUT - 替换现有资源,覆盖原有的任何内容(如果有的话)
  • DELETE - 删除现有资源
  • PATCH - 部分修改现有资源

这些是标准化且规范完备的 HTTP 动词。通过采用和使用已经定义的 HTTP 操作,我们无需发明一种新语言并对行业进行教育。

REST 的另一个约束是使用媒体类型来定义数据的格式。与其让每个人都为信息交换编写自己的方言,不如明智地开发一些媒体类型。最受欢迎并已被接受的媒体类型之一是 HAL,媒体类型为 application/hal+json。它是 Spring Data REST 的默认媒体类型。一个重要的价值在于,REST 没有中心化的、单一的媒体类型。相反,人们可以开发媒体类型并将其接入。进行尝试。随着不同需求的出现,行业可以灵活地发展。

REST 的一个关键特性是包含指向相关资源的链接。例如,如果您正在查看一个订单,一个 RESTful API 将包含指向相关客户的链接、指向商品目录的链接,以及可能指向下订单商店的链接。在本节中,您将介绍分页,并了解如何使用导航分页链接。

从后端开启分页

要开始使用前端超媒体控件,您需要开启一些额外的控件。Spring Data REST 提供了分页支持。要使用它,只需调整仓库定义即可

src/main/java/com/greglturnquist/payroll/EmployeeRepository.java
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

您的接口现在扩展了 PagingAndSortingRepository,它增加了设置页面大小的额外选项,并添加了用于页面跳转的导航链接。后端的其余部分是相同的(除了一些额外的预加载数据以增加趣味性)。

重启应用(./mvnw spring-boot:run)并看看它是如何工作的。

$ curl localhost:8080/api/employees?size=2
{
  "_links" : {
    "first" : {
      "href" : "http://localhost:8080/api/employees?page=0&size=2"
    },
    "self" : {
      "href" : "http://localhost:8080/api/employees"
    },
    "next" : {
      "href" : "http://localhost:8080/api/employees?page=1&size=2"
    },
    "last" : {
      "href" : "http://localhost:8080/api/employees?page=2&size=2"
    }
  },
  "_embedded" : {
    "employees" : [ {
      "firstName" : "Frodo",
      "lastName" : "Baggins",
      "description" : "ring bearer",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/api/employees/1"
        }
      }
    }, {
      "firstName" : "Bilbo",
      "lastName" : "Baggins",
      "description" : "burglar",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/api/employees/2"
        }
      }
    } ]
  },
  "page" : {
    "size" : 2,
    "totalElements" : 6,
    "totalPages" : 3,
    "number" : 0
  }
}

默认页面大小为 20,所以要查看实际效果,应用 ?size=2。正如预期,只列出了两名员工。此外,还有一个 firstnextlast 链接。还有一个 self 链接,它不包含任何上下文,包括页面参数

如果您导航到 next 链接,您还会看到一个 prev 链接

$ curl "http://localhost:8080/api/employees?page=1&size=2"
{
  "_links" : {
    "first" : {
      "href" : "http://localhost:8080/api/employees?page=0&size=2"
    },
    "prev" : {
      "href" : "http://localhost:8080/api/employees?page=0&size=2"
    },
    "self" : {
      "href" : "http://localhost:8080/api/employees"
    },
    "next" : {
      "href" : "http://localhost:8080/api/employees?page=2&size=2"
    },
    "last" : {
      "href" : "http://localhost:8080/api/employees?page=2&size=2"
    }
  },
...
注意
在 URL 查询参数中使用“&”时,命令行会认为它是换行符。用引号将整个 URL 括起来以绕过此问题。

这看起来很简洁,但当您更新前端来利用这一点时,效果会更好。

按关系导航

就这样!无需再对后端进行更改即可开始使用 Spring Data REST 开箱即用的超媒体控件。您可以切换到前端工作。(这就是 Spring Data REST 的一部分美妙之处。无需繁琐的控制器更新!)

注意
需要指出的是,这个应用并不是“Spring Data REST 特有的”。相反,它使用了 HALURI Templates 以及其他标准。这就是为什么使用 rest.js 如此轻松的原因:该库自带 HAL 支持。

在上一节中,您硬编码了 /api/employees 的路径。相反,您应该硬编码的 ONLY 路径是根路径。

...
var root = '/api';
...

借助一个方便小巧的follow() 函数,您现在可以从根目录开始并导航到所需位置!

componentDidMount: function () {
    this.loadFromServer(this.state.pageSize);
},

在上一节中,加载是直接在 componentDidMount() 内部完成的。在本节中,我们将使其能够在更新页面大小时重新加载整个员工列表。为此,我们已将相关内容移至 loadFromServer() 中。

loadFromServer: function (pageSize) {
    follow(client, root, [
        {rel: 'employees', params: {size: pageSize}}]
    ).then(employeeCollection => {
        return client({
            method: 'GET',
            path: employeeCollection.entity._links.profile.href,
            headers: {'Accept': 'application/schema+json'}
        }).then(schema => {
            this.schema = schema.entity;
            return employeeCollection;
        });
    }).done(employeeCollection => {
        this.setState({
            employees: employeeCollection.entity._embedded.employees,
            attributes: Object.keys(this.schema.properties),
            pageSize: pageSize,
            links: employeeCollection.entity._links});
    });
},

loadFromServer 与上一节非常相似,但它使用了 follow()

  • follow() 函数的第一个参数是用于进行 REST 调用的 client 对象。
  • 第二个参数是起始根 URI。
  • 第三个参数是要导航的关系数组。每个关系可以是字符串或对象。

关系数组可以像 ["employees"] 一样简单,这意味着在进行第一次调用时,在 _links 中查找名为 employees 的关系(或 rel)。找到其 href 并导航到它。如果数组中还有其他关系,重复此过程。

有时,仅凭 rel 本身是不够的。在这段代码片段中,它还传入了一个查询参数 ?size=<pageSize>。还有其他选项可以提供,正如您稍后将看到的那样。

获取 JSON Schema 元数据

使用基于 size 的查询导航到 employees 后,employeeCollection 触手可及。在上一节中,我们将其视为一天的工作结束,并在 <EmployeeList /> 中显示了数据。今天,您将进行另一次调用,以获取在 /api/profile/employees 找到的一些 JSON Schema 元数据

您可以自己查看数据

$ curl http://localhost:8080/api/profile/employees -H 'Accept:application/schema+json'
{
  "title" : "Employee",
  "properties" : {
    "firstName" : {
      "title" : "First name",
      "readOnly" : false,
      "type" : "string"
    },
    "lastName" : {
      "title" : "Last name",
      "readOnly" : false,
      "type" : "string"
    },
    "description" : {
      "title" : "Description",
      "readOnly" : false,
      "type" : "string"
    }
  },
  "definitions" : { },
  "type" : "object",
  "$schema" : "https://json-schema.fullstack.org.cn/draft-04/schema#"
}
注意
/profile/employees 的默认元数据格式是 ALPS。但在本例中,您使用了内容协商来获取 JSON Schema。

通过在 <App /> 组件的状态中捕获此信息,您可以在稍后构建输入表单时很好地利用它。

创建新记录

有了这些元数据,您现在可以向 UI 添加一些额外的控件。创建一个新的 React 组件,<CreateDialog />

var CreateDialog = React.createClass({
handleSubmit: function (e) {
    e.preventDefault();
    var newEmployee = {};
    this.props.attributes.forEach(attribute =&gt; {
        newEmployee[attribute] = React.findDOMNode(this.refs[attribute]).value.trim();
    });
    this.props.onCreate(newEmployee);

    // clear out the dialog's inputs
    this.props.attributes.forEach(attribute =&gt; {
        React.findDOMNode(this.refs[attribute]).value = '';
    });

    // Navigate away from the dialog to hide it.
    window.location = "#";
},

render: function () {
    var inputs = this.props.attributes.map(attribute…

本周 Spring 资讯 - 2015 年 9 月 8 日

工程 | Josh Long | 2015 年 9 月 8 日 | ...

欢迎来到新一期《本周 Spring 资讯》!这周我离开日本东京,一直在中国上海,与一些大型初创公司交流——包括中国非常拥挤的市场中最大的外卖服务和应用饿了么,他们每天有超过 200 万订单——关于使用 Spring Boot、Spring Cloud 和 Cloud Foundry 构建云原生应用!明天,我就要前往挪威奥斯陆,参加精彩的 JavaZone 大会。我非常喜欢这个会议,但在过去两年未能参加,因为它与 SpringOne2GX 同时举行……

Spring REST Docs 1.0.0.RC1

工程 | Andy Wilkinson | 2015 年 9 月 8 日 | ...

很高兴宣布 Spring REST Docs 1.0.0.RC1 已发布,可从我们的里程碑仓库获取。

如果您想查看 Spring REST Docs 可以生成的一些示例,请查看示例文档

新特性

入门…

Spring Data Gosling 版本有哪些新特性?

工程 | Christoph Strobl | 2015 年 9 月 4 日 | ...

在 12 个项目中修复了 300 多个问题,这使得跟踪自上次发布以来发生的事情变得相当困难。所以这里是对我们在上一次迭代中开发的一些新特性的更详细摘录。

Ad-hoc JPA fetch graphs。

自 Dijkstra 版本列车以来,我们已经能够在 JPA 支持的仓库中通过 @EntityGraph 注解引用实体上声明的命名实体图。在下面的示例中,这强制急切加载 firstname 和 lastname,而所有其他属性则保持惰性加载。

@Entity
@NamedEntityGraphs(
  @NamedEntityGraph(name…

React.js 和 Spring Data REST:第 1 部分 - 基本特性

工程 | Greg L. Turnquist | 2015 年 9 月 1 日 | ...
要查看此代码的更新,请访问我们的 React.js 和 Spring Data REST 教程

欢迎 Spring 社区的朋友们,

这是几篇博客文章中的第一篇。在本节中,您将看到如何快速搭建一个最简单的 Spring Data REST 应用并使其运行起来。然后您将使用 Facebook 的 React.js 工具集在其之上构建一个简单的 UI。

步骤 0 - 设置您的环境

您可以随意从该仓库中获取代码并跟着操作。

如果您想自己动手,请访问 http://start.spring.io 并选择以下项:

  • Rest Repositories
  • Thymeleaf
  • JPA
  • H2

本演示使用 Java 8、Maven Project 和 Spring Boot 的最新稳定版本。这将为您提供一个干净、空的项目。在此基础上,您可以添加本节中明确显示的各种文件,和/或从上面列出的仓库中借用。

起初…​

起初有数据。数据很好。但后来人们想通过各种方式访问数据。多年来,人们拼凑了许多 MVC 控制器,其中许多使用了 Spring 强大的 REST 支持。但一遍又一遍地这样做花费了大量时间。

如果满足一些假设,Spring Data REST 可以解决这个问题的简单程度:

  • 开发者使用支持仓库模型的 Spring Data 项目。
  • 系统使用广受接受的行业标准协议,如 HTTP 动词、标准化媒体类型和 IANA 批准的链接名称。

声明您的领域

任何基于 Spring Data REST 的应用的基石是领域对象。在本节中,您将构建一个应用来跟踪公司的员工。通过创建如下所示的数据类型来开始:

src/main/java/com/greglturnquist/payroll/Employee.java
@Data
@Entity
public class Employee {
private @Id @GeneratedValue Long id;
private String firstName;
private String lastName;
private String description;

private Employee() {}

public Employee(String firstName, String lastName, String description) {
	this.firstName = firstName;
	this.lastName = lastName;
	this.description = description;
}

}

  • @Entity 是一个 JPA 注解,表示将整个类存储到关系表中。
  • @Id@GeneratedValue 是 JPA 注解,表示主键并在需要时自动生成。
  • @Data@RequiredArgsConstructor 是 Project Lombok 注解,用于自动生成 getter、setter、构造函数、toString、hash、equals 以及其他内容。它减少了样板代码。

此实体用于跟踪员工信息。在本例中,是他们的姓名和职位描述。

注意
Spring Data REST 不局限于 JPA。它支持许多 NoSQL 数据存储,但我们在此不进行介绍。

定义仓库

Spring Data REST 应用的另一个关键部分是创建一个相应的仓库定义。

src/main/java/com/greglturnquist/payroll/EmployeeRepository.java
public interface EmployeeRepository extends CrudRepository<Employee, Long> {

}

  • 该仓库扩展了 Spring Data Commons 的 CrudRepository,并传入了领域对象的类型及其主键

这就是所需的全部!实际上,如果它是顶级且可见的,您甚至无需对其进行注解。如果您使用 IDE 打开 CrudRepository,您会发现其中已经定义了大量预构建的方法。

注意
如果您愿意,可以定义自己的仓库。Spring Data REST 也支持这一点。

预加载演示数据

要使用这个应用,您需要预加载一些数据,如下所示

src/main/java/com/greglturnquist/payroll/DatabaseLoader.java
@Component
public class DatabaseLoader implements CommandLineRunner {
private final EmployeeRepository repository;

@Autowired
public DatabaseLoader(EmployeeRepository repository) {
	this.repository = repository;
}

@Override
public void run(String... strings) throws Exception {
	this.repository.save(new Employee("Frodo", "Baggins…

本周 Spring 资讯 - 2015 年 9 月 1 日

工程 | Josh Long | 2015 年 9 月 1 日 | ...

欢迎来到新一期《本周 Spring 资讯》!Spring 团队正努力工作,为华盛顿特区的 SpringOne2GX 大会准备最新的精彩内容!时间过得快!我简直不敢相信我们已经进入九月了!这周我在日本东京,参加 Spring 用户组的盛大活动《夏季 Spring》,我在会上发表了主题演讲并就 Spring Boot 和 Spring Cloud 进行了两次演讲。这个为期一天的活动吸引了一些公司最大的网站,并且非常有趣!

总之,废话不多说,我们开始吧!

本周 Spring 资讯 - 2015 年 8 月 25 日

工程 | Josh Long | 2015 年 8 月 25 日 | ...

欢迎来到新一期《本周 Spring 资讯》!像往常一样,我们有很多内容要报道,所以让我们开始吧!这周我在巴西里约热内卢参加精彩的 QCon Rio 大会,然后我要前往日本东京参加为当地 Spring 用户举办的《夏季 Spring》大会!

团队正为今年的 SpringOne2GX 2016 兴奋不已,这将是有史以来规模最大、最精彩的 SpringOne2GX!今年,您将看到我们用少得多的资源实现多得多的功能,并将其投入生产,太棒了!这对我们每个人来说都令人兴奋,对观众也是如此!

将 Spring Web MVC 应用从 JSP 迁移到 AngularJS

工程 | Michael Isvy | 2015 年 8 月 19 日 | ...

作者附注

本文是客座文章,由Han LimTony Nguyen 撰写。Han 和 Tony 在新加坡 Spring 用户组上做了一个关于 Spring + Angular JS 的精彩演讲。本文基于他们的演讲。

摘要

在本文中,我们试图描述我们将视图技术从 JSP、Struts 和 Velocity 等服务器端渲染转向使用 AngularJS(一个流行的现代浏览器 JavaScript 框架)进行客户端渲染的经验。我们将讨论在进行此更改时需要注意的一些事项以及可能遇到的潜在陷阱。如果…

本周 Spring 资讯 - 2015 年 8 月 18 日

工程 | Josh Long | 2015 年 8 月 19 日 | ...

欢迎来到新一期《本周 Spring 资讯》!像往常一样,我们有很多内容要报道,所以让我们开始吧!这周我回到了巴西圣保罗拜访客户,然后就将前往里约参加 QCon Rio!如果您在这两个城市,请在 Twitter 上给我发消息,我们一起喝杯咖啡

  • 今天,Pivotal 首席执行官 Paul Maritz 卸任,并欢迎 Pivotal Labs 创始人 Rob Mee 担任新任首席执行官。这里有 PaulRob 的文章。感谢 Paul,欢迎 Rob!
  • 杰出的 Scott Frederick 刚刚宣布了 Spring Cloud Connectors 1.2.0 正式发布! - 快来看看吧!
  • Spring 大师 Stéphane Nicoll 刚刚宣布了 Spring Boot 1.3M4,其中包含大量修复和改进
  • Spring Integration 项目负责人 Gary Russell 刚刚宣布了一个内容极其丰富的新版本 Spring Integration 4.2,包括但不限于:支持安全上下文传播、STOMP 客户端通道适配器、指标、新的 Spring Framework 4.2 事件通道适配器、一个进程屏障组件、最后修改的文件列表过滤器、编解码器、JMS 共享订阅、(S)FTP 改进、SOAP Action 传播、

本周 Spring 资讯 - 2015 年 8 月 11 日

工程 | Josh Long | 2015 年 8 月 12 日 | ...

欢迎来到新一期《本周 Spring 资讯》!像往常一样,我们有很多内容要报道,所以让我们开始吧!

获取 Spring 新闻通讯

订阅 Spring 新闻通讯,保持联系

订阅

取得进步

VMware 提供培训和认证,助您加速进步。

了解更多

获取支持

Tanzu Spring 通过一个简单的订阅提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

近期活动

查看 Spring 社区的所有近期活动。

查看全部