React.js 和 Spring Data REST_REST

本教程展示了一系列使用 Spring Data REST 及其强大的后端功能的应用程序,结合 React 的复杂功能来构建易于理解的 UI。

  • 弹簧数据休息提供了一种构建超媒体驱动的存储库的快速方法。
  • 反应是 Facebook 在 JavaScript 中高效、快速和易于使用的视图的解决方案。

第 1 部分 — 基本功能

欢迎,春天社区。

本节介绍如何快速启动并运行基本的 Spring Data REST 应用程序。然后,它展示了如何使用Facebook的React.js工具集在其上构建一个简单的UI。

步骤 0 — 设置环境

随意获取代码从此存储库并继续操作。

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

  • 其余存储库
  • 百里香叶
  • 太平绅士
  • H2

此演示使用 Java 8、Maven Project 和最新的 Spring Boot 稳定版本。它还使用 React.js编码​​ES6​​.这将为您提供一个干净的空项目。从那里,您可以添加本节中明确显示的各种文件和/或从前面列出的存储库中借用。

一开始...

一开始,有数据。而且很好。但后来人们希望通过各种方式访问数据。多年来,人们拼凑了许多MVC控制器,其中许多使用Spring强大的REST支持。但是一遍又一遍地做会花费很多时间。

Spring Data REST解决了如果做出一些假设,这个问题是多么简单:

  • 开发人员使用支持存储库模型的 Spring 数据项目。
  • 该系统使用广为接受的行业标准协议,例如 HTTP 动词、标准化媒体类型和IANA 批准的链接名称.

声明您的域

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

例 1.src/main/java/com/greglturnquist/payroll/Employee.java

@Entity (1)
public class Employee {

private @Id @GeneratedValue Long id; (2)
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;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return Objects.equals(id, employee.id) &&
Objects.equals(firstName, employee.firstName) &&
Objects.equals(lastName, employee.lastName) &&
Objects.equals(description, employee.description);
}

@Override
public int hashCode() {

return Objects.hash(id, firstName, lastName, description);
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

@Override
public String toString() {
return "Employee{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", description='" + description + '\'' +
'}';
}
}

1

​@Entity​​是一个 JPA 注释,表示要在关系表中存储的整个类。

2

​@Id​​​并且是 JPA 注释,用于记下主键,并在需要时自动生成。​​@GeneratedValue​

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

Spring Data REST不仅限于JPA。它支持许多 NoSQL 数据存储,尽管您不会在本教程中看到这些数据存储。有关详细信息,请参阅使用 REST 访问 Neo4j 数据,使用 REST 访问 JPA 数据和使用 REST 访问 MongoDB 数据.

定义存储库

Spring Data REST 应用程序的另一个关键部分是相应的存储库定义,如下所示:

例 2.src/main/java/com/greglturnquist/payroll/EmployeeRepository.java

public interface EmployeeRepository extends CrudRepository<Employee, Long> { (1)

}

1

存储库扩展了 Spring Data Commons,并插入了域对象的类型及其主键​​CrudRepository​

这就是所需要的!事实上,如果界面是顶级的和可见的,你甚至不需要注释它。如果使用 IDE 并打开 ,则会找到预定义方法的集合。​​CrudRepository​

您可以定义您自己的存储库如果你愿意。Spring Data REST也支持这一点。

预加载演示

要使用此应用程序,您需要用一些数据预加载它,如下所示:

例 3.src/main/java/com/greglturnquist/payroll/DatabaseLoader.java

@Component (1)
public class DatabaseLoader implements CommandLineRunner { (2)

private final EmployeeRepository repository;

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

@Override
public void run(String... strings) throws Exception { (4)
this.repository.save(new Employee("Frodo", "Baggins", "ring bearer"));
}
}

1

这个类用 Spring 的注解标记,以便它被 自动拾取。​​@Component​​​​@SpringBootApplication​

2

它实现了 Spring Boot,以便在创建和注册所有 bean 后运行。​​CommandLineRunner​

3

它使用构造函数注入和自动连线来自动创建 Spring 数据。​​EmployeeRepository​

4

该方法使用命令行参数调用,加载数据。​​run()​

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

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

调整根 URI

默认情况下,Spring Data REST 在 .由于你将在该路径上托管 Web UI,因此需要更改根 URI,如下所示:​​/​

例 4.src/main/resources/application.properties

spring.data.rest.base-path=/api

启动后端

启动完全可操作的 REST API 所需的最后一步是使用 Spring Boot 编写方法,如下所示:​​public static void main​

例 5.src/main/java/com/greglturnquist/payroll/ReactAndSpringDataRestApplication.java

@SpringBootApplication
public class ReactAndSpringDataRestApplication {

public static void main(String[] args) {
SpringApplication.run(ReactAndSpringDataRestApplication.class, args);
}
}

假设以前的类以及您的 Maven 构建文件是从https://start.spring.io,现在可以通过在 IDE 中运行该方法或在命令行上键入来启动它。(适用于视窗用户)。​​main()​​​​./mvnw spring-boot:run​​​​mvnw.bat​

如果您不了解 Spring Boot 及其工作原理,您应该观看以下之一乔希·朗的介绍性演讲.是吗?加油!

参观您的 REST 服务

在应用程序运行时,您可以使用卷曲(或您喜欢的任何其他工具)。以下命令(与其输出一起显示)列出了应用程序中的链接:

$ curl localhost:8080/api
{
"_links" : {
"employees" : {
"href" : "http://localhost:8080/api/employees"
},
"profile" : {
"href" : "http://localhost: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" : "http://localhost:8080/api/employees/1"
}
}
} ]
}
}

在此阶段,您正在查看整个员工集合。

除了您之前预加载的数据外,还包括一个带有链接的属性。这是该特定员工的规范链接。什么是规范?它的意思是“脱离上下文”。例如,可以通过 获取同一用户,其中员工与处理特定订单相关联。在这里,与其他实体没有关系。​​_links​​​​self​​​​/api/orders/1/processor​

链接是 REST 的一个关键方面。它们提供导航到相关项的功能。它使其他方可以在您的 API 中导航,而无需在每次更改时重写内容。当客户端对资源的路径进行硬编码时,客户端中的更新是一个常见问题。重组资源可能会导致代码发生重动。如果使用链接并保持导航路线,则进行此类调整变得容易而灵活。

如果您愿意,您可以决定查看该员工。以下命令(与其输出一起显示)执行此操作:

$ curl localhost:8080/api/employees/1
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"description" : "ring bearer",
"_links" : {
"self" : {
"href" : "http://localhost: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" : "http://localhost:8080/api/employees/2"
}
}
}

您还可以 、 和 ,如 中所示​​PUT​​​​PATCH​​​​DELETE​​此相关指南.不过,现在,我们将继续构建一个流畅的 UI。

设置自定义 UI 控制器

Spring Boot 使建立自定义网页变得非常简单。首先,你需要一个Spring MVC控制器,如下所示:

例 6.src/main/java/com/greglturnquist/payroll/HomeController.java

@Controller (1)
public class HomeController {

@RequestMapping(value = "/") (2)
public String index() {
return "index"; (3)
}

}

1

​@Controller​​将此类标记为 Spring MVC 控制器。

2

​@RequestMapping​​​标记支持路由的方法。​​index()​​​​/​

3

它作为模板的名称返回,Spring Boot 的自动配置视图解析器将映射到该模板。​​index​​​​src/main/resources/templates/index.html​

定义 HTML 模板

您正在使用百里香叶,尽管您不会真正使用它的许多功能。要开始使用,您需要一个索引页,如下所示:

例 7.src/main/resources/templates/index.html

<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head lang="en">
<meta charset="UTF-8"/>
<title>ReactJS + Spring Data REST</title>
<link rel="stylesheet" href="/main.css" />
</head>
<body>

<div id="react"></div>

<script src="built/bundle.js"></script>

</body>
</html>

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

您可能还想知道该文件来自哪里。下一节将介绍其构建方式。​​bundle.js​

本教程不显示 ,但您可以在上面看到它的链接。当涉及到CSS时,Spring Boot将自动提供在中找到的任何内容。把你自己的文件放在那里。本教程中没有显示它,因为我们的重点是 React 和 Spring Data REST,而不是 CSS。​​main.css​​​​src/main/resources/static​​​​main.css​

加载 JavaScript 模块

本节包含准系统信息,以便将 JavaScript 位从地面上移开。虽然你可以安装所有的JavaScripts命令行工具,但你不需要这样做——至少现在还不需要。相反,您需要做的就是将以下内容添加到构建文件中:​​pom.xml​

例 8.用于构建 JavaScript 位​​frontend-maven-plugin​

<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
</plugin>

这个小插件执行多个步骤:

  • 该命令会将 node.js 及其包管理工具 安装到文件夹中。(这可确保二进制文件不会在源代码管理下拉取,并且可以使用 清除)。install-node-and-npmnpmtargetclean
  • 该命令将使用提供的参数 () 执行 npm 二进制文件。这将安装 中定义的模块。npminstallpackage.json
  • 该命令将执行 webpack 二进制文件,它基于 .webpackwebpack.config.js

这些步骤是按顺序运行的,本质上是安装 node.js、下载 JavaScript 模块和构建 JS 位。

安装了哪些模块?JavaScript 开发人员通常用于构建文件,如下所示:​​npm​​​​package.json​

例 9.包.json

{
"name": "spring-data-rest-and-reactjs",
"version": "0.1.0",
"description": "Demo of ReactJS + Spring Data REST",
"repository": {
"type": "git",
"url": "git@github.com:spring-guides/tut-react-and-spring-data-rest.git"
},
"keywords": [
"rest",
"hateoas",
"spring",
"data",
"react"
],
"author": "Greg L. Turnquist",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/spring-guides/tut-react-and-spring-data-rest/issues"
},
"homepage": "https://github.com/spring-guides/tut-react-and-spring-data-rest",
"dependencies": {
"react": "^16.5.2",
"react-dom": "^16.5.2",
"rest": "^1.3.1"
},
"scripts": {
"watch": "webpack --watch -d --output ./target/classes/static/built/bundle.js"
},
"devDependencies": {
"@babel/core": "^7.1.0",
"@babel/preset-env": "^7.1.0",
"@babel/preset-react": "^7.0.0",
"babel-loader": "^8.0.2",
"webpack": "^4.19.1",
"webpack-cli": "^3.1.0"
}
}

主要依赖关系包括:

  • 反应.js:本教程使用的工具包
  • rest.js:用于进行REST调用的CujoJS工具包
  • webpack:用于将 JavaScript 组件编译成单个可加载捆绑包的工具包
  • babel:使用 ES6 编写 JavaScript 代码并将其编译为 ES5 以在浏览器中运行

要构建稍后将使用的 JavaScript 代码,您需要为网络包如下:

例 10.webpack.config.js

var path = require('path');

module.exports = {
entry: './src/main/js/app.js',
devtool: 'sourcemaps',
cache: true,
mode: 'development',
output: {
path: __dirname,
filename: './src/main/resources/static/built/bundle.js'
},
module: {
rules: [
{
test: path.join(__dirname, '.'),
exclude: /(node_modules)/,
use: [{
loader: 'babel-loader',
options: {
presets: ["@babel/preset-env", "@babel/preset-react"]
}
}]
}
]
}
};

此 webpack 配置文件:

  • 入口点定义为 。从本质上讲,(您稍后将编写的模块)是我们JavaScript应用程序的谚语。 必须知道这一点,以便知道浏览器加载最终捆绑包时要启动什么./src/main/js/app.jsapp.jspublic static void main()webpack
  • 创建源映射,以便在浏览器中调试 JS 代码时,可以链接回原始源代码。
  • 将所有 JavaScript 位编译成 ,这是一个相当于 Spring Boot uber JAR 的 JavaScript。所有自定义代码和调用拉入的模块都填充到此文件中。./src/main/resources/static/built/bundle.jsrequire()
  • 它挂接到 babel 引擎,使用两者和预设,以便将 ES6 React 代码编译成能够在任何标准浏览器中运行的格式。es2015react

有关这些 JavaScript 工具如何操作的更多详细信息,请阅读其相应的参考文档。

想要自动查看您的 JavaScript 更改吗?运行以将 webpack 置于监视模式。它将在您编辑源时重新生成。​​npm run-script watch​​​​bundle.js​

完成所有这些,您可以专注于 React 位,这些位是在加载 DOM 后获取的。它分为以下几部分:

由于您正在使用 webpack 来组装东西,请继续获取您需要的模块:

例 11.src/main/js/app.js

const React = require('react'); (1)
const ReactDOM = require('react-dom'); (2)
const client = require('./client'); (3)

1

​React​​是Facebook用于构建此应用程序的主要库之一。

2

​ReactDOM​​是作为 REACT 的 DOM 和服务器渲染器的入口点的包。它旨在与通用 React 包配对。

3

​client​​​是配置 rest.js 的自定义代码,以包括对 HAL、URI 模板和其他内容的支持。它还将默认请求标头设置为 。您可以​​Accept​​​​application/hal+json​​在此处阅读代码.

未显示 的代码,因为用于进行 REST 调用的内容并不重要。随意检查源代码,但关键是,您可以插入 Restangular(或任何您喜欢的东西),并且这些概念仍然适用。​​client​

深入了解 React

React 基于定义组件。通常,一个组件可以在父子关系中保存另一个组件的多个实例。这个概念可以扩展到几个层。

首先,为所有组件提供一个顶级容器非常方便。(随着您在本系列中对代码的扩展,这一点将变得更加明显。现在,您只有员工列表。但是,稍后可能需要一些其他相关组件,因此请从以下内容开始:

例 12.src/main/js/app.js - App component

class App extends React.Component { (1)

constructor(props) {
super(props);
this.state = {employees: []};
}

componentDidMount() { (2)
client({method: 'GET', path: '/api/employees'}).done(response => {
this.setState({employees: response.entity._embedded.employees});
});
}

render() { (3)
return (
<EmployeeList employees={this.state.employees}/>
)
}
}

1

​class App extends React.Component{…}​​是创建 React 组件的方法。

2

​componentDidMount​​是 React 在 DOM 中渲染组件后调用的 API。

3

​render​​是在屏幕上“绘制”组件的 API。

在 React 中,大写是命名组件的约定。

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

React 组件有两种类型的数据:状态属性

状态是组件应自行处理的数据。数据也会波动和变化。要读取状态,请使用 .要更新它,请使用 .每次调用时,React 都会更新状态,计算先前状态和新状态之间的差异,并向页面上的 DOM 注入一组更改。这样可以快速高效地更新 UI。​​this.state​​​​this.setState()​​​​this.setState()​

常见的约定是在构造函数中的所有属性都为空的情况下初始化状态。然后,通过使用和填充属性从服务器查找数据。从那里开始,更新可以由用户操作或其他事件驱动。​​componentDidMount​

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

JavaScript 不像其他语言那样锁定数据结构。你可以尝试通过赋值来颠覆属性,但这不适用于 React 的差分引擎,应该避免。

在此代码中,该函数通过 、​​client​​符合承诺要求休息实例.js。当它完成从 检索完后,它会调用里面的函数,并根据其 HAL 文档 () 设置状态。查看结构​​/api/employees​​​​done()​​​​response.entity._embedded.employees​​​​curl /api/employees​​ 早些时候看看它是如何映射到这个结构上的。

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

下面的清单显示了 的定义:​​EmployeeList​

例 13.src/main/js/app.js - EmployeeList 组件

class EmployeeList extends React.Component{
render() {
const employees = this.props.employees.map(employee =>
<Employee key={employee._links.self.href} employee={employee}/>
);
return (
<table>
<tbody>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Description</th>
</tr>
{employees}
</tbody>
</table>
)
}
}

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

请考虑以下列表:

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

前面的清单创建了一个新的 React 组件(请注意大写格式),该组件具有两个属性:key 和 data。它们提供来自 和 的值。​​employee._links.self.href​​​​employee​

每当你使用Spring Data REST时,链接都是给定资源的键。React 需要一个子节点的唯一标识符,并且是完美的。​​self​​​​_links.self.href​

最后,你返回一个 HTML 表,它围绕着 build with mapping 的数组,如下所示:​​employees​

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

这种状态、属性和 HTML 的简单布局展示了 React 如何让你以声明方式创建一个简单易懂的组件。

这段代码是否同时包含 HTML 和 JavaScript?是的,这是JSX.不需要使用它。React 可以使用纯 JavaScript 编写,但 JSX 语法非常简洁。由于 Babel.js 的快速工作,转译器同时提供了 JSX 和 ES6 支持。

JSX 还包括零碎的ES6​.此代码中使用的是箭头功能.它避免创建具有自己的作用域的嵌套,并避免需要​​function()​​​​this​​self变量.

担心将逻辑与您的结构混合?React 的 API 鼓励与状态和属性相结合的漂亮的声明式结构。与其混合一堆不相关的 JavaScript 和 HTML,React 鼓励构建具有少量相关状态和属性的简单组件,这些组件可以很好地协同工作。它使您可以查看单个组件并了解设计。然后它们很容易组合在一起以获得更大的结构。

接下来,您需要实际定义 a 是什么,如下所示:​​<Employee />​

例 14。src/main/js/app.js - 员工组件

class Employee extends React.Component{
render() {
return (
<tr>
<td>{this.props.employee.firstName}</td>
<td>{this.props.employee.lastName}</td>
<td>{this.props.employee.description}</td>
</tr>
)
}
}

这个组件非常简单。它有一个 HTML 表行环绕着员工的三个属性。属性本身是 。请注意,传入 JavaScript 对象如何使传递从服务器获取的数据变得容易。​​this.props.employee​

由于此组件不管理任何状态,也不处理用户输入,因此无需执行任何其他操作。这可能会诱使您将其塞入上面的内容。不要这样做!将应用拆分为小组件,每个组件执行一项工作,这将使将来更容易构建功能。​​<EmployeeList />​

最后一步是渲染整个事情,如下所示:

例 15。src/main/js/app.js - 渲染代码

ReactDOM.render(
<App />,
document.getElementById('react')
)

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

完成所有这些操作后,重新运行应用程序 () 并访问​​./mvnw spring-boot:run​​​​http://localhost:8080​​.下图显示了更新的应用程序:

React.js 和 Spring Data REST_Data_02

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

还记得使用 cURL 创建新条目吗?使用以下命令再次执行此操作:

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

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

React.js 和 Spring Data REST_Data_03

现在,您可以看到它们都列在网站上。

回顾

在本节中:

  • 您定义了域对象和相应的存储库。
  • 你让Spring Data REST使用成熟的超媒体控件导出它。
  • 您在父子关系中创建了两个简单的 React 组件。
  • 您获取了服务器数据,并将其呈现为简单的静态 HTML 结构。

问题?

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

我们将在下一节中讨论这些缺点。