React.js 与 Spring Data REST:第一部分 - 基本功能

工程 | 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 项目和最新稳定的 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", "ring bearer"));
}

}

  • 此类用 Spring 的 @Component 注释标记,以便 @SpringBootApplication 可以自动拾取它。
  • 它实现了 Spring Boot 的 CommandLineRunner,因此在所有 bean 都已创建并注册后,它会被运行。
  • 它使用构造函数注入和自动装配来获取 Spring Data 自动创建的 EmployeeRepository
  • run() 方法会用命令行参数调用,加载您的数据。

Spring Data 最强大、最强大的功能之一是能够为您编写 JPA 查询。这不仅可以缩短您的开发时间,还可以降低 bug 和错误的风险。Spring Data 会查看存储库类中方法的名称,并找出您需要的操作,包括保存、删除和查找。

这就是我们如何编写一个空接口,并继承已构建的保存、查找和删除操作。

调整根 URI

默认情况下,Spring Data REST 会在 / 处托管链接的根集合。由于您将在同一路径上托管 Web UI,因此需要更改根 URI。

src/main/resources/application.properties
spring.data.rest.base-path=/api

启动后端

要启动一个完全可操作的 REST API 所需的最后一步是使用 Spring Boot 编写一个 public static void main 方法。

src/main/java/com/greglturnquist/payroll/ReactAndSpringDataRestApplication.java
@SpringBootApplication
public class ReactAndSpringDataRestApplication {
public static void main(String[] args) {
	SpringApplication.run(ReactAndSpringDataRestApplication.class, args);
}

}

假设之前的类以及您的 Maven 构建文件都是从 http://start.spring.io 生成的,您现在可以通过在 IDE 中运行该 main() 方法,或者在命令行中输入 ./mvnw spring-boot:run 来启动它。(Windows 用户请使用 mvnw.bat)。

注意
如果您对 Spring Boot 及其工作原理不太了解,建议观看 Josh Long 的 入门演示文稿之一。做到了吗?继续前进!

浏览您的 REST 服务

应用程序运行时,您可以使用 cURL(或其他任何您喜欢的工具)在命令行中进行检查。

$ curl localhost:8080/api
{
  "_links" : {
    "employees" : {
      "href" : "https://:8080/api/employees"
    },
    "profile" : {
      "href" : "https://:8080/api/profile"
    }
  }
}

当您 ping 根节点时,您会得到一个用 HAL 格式的 JSON 文档包装起来的链接集合。

  • _links 是可用的链接集合。
  • employees 指向由 EmployeeRepository 接口定义的员工对象的聚合根。
  • profile 是一个 IANA 标准关系,指向整个服务的可发现元数据。我们将在稍后的会话中对此进行探讨。

您可以通过导航 employees 链接进一步深入了解此服务。

$ curl localhost:8080/api/employees
{
  "_embedded" : {
    "employees" : [ {
      "firstName" : "Frodo",
      "lastName" : "Baggins",
      "description" : "ring bearer",
      "_links" : {
        "self" : {
          "href" : "https://:8080/api/employees/1"
        }
      }
    } ]
  }
}

此时,您正在查看所有员工的集合。

与您之前预加载的数据一起包含的是一个带有 self 链接的 _links 属性。这是该特定员工的规范链接。什么是规范?这意味着它不包含上下文。例如,可以通过像 /api/orders/1/processor 这样的链接获取同一用户,其中员工与处理特定订单相关。在这里,没有与其他实体的关系。

重要
链接是 REST 的一个关键方面。它们提供了导航到相关项的能力。它使得其他方无需每次发生更改时都重写代码即可导航您的 API。客户端的更新是常见的难题,因为客户端会硬编码资源的路径。重构资源可能会导致代码发生大动荡。如果使用链接并维护导航路线,那么进行此类调整将变得容易且灵活。

如果您愿意,您可以选择查看那一个员工。

$ curl localhost:8080/api/employees/1
{
  "firstName" : "Frodo",
  "lastName" : "Baggins",
  "description" : "ring bearer",
  "_links" : {
    "self" : {
      "href" : "https://:8080/api/employees/1"
    }
  }
}

这里变化不大,只是因为只有一个域对象,所以不需要 _embedded 包装器。

这一切都很好,但您可能迫不及待地想创建一些新条目。

$ curl -X POST localhost:8080/api/employees -d '{"firstName": "Bilbo", "lastName": "Baggins", "description": "burglar"}' -H 'Content-Type:application/json'
{
  "firstName" : "Bilbo",
  "lastName" : "Baggins",
  "description" : "burglar",
  "_links" : {
    "self" : {
      "href" : "https://:8080/api/employees/2"
    }
  }
}

您还可以执行 PUT、PATCH 和 DELETE 操作,如 此相关指南所示。但我们就不深入研究了。您已经花了太多时间手动与此 REST 服务进行交互。您不想构建一个漂亮的 UI 吗?

设置自定义 UI 控制器

Spring Boot 可以非常轻松地搭建自定义网页。首先,您需要一个 Spring MVC 控制器。

src/main/java/com/greglturnquist/payroll/HomeController.java
@Controller
public class HomeController {
@RequestMapping(value = "/")
public String index() {
	return "index";
}

}

  • @Controller 标记此类为 Spring MVC 控制器。
  • @RequestMapping 标记 index() 方法以支持 / 路由。
  • 它返回 index 作为模板名称,Spring Boot 的自动配置视图解析器将把此名称映射到 src/main/resources/templates/index.html

定义 HTML 模板

您正在使用 Thymeleaf,尽管您可能不会用到它的很多功能。

src/main/resources/templates/index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head lang="en">
    <meta charset="UTF-8"/>
    <title>ReactJS + Spring Data REST</title>
    <script async="" data-main="/run.js" src="/bower_components/requirejs/require.js"></script>
    <link rel="stylesheet" href="/main.css" />
</head>
<body>
&lt;div id="react"&gt;&lt;/div&gt;

</body> </html>

此模板的关键部分是中间的 <div id="react"></div> 组件。它将是您指示 React 插入渲染输出的地方。

加载 JavaScript 模块

本教程不会详细介绍它如何使用 require.js 加载 JavaScript 模块。但是,由于 frontend-maven-plugin,您不必安装任何 node.js 工具即可构建和运行代码。

将使用以下 JavaScript 模块

  • require.js
  • react.js
  • rest.js
  • requirejs-react-js

如果您有兴趣,JavaScript 模块的路径定义在 run.js 中。主 JavaScript 应用程序通过 main.js 由网页加载。

有了这一切,您就可以专注于 React 部分,这些部分在 DOM 加载后被获取。它被分解为以下部分

由于您使用 require.js 加载内容,请继续获取您需要的模块

src/main/resources/static/app.jsx
var React = require('react');
var client = require('./client');
  • React 是 Facebook 用于构建此应用程序的主要库。
  • client 是自定义代码,它配置 rest.js 以支持 HAL、URI 模板和其他功能。它还将默认的 Accept 请求头设置为 application/hal+json。您可以在 此处阅读代码

深入 React

React 基于定义组件。通常,一个组件可以包含另一个组件的多个实例,形成父子关系。这种概念很容易扩展到多个层。

为了开始,拥有一个所有组件的顶级容器非常有用。(当您在本系列中扩展代码时,这一点会更加明显。)目前,您只有员工列表。但将来您可能需要一些其他相关的组件,所以让我们从这个开始

src/main/resources/static/app.jsx - App 组件
var App = React.createClass({
    getInitialState: function () {
        return ({employees: []});
    },
    componentDidMount: function () {
        client({method: 'GET', path: '/api/employees'}).done(response => {
            this.setState({employees: response.entity._embedded.employees});
        });
    },
    render: function () {
        return (
            <EmployeeList employees={this.state.employees}/>
        )
    }
})
  • React.createClass({…​}) 是创建 React 组件的方法。
  • getInitialState 是初始化状态数据(预期会变化的数据)的 API。
  • componentDidMount 是 React 将组件渲染到 DOM 后调用的 API。
  • render 是在屏幕上“绘制”组件的 API。
注意
在 React 中,大写是命名组件的约定。

App 组件中,从 Spring Data REST 后端获取员工数组并存储在此组件的 state 数据中。

React 组件有两种数据:stateproperties

State 是组件预计自行处理的数据。它也是可能波动和变化的数据。要读取 state,请使用 this.state。要更新它,请使用 this.setState()。每次调用 this.setState() 时,React 都会更新 state,计算前一个 state 和新 state 之间的差异,并将一组更改注入到页面上的 DOM 中。这会带来快速高效的 UI 更新。

通用约定是在 getInitialState 中用所有属性为空来初始化 state。然后使用 componentDidMount 从服务器查找数据并填充属性。之后,更新可以由用户操作或其他事件驱动。

Properties 包含传递给组件的数据。Properties 不会改变,而是固定的值。要设置它们,您会在创建新组件时将其分配给属性,您很快就会看到。

警告
JavaScript 不像其他语言那样锁定数据结构。您可以尝试通过分配值来规避 properties,但这对于 React 的差异引擎不起作用,并且应避免。

在此代码中,该函数通过 client(一个 Promise 兼容的 rest.js 实例)加载数据。在完成从 /api/employees 的检索后,它会调用 done() 中的函数,并根据其 HAL 文档(response.entity._embedded.employees)设置 state。您可能还记得 之前 curl /api/employees 的结构,并看到它如何映射到此结构。

当 state 更新时,框架会调用 render() 函数。员工 state 数据包含在 <EmployeeList /> React 组件的创建中,作为输入参数。

以下是 EmployeeList 的定义。

src/main/resources/static/app.jsx - EmployeeList 组件
var EmployeeList = React.createClass({
    render: function () {
        var employees = this.props.employees.map(employee =>
            <Employee key={employee._links.self.href} employee={employee}/>
        );
        return (
            <table>
                <tr>
                    <th>First Name</th>
                    <th>Last Name</th>
                    <th>Description</th>
                </tr>
                {employees}
            </table>
        )
    }
})

使用 JavaScript 的 map 函数,this.props.employees 从员工记录数组转换为 <Element /> React 组件数组(您将在稍后看到)。

<Employee key={employee._links.self.href} data={employee} />

这显示了一个新的 React 组件(注意大写格式)被创建,并带有两个属性:keydata。它们的值分别来自 employee._links.self.hrefemployee

重要
每当您使用 Spring Data REST 时,self 链接就是给定资源的 key。React 需要子节点的唯一标识符,而 _links.self.href 非常合适。

最后,您返回一个 HTML 表格,其中包含通过 mapping 构建的 employees 数组。

<table>
    <tr>
        <th>First Name</th>
        <th>Last Name</th>
        <th>Description</th>
    </tr>
    {employees}
</table>

这种 state、properties 和 HTML 的简单布局展示了 React 如何让您声明式地创建简单易懂的组件。

这段代码同时包含 HTML *和* JavaScript 吗?是的。如果您注意到文件扩展名,这就是 JSX。没有要求使用它。React 可以使用纯 JavaScript 编写,但 JSX 语法非常简洁。

JSX 还包含 ES6 的一些内容。代码中使用的那个是 箭头函数。它避免了创建具有自己的作用域 this 的嵌套 function(),并避免了需要 self 变量

担心将逻辑与结构混合?React 的 API 鼓励使用整洁、声明式的结构,结合 state 和 properties。React 鼓励构建带有少量相关 state 和 properties 的简单组件,而不是混合大量不相关的 JavaScript 和 HTML。它允许您查看单个组件并理解其设计。然后,它们可以轻松地组合在一起构成更大的结构。

接下来,您需要实际定义一个 <Employee /> 是什么。

src/main/resources/static/app.jsx - Employee 组件
var Employee = React.createClass({
    render: function () {
        return (
            <tr>
                <td>{this.props.employee.firstName}</td>
                <td>{this.props.employee.lastName}</td>
                <td>{this.props.employee.description}</td>
            </tr>
        )
    }
})

这个组件非常简单。它有一个 HTML 表格行,包含员工的三个属性。属性本身是 this.props.employee。注意传递 JavaScript 对象如何轻松地传递从服务器获取的数据?

因为这个组件不管理任何 state,也不处理用户输入,所以没有其他事情需要做。这可能会诱使您将其塞入上面的 <EmployeeList /> 中。不要这样做!相反,将您的应用程序分成做一件事的小组件将使将来构建功能更容易。

最后一步是渲染整个内容。

src/main/resources/static/app.jsx - 渲染代码
React.render(
    <App />,
    document.getElementById('react')
)

React.render() 接受两个参数:您定义的 React 组件以及要注入它的 DOM 节点。还记得之前在 HTML 页面中看到的 <div id="react"></div> 元素吗?这就是它被拾取并插入的地方。

有了这一切,请重新运行应用程序(./mvnw spring-boot:run)并访问 https://:8080

basic 1

您可以看到系统加载的初始员工。

还记得使用 cURL 创建新条目吗?再做一次。

curl -X POST localhost:8080/api/employees -d '{"firstName": "Bilbo", "lastName": "Baggins", "description": "burglar"}' -H 'Content-Type:application/json'

刷新浏览器,您应该会看到新条目。

basic 2

现在您可以在网站上看到两者都列出了。

回顾

在本节中

  • 您定义了一个域对象和相应的存储库。
  • 您让 Spring Data REST 导出了具有完整超媒体控件的功能。
  • 您创建了两个简单的 React 组件,它们之间存在父子关系。
  • 您获取了服务器数据并将其渲染为简单的静态 HTML 结构。

问题?

  • 网页不是动态的。您必须刷新浏览器才能获取新记录。
  • 网页没有使用任何超媒体控件或元数据。相反,它被硬编码为从 /api/employees 获取数据。
  • 它是只读的。虽然您可以使用 cURL 修改记录,但网页不提供任何此类功能。

这些是我们可以在下一节中解决的问题。在此之前,祝您编码愉快!

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

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

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看所有

版权所有 © 2005 -2025Broadcom。保留所有权利。“Broadcom”一词指 Broadcom Inc. 和/或其子公司。
使用条款 隐私 商标指南

Apache®、Apache Tomcat®、Apache Kafka®、Apache Cassandra™ 和 Apache Geode™ 是 Apache Software Foundation 在美国和/或其他国家的商标或注册商标。Java™、Java™ SE、Java™ EE 和 OpenJDK™ 是 Oracle 和/或其关联公司的商标。Kubernetes® 是 Linux Foundation 在美国和其他国家的注册商标。Linux® 是 Linus Torvalds 在美国和其他国家的注册商标。Windows® 和 Microsoft® Azure 是 Microsoft Corporation 的注册商标。“AWS”和“Amazon Web Services”是 Amazon.com Inc. 或其关联公司的商标或注册商标。所有其他商标和版权均为其各自所有者的财产,仅为提供信息之目的提及。其他名称可能是其各自所有者的商标。