React.js 与 Spring Data REST(一)(官方文档翻译)

原文链接

本教程展示了一组使用Spring Data REST的应用程序,以及它强大的后端功能,结合了React的复杂特性,构建了一个易于grok(理解?)的UI。
Spring Data REST提供了一种快速的方法来构建基于多媒体的存储库。
React 是Facebook在JavaScript的领域里用来高效、快速和易于使用视图的解决方案。

第1部分-基本特征

欢迎Spring社区,在本节中,您将看到如何快速地获得一个简单的Spring Data REST应用程序。然后,您将使用Facebook的React构建一个简单的UI。js工具集。

步骤0——设置您的环境

您可以从这个存储库中获取代码并继续执行。

如果您想自己动手,请访问http://start.spring。输入这些项目:Rest Repositories、Thymeleaf、JPA、H2、Lombok(可能需要确保您的IDE也支持此功能)。

这个Demo使用了Java 8、Maven项目和SpringBoot的最新稳定版本。它还使用React.js的ES6编码。它将为您提供一个干净、空的项目。在这里,您可以添加本节中显式显示的各种文件,或从上面列出的存储库中借用。


一开始……

一开始就有数据。很好。但是接下来人们想要通过各种方式访问数据。多年来,人们拼凑了大量的MVC控制器,许多人使用Spring强大的REST支持。但是这样做一遍又一遍的花费了很多时间。


如果有一些假设的话,SpringDataREST使这个问题变得简单:

1、开发人员使用一个支持存储库模型的Spring Data项目。

2、该系统使用公认的行业标准协议,如HTTP枚举、标准化媒体类型和iana-provides的链接名称。


声明你的domain(域名?)

任何基于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是一个项目Lombok注释,用于自动生成getter、setter、构造函数、toString、hash、equals和其他东西。它减少了样板文件。

这个实体用于跟踪员工信息。在这个例子中包含员工的名字和职位描述。

SpringDataREST并不局限于JPA。它支持许多NoSQL数据存储,但是您不会在这里涉及这些数据存储。


定义存储库

Spring Data REST应用程序的另一个关键部分是创建相应的资源库定义。

public interface EmployeeRepository extends CrudRepository<Employee, Long> {

}

存储库继承了Spring Data Commons的CrudRepository,并插入了域对象及其主键的类型。

这就是我们所需要的!事实上,如果它是顶级的和可见的,你甚至不需要注释它。如果您使用IDE并打开CrudRepository,您将会发现一个已经定义好的全的预构建方法集。

如果您愿意,您可以定义自己的存储库。SpringDataREST也支持这一点。


预加载这个demo

要使用这个应用程序,您需要使用如下的数据预加载它:

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被创建和注册之后运行。

它使用构造器注入和自动装配来获得SpringData自动创建的EmployeeRepository。

run()方法是用命令行参数调用的,加载您的数据。


Spring Data最大的、最强大的特性之一是它能够为您编写JPA查询。这不仅减少了您的开发时间,而且还降低了bug和error的风险。SpringData查看存储库类中的方法名,并找出您需要的操作,包括保存、删除和查找。

这就是我们如何编写一个空接口,并继承已经构建的save、find和delete的操作。


调整根URI

默认情况下,Spring Data REST用 / 提供链接的根集合因为您将在相同的路径上提供一个web UI,所以您需要更改根URI。

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

启动后端

要获得完全运行的REST API所需的最后一步是使用SpringBoot来编写一个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引导来启动它。(mvnw.bat为Windows用户提供使用)。

如果你不了解SpringBoot的最新情况,以及它是如何工作的,你应该考虑一下Josh Long的介绍性演讲。(需翻墙)

Touring(参观?)你的REST服务

随着应用程序的运行,您可以使用cURL(或者您喜欢的任何其他工具)在命令行上检查事情。

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

当你ping根节点时,你会得到一个包含在HAL-formated JSON document中的链接的集合。

_link是一个可用的链接集合。

employees指向由EmployeeRepository接口定义的员工对象的聚合根。

profile是一个IANA-standard关系,并指向可发现的关于整个服务的元数据。我们将在后面的部分中探讨这个问题。

您可以通过导航员工链接进一步挖掘这项服务。

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

在这个阶段,您将查看所有员工的集合。

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


链接是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"
    }
  }
}

您还可以像本指南中所示的那样POST、PATCH和DELETE。但我们还是不要深究了。您已经花费了太多的时间来手动与REST服务交互。难道你不想构建一个漂亮的UI吗?

设置一个自定义UI控制器

SpringBoot使它非常容易地定制一个web页面。首先,您需要一个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()方法来支持/路由。

它返回索引作为模板的名称,Spring Boot的自动配置视图解析器将映射到src/main/resources/templat/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>
    <link rel="stylesheet" href="/main.css" />
</head>
<body>

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

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

</body>
</html>

这个模板的关键部分是中间的<div id="react"></div>组件。在这里,您将直接作出反应,以插入渲染的输出。

您可能还想知道bundle.js文件从何而来。它的构建方式将在下一节中显示。


加载JavaScript模块

This section contains the barebones information to get off the JavaScript bits off the ground。(?)

虽然您可以安装所有的javascommand命令行工具,但您不必这样做。至少,现在还不是时候。相反,你需要添加的是你的pom.xml构建文件:

The  frontend-maven-plugin used to build JavaScript bits
<plugin>
	<groupId>com.github.eirslett</groupId>
	<artifactId>frontend-maven-plugin</artifactId>
	<version>1.2</version>
	<configuration>
		<installDirectory>target</installDirectory>
	</configuration>
	<executions>
		<execution>
			<id>install node and npm</id>
			<goals>
				<goal>install-node-and-npm</goal>
			</goals>
			<configuration>
				<nodeVersion>v4.4.5</nodeVersion>
				<npmVersion>3.9.2</npmVersion>
			</configuration>
		</execution>
		<execution>
			<id>npm install</id>
			<goals>
				<goal>npm</goal>
			</goals>
			<configuration>
				<arguments>install</arguments>
			</configuration>
		</execution>
		<execution>
			<id>webpack build</id>
			<goals>
				<goal>webpack</goal>
			</goals>
		</execution>
	</executions>

</plugin>

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

install-node-npm命令将安装node.js和它的包管理工具npm进入目标文件夹。(这确保了二进制文件没有被拉到源代码控制之下,并且可以用干净的方式进行清理)。

npm命令将使用提供的参数(安装)来执行npm二进制文件。这将安装在package.json中定义的模块。

webpack命令将执行webpack二进制文件,它根据webpack.config.js编译所有JavaScript代码。

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

什么模块被安装?JavaScript开发人员通常使用npm来构建一个package.json文件如下所示:

package.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": "^15.3.2",
    "react-dom": "^15.3.2",
    "rest": "^1.3.1",
    "webpack": "^1.12.2"
  },
  "scripts": {
    "watch": "webpack --watch -d"
  },
  "devDependencies": {
    "babel-core": "^6.18.2",
    "babel-loader": "^6.2.7",
    "babel-polyfill": "^6.16.0",
    "babel-preset-es2015": "^6.18.0",
    "babel-preset-react": "^6.16.0"
  }
}

关键依赖关系包括: 

react.js-本教程使用的工具箱

rest.js-CujoJS工具包用于进行REST调用

webpack-用来将JavaScript组件编译成一个可加载的捆绑包的工具包

babel-使用ES6编写JavaScript代码并将其编译为ES5,以便在浏览器中运行


要构建JavaScript代码,您需要进一步深入了解,您需要为webpack定义一个构建文件

webpack.config.js
var path = require('path');

module.exports = {
    entry: './src/main/js/app.js',
    devtool: 'sourcemaps',
    cache: true,
    debug: true,
    output: {
        path: __dirname,
        filename: './src/main/resources/static/built/bundle.js'
    },
    module: {
        loaders: [
            {
                test: path.join(__dirname, '.'),
                exclude: /(node_modules)/,
                loader: 'babel',
                query: {
                    cacheDirectory: true,
                    presets: ['es2015', 'react']
                }
            }
        ]
    }
};

这个webpack配置文件执行以下操作:

将入口点定义为 ./src/main/js/app.js。从本质上说,app.js(我们将很快编写的一个模块)是我们的JavaScript应用程序的public statoc void main()。webpack必须知道这一点,以便知道当最终捆绑包被浏览器加载时要启动什么。

在浏览器中调试JS代码时,创建sourcemaps,能够链接回原始源代码。

将所有的JavaScript位编译成./src/main/resources/static/buil/bundle.js,它是一款类似于SpringBoot的uber JAR的JavaScript脚本。所有您的自定义代码和in a la require()调用的模块都被塞进这个文件中。

它使用es2015和响应预置来连接到babel引擎,以便将ES6的反应代码编译成能够在任何标准浏览器中运行的格式。

想要看到你的JavaScript自动改变吗?运行npm run-script watch 将webpack放入观察模式。它将在编辑源代码时重新生成bundle.js。

有了这些之后,您就可以将注意力集中在DOM加载后获取的反应位上。它被分解成如下部分:

因为您正在使用webpack来组装东西,所以继续获取您需要的模块:

src/main/js/app.js
const React = require('react');
const ReactDOM = require('react-dom');
const client = require('./client');

React和ReactDom是Facebook用来构建这款应用的主要库。client是配置rest的自定义代码。js包括对HAL、URI模板和其他东西的支持。它还将默认的Accept请求头设置为application/hal+json。你可以在这里阅读代码

深入React

React是基于组件定义的。通常,一个组件可以在父子关系中容纳另一个组件的多个实例。这个概念很容易扩展好几层。

首先,对于所有组件都有一个顶级的容器是非常方便的。(当您在本系列文章中扩展代码时,这将变得更加明显。)现在,你只有员工名单。但是以后可能还需要一些其他相关的组件,让我们从这个开始:

src/main/js/app.js - App component
class App extends React.Component {

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

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

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

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

componentDidMount t是在React中调用DOM中的组件后调用的API。

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

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

在App组件中,从Spring Data REST后端取出一批员工,并存储在该组件的状态数据中。

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

状态是组件被期望处理自身的数据。它也是可以波动和变化的数据。要读取状态,您可以使用this.state。要更新它,您可以使用this.setstate()。每次调用setstate()时,都会对状态进行更新,计算前一个状态和新状态之间的差异,并向页面上的DOM注入一组更改。这将导致对UI的快速和高效的更新。

常见的约定是在构造函数中清空所有属性的状态。然后使用componentDidMount从服务器查找数据并填充属性。从这里开始,更新可以由用户操作或其他事件驱动。

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

JavaScript不会像其他语言一样锁定数据结构。你可以通过分配值来破坏属性,但是这与React的微分引擎不兼容,应该避免。


在这段代码中,函数通过客户端加载数据,这是一个承诺兼容的rest.js实例。当它从/api/employees中检索时,它会调用done()中的函数,并根据它的HAL document(response.entity._embedded.employees)来设置状态。您可能还记得curl/api/employees的结构,并了解它是如何映射到这个结构的。

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

下面是一个雇员列表的定义。

src/main/js/app.js - EmployeeList component
class EmployeeList extends React.Component{
	render() {
		var 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的map函数,this.props.employees从一组员工记录转变为<Element />React组件(您将会看到更深入的内容)。

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

这显示了一个新的React组件(注意大写的格式)和两个属性:键和数据。这些都是来自employee.self.href和employee。

当您使用SpringDataREST时,self link是给定资源的键。React需要一个唯一的子节点标识,以及_links.self.href是完美的。

最后,您返回一个HTML表格,它围绕着用映射构建的雇员数组。

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

这个简单布局的状态、属性和HTML显示了React如姐让您可以声明地创建一个简单且易于理解的组件。

这段代码包含HTML和JavaScript吗?是的,这是JSX。没有必要使用它。可以使用纯JavaScript编写响应,但是JSX语法非常简洁。多亏了babel.js的快速工作。它同时提供了JSX和ES6支持。

JSX还包括ES6的部分。在代码中使用的是箭头函数。它避免创建一个嵌套函数(),并避免需要一个self变量。

担心把逻辑和你的结构混合在一起?React的api鼓励良好的、声明性的结构与状态和属性相结合。与其混合一堆不相关的JavaScript和HTML,还不如鼓励构建简单的组件,这些组件的相关状态和属性可以很好地协同工作。它让您查看单个组件并了解设计。然后它们很容易组合在一起,形成更大的结构。

接下来,您需要定义什么是<Employee />

src/main/js/app.js - Employee component
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表格行,围绕着员工的三个属性。这个属性本身就是this.prop.employee。请注意,如何通过JavaScript对象使传递从服务器获取的数据变得容易?

因为这个组件不管理任何状态,也不处理用户输入,所以没有其他事情可做。这可能会诱使你把它塞进上面的<EmployeeList />不要这样做!相反,将你的应用分割成小的组件,每个人都做一份工作,这将使你在未来更容易建立起功能。

最后一步是渲染整个过程。

src/main/js/app.js - rendering code
ReactDOM.render(
	<App />,
	document.getElementById('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"

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

现在你可以看到它们都在网站上列出了。

回顾

在本节中:

你定义了一个域对象和一个相应的存储库。

您可以让SpringDataREST导出它,并使用完全的超媒体控件。

您在父子关系中创建了两个简单的React组件。

您获取了服务器数据并将它们呈现为一个简单的静态HTML结构。


问题?

web页面并不是动态的。您必须刷新浏览器以获取新的记录。

web页面没有使用任何超媒体控件或元数据。相反,它是硬编码从/api/雇员获取数据的。

它是只读的。虽然您可以使用cURL来修改记录,但是web页面并没有提供这些内容。

这些是我们在下一节中可以解决的问题。


猜你喜欢

转载自blog.csdn.net/zmzmzm123321/article/details/80494277