本教程展示了一系列使用 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 | |
2 | |
此实体用于跟踪员工信息,在本例中为他们的姓名和职位描述。
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,并插入了域对象的类型及其主键 |
这就是所需要的!事实上,如果界面是顶级的和可见的,你甚至不需要注释它。如果使用 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 的注解标记,以便它被 自动拾取。 |
2 | 它实现了 Spring Boot,以便在创建和注册所有 bean 后运行。 |
3 | 它使用构造函数注入和自动连线来自动创建 Spring 数据。 |
4 | 该方法使用命令行参数调用,加载数据。 |
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 | |
2 | |
3 | 它作为模板的名称返回,Spring Boot 的自动配置视图解析器将映射到该模板。 |
定义 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。 |
加载 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-npm
npm
target
clean
- 该命令将使用提供的参数 () 执行 npm 二进制文件。这将安装 中定义的模块。
npm
install
package.json
- 该命令将执行 webpack 二进制文件,它基于 .
webpack
webpack.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": "[email protected]: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.js
app.js
public static void main()
webpack
- 创建源映射,以便在浏览器中调试 JS 代码时,可以链接回原始源代码。
- 将所有 JavaScript 位编译成 ,这是一个相当于 Spring Boot uber JAR 的 JavaScript。所有自定义代码和调用拉入的模块都填充到此文件中。
./src/main/resources/static/built/bundle.js
require()
- 它挂接到 babel 引擎,使用两者和预设,以便将 ES6 React 代码编译成能够在任何标准浏览器中运行的格式。
es2015
react
有关这些 JavaScript 工具如何操作的更多详细信息,请阅读其相应的参考文档。
想要自动查看您的 JavaScript 更改吗?运行以将 webpack 置于监视模式。它将在您编辑源时重新生成。 |
完成所有这些,您可以专注于 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 | |
2 | |
3 | |
未显示 的代码,因为用于进行 REST 调用的内容并不重要。随意检查源代码,但关键是,您可以插入 Restangular(或任何您喜欢的东西),并且这些概念仍然适用。 |
深入了解 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 | |
2 | |
3 | |
在 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 需要一个子节点的唯一标识符,并且是完美的。 |
最后,你返回一个 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.下图显示了更新的应用程序:
您可以看到系统加载的初始员工。
还记得使用 cURL 创建新条目吗?使用以下命令再次执行此操作:
curl -X POST localhost:8080/api/employees -d "{\"firstName\": \"Bilbo\", \"lastName\": \"Baggins\", \"description\": \"burglar\"}" -H "Content-Type:application/json"
刷新浏览器,您应该会看到新条目:
现在,您可以看到它们都列在网站上。
回顾
在本节中:
- 您定义了域对象和相应的存储库。
- 你让Spring Data REST使用成熟的超媒体控件导出它。
- 您在父子关系中创建了两个简单的 React 组件。
- 您获取了服务器数据,并将其呈现为简单的静态 HTML 结构。
问题?
- 网页不是动态的。您必须刷新浏览器才能获取新记录。
- 网页未使用任何超媒体控件或元数据。相反,它被硬编码以从 获取数据。
/api/employees
- 它是只读的。虽然您可以使用 cURL 更改记录,但网页不提供交互性。
我们将在下一节中讨论这些缺点。
标签:Spring,employees,REST,js,React,组件,Data From: https://blog.51cto.com/u_15326439/5976755