领先一步
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" : "http://localhost:8080/api/employees" }, "profile" : { "href" : "http://localhost: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" : "http://localhost: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" : "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 操作,如本相关指南所示。但我们不必深入探讨这些。您已经花了太多时间手动与此 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 是组件本身应处理的数据。它也是可能波动和变化的数据。要读取状态,您使用 this.state
。要更新它,您使用 this.setState()
。每次调用 this.setState()
时,React 都会更新状态,计算先前状态和新状态之间的差异,并将一组更改注入到页面上的 DOM 中。这会使您的 UI 更新快速高效。
常见的约定是在 getInitialState
中初始化所有属性为空的状态。然后您使用 componentDidMount
从服务器查找数据并填充属性。从那时起,更新可以由用户操作或其他事件驱动。
Properties 包含传递到组件中的数据。Properties 不会改变,而是固定值。要设置它们,您在创建新组件时将它们赋值给属性,您很快就会看到。
警告
|
与其他语言不同,JavaScript 不会锁定数据结构。您可以尝试通过赋值来绕过 properties,但这不适用于 React 的差分引擎,应避免这样做。 |
在这段代码中,函数通过 client
(一个Promise 规范的 rest.js 实例)加载数据。从 /api/employees
获取数据完成后,它会调用 done()
中的函数,并根据其 HAL 文档(response.entity._embedded.employees
)设置状态。您可能还记得之前 curl /api/employees
的结构,并看到它如何映射到这个结构。
状态更新后,框架会调用 render()
函数。员工状态数据作为输入参数包含在 <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 链接就是给定资源的键。React 需要子节点的唯一标识符,而 _links.self.href 是完美的。 |
最后,您返回一个包裹着通过映射构建的 employees
数组的 HTML 表格。
<table>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Description</th>
</tr>
{employees}
</table>
这种简单的状态、属性和 HTML 布局展示了 React 如何让您声明式地创建一个简单易懂的组件。
这段代码既包含 HTML 又包含 JavaScript 吗?是的。如果您注意到了文件扩展名,这是 JSX。没有强制要求使用它。React 可以用纯 JavaScript 编写,但 JSX 语法非常简洁。
担心将逻辑与结构混合?React 的 API 鼓励使用状态和属性构建漂亮的、声明式的结构。React 鼓励构建简单的组件,这些组件包含少量相关的状态和属性,并且能够协同工作,而不是将一堆不相关的 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 对象后,传递从服务器获取的数据是多么容易?
因为这个组件既不管理任何状态也不处理用户输入,所以没有其他要做的事情。这可能会诱使您将其塞入上面的 <EmployeeList />
中。不要这样做!相反,将您的应用程序分解成每个只完成一项工作的更小的组件,这将使您将来更容易构建功能。
最后一步是渲染整个内容。
React.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'
刷新浏览器,您应该看到新的条目
现在您可以看到他们两个都列在网站上了。
在本节中
问题?
/api/employees
获取数据。这些问题我们将在下一节中解决。在此之前,编码愉快!