领先一步
VMware 提供培训和认证,助您加速进步。
了解更多| 要查看此代码的更新,请访问我们的 React.js 与 Spring Data REST 教程。 |
Spring 社区的各位,欢迎!
这是几篇博客文章的第一篇。在本期中,您将看到如何快速启动并运行一个最基本的 Spring Data REST 应用程序。然后,您将使用 Facebook 的 React.js 工具集在其之上构建一个简单的 UI。
您可以随时从该存储库 获取代码 并进行学习。
如果您想自己动手,请访问 http://start.spring.io 并选择以下选项
此演示使用 Java 8、Maven 项目和最新稳定的 Spring Boot 版本。这将为您提供一个干净的空白项目。之后,您可以添加本期中明确显示到的各种文件,和/或从上面列出的存储库中借用。
起初,有数据。一切都很好。但随后人们希望通过各种方式访问数据。多年来,人们拼凑了许多 MVC 控制器,其中许多使用了 Spring 强大的 REST 支持。但一遍又一遍地重复会耗费大量时间。
Spring Data REST 解决了在做了一些假设的情况下,这个问题可以变得多么简单
任何基于 Spring Data REST 的应用程序的基石都是域对象。在本期中,您将构建一个应用程序来跟踪公司的员工。通过创建一个如下的数据类型来启动这个过程
@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 应用程序的另一个关键部分是创建相应的存储库定义。
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
}
CrudRepository,并注入了域对象的类型及其主键。仅此而已!事实上,如果它是顶级的且可见的,您甚至不必注解它。如果您使用 IDE 并打开 CrudRepository,您会发现其中已经定义了大量预构建的方法。
|
注意
|
您可以根据需要 定义自己的存储库。Spring Data REST 也支持这一点。 |
要使用此应用程序,您需要预先加载一些数据,如下所示
@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")); }
}
@Component 注释标记,以便 @SpringBootApplication 可以自动拾取它。CommandLineRunner,因此在所有 bean 都已创建并注册后,它会被运行。EmployeeRepository。run() 方法会用命令行参数调用,加载您的数据。Spring Data 最强大、最强大的功能之一是能够为您编写 JPA 查询。这不仅可以缩短您的开发时间,还可以降低 bug 和错误的风险。Spring Data 会查看存储库类中方法的名称,并找出您需要的操作,包括保存、删除和查找。
这就是我们如何编写一个空接口,并继承已构建的保存、查找和删除操作。
默认情况下,Spring Data REST 会在 / 处托管链接的根集合。由于您将在同一路径上托管 Web UI,因此需要更改根 URI。
spring.data.rest.base-path=/api
要启动一个完全可操作的 REST API 所需的最后一步是使用 Spring Boot 编写一个 public static void main 方法。
@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 的 入门演示文稿之一。做到了吗?继续前进! |
应用程序运行时,您可以使用 cURL(或其他任何您喜欢的工具)在命令行中进行检查。
$ curl localhost:8080/api
{
"_links" : {
"employees" : {
"href" : "https://:8080/api/employees"
},
"profile" : {
"href" : "https://:8080/api/profile"
}
}
}
当您 ping 根节点时,您会得到一个用 HAL 格式的 JSON 文档包装起来的链接集合。
EmployeeRepository 接口定义的员工对象的聚合根。您可以通过导航 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 吗?
Spring Boot 可以非常轻松地搭建自定义网页。首先,您需要一个 Spring MVC 控制器。
@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。您正在使用 Thymeleaf,尽管您可能不会用到它的很多功能。
<!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><div id="react"></div>
</body> </html>
此模板的关键部分是中间的 <div id="react"></div> 组件。它将是您指示 React 插入渲染输出的地方。
本教程不会详细介绍它如何使用 require.js 加载 JavaScript 模块。但是,由于 frontend-maven-plugin,您不必安装任何 node.js 工具即可构建和运行代码。
将使用以下 JavaScript 模块
有了这一切,您就可以专注于 React 部分,这些部分在 DOM 加载后被获取。它被分解为以下部分
由于您使用 require.js 加载内容,请继续获取您需要的模块
var React = require('react');
var client = require('./client');
React 是 Facebook 用于构建此应用程序的主要库。client 是自定义代码,它配置 rest.js 以支持 HAL、URI 模板和其他功能。它还将默认的 Accept 请求头设置为 application/hal+json。您可以在 此处阅读代码。React 基于定义组件。通常,一个组件可以包含另一个组件的多个实例,形成父子关系。这种概念很容易扩展到多个层。
为了开始,拥有一个所有组件的顶级容器非常有用。(当您在本系列中扩展代码时,这一点会更加明显。)目前,您只有员工列表。但将来您可能需要一些其他相关的组件,所以让我们从这个开始
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 组件有两种数据:state 和 properties。
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 的定义。
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 组件(注意大写格式)被创建,并带有两个属性:key 和 data。它们的值分别来自 employee._links.self.href 和 employee。
|
重要
|
每当您使用 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 语法非常简洁。
担心将逻辑与结构混合?React 的 API 鼓励使用整洁、声明式的结构,结合 state 和 properties。React 鼓励构建带有少量相关 state 和 properties 的简单组件,而不是混合大量不相关的 JavaScript 和 HTML。它允许您查看单个组件并理解其设计。然后,它们可以轻松地组合在一起构成更大的结构。
接下来,您需要实际定义一个 <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 /> 中。不要这样做!相反,将您的应用程序分成做一件事的小组件将使将来构建功能更容易。
最后一步是渲染整个内容。
React.render(
<App />,
document.getElementById('react')
)
React.render() 接受两个参数:您定义的 React 组件以及要注入它的 DOM 节点。还记得之前在 HTML 页面中看到的 <div id="react"></div> 元素吗?这就是它被拾取并插入的地方。
有了这一切,请重新运行应用程序(./mvnw spring-boot:run)并访问 https://:8080。
您可以看到系统加载的初始员工。
还记得使用 cURL 创建新条目吗?再做一次。
curl -X POST localhost:8080/api/employees -d '{"firstName": "Bilbo", "lastName": "Baggins", "description": "burglar"}' -H 'Content-Type:application/json'
刷新浏览器,您应该会看到新条目。
现在您可以在网站上看到两者都列出了。
在本节中
问题?
/api/employees 获取数据。这些是我们可以在下一节中解决的问题。在此之前,祝您编码愉快!