本周 Spring - 2015 年 10 月 6 日

工程 | Josh Long | 2015 年 10 月 7 日 | ...

欢迎来到另一期本周 Spring!像往常一样,我们本周有很多内容要介绍,所以让我们开始吧!

Spring Initializr 的发展

工程 | Brian Clozel | 2015 年 10 月 6 日 | ...

我们很高兴今天在https://start.spring.io发布 Spring Initializr 的新版本!

最初,它只是一个用于生成 Spring Boot 项目的小型内部 Web 应用程序,但后来发展成为超出我们预期的规模。您现在可以在 Web 上、您最喜欢的 IDE(Eclipse STS 和 IntelliJ IDEA)中,甚至使用命令行工具(尝试curl https://start.spring.io)使用 Spring Initializr。

与此同时,Spring 产品组合也在不断发展,我们收到了来自 Spring 社区的许多有益反馈。因为没有什么比实际数据更重要,所以在夏季之前,我们改进了服务,使其能够将其指标导出到集中的 Redis 实例。这使我们能够在较长时间内(无论我们在Pivotal Web Services上部署了多少个实例)保持可靠的统计数据集…

本周 Spring - 2015 年 9 月 29 日

工程 | Josh Long | 2015 年 9 月 30 日 | ...

欢迎来到另一期本周 Spring!对于那些在美国阅读的读者,请允许我首先祝大家度过一个愉快的全国咖啡日!像往常一样,我们有很多内容要介绍,所以让我们开始吧!

React.js 和 Spring Data REST:第 3 部分 - 条件操作

工程 | Greg L. Turnquist | 2015 年 9 月 29 日 | ...
要查看此代码的更新,请访问我们的React.js 和 Spring Data REST 教程

上一节中,您了解了如何开启 Spring Data REST 的超媒体控件、让 UI 通过分页进行导航以及根据页面大小的变化动态调整大小。您添加了创建和删除员工的功能,并让页面进行调整。但是,如果没有考虑其他用户对您当前正在编辑的相同数据所做的更新,那么任何解决方案都不完整。

随时可以从此存储库中获取代码并继续学习。本节基于上一节的应用程序,并添加了一些额外内容。

是 PUT 还是不 PUT,这是一个问题

当您获取资源时,如果其他人更新了它,则存在它可能过时的风险。为了解决这个问题,Spring Data REST 集成了两种技术:资源版本控制和 ETag。

通过在后端对资源进行版本控制并在前端使用 ETag,可以有条件地执行 PUT 操作。换句话说,您可以检测资源是否已更改,并防止 PUT(或 PATCH)覆盖其他人的更新。让我们来看看。

资源版本控制

要支持资源版本控制,请为需要此类保护的域对象定义一个版本属性。

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 @Version @JsonIgnore Long version;

private Employee() {}

public Employee(String firstName, String lastName, String description) {
	this.firstName = firstName;
	this.lastName = lastName;
	this.description = description;
}

}

  • version 字段使用javax.persistence.Version进行注释。它会导致每次插入和更新行时自动存储和更新一个值。

在获取单个资源(而不是集合资源)时,Spring Data REST 会自动添加一个带有此字段值的 ETag 响应标头

获取单个资源及其标头

上一节中,您使用集合资源来收集数据并填充 UI 的 HTML 表格。在 Spring Data REST 中,_embedded 数据集被视为数据的预览。虽然它对于快速浏览数据很有用,但要获取像 ETag 这样的标头,您需要分别获取每个资源。

在此版本中,loadFromServer已更新为获取集合,然后使用 URI 来检索每个单独的资源。

src/main/resources/static/app.jsx - 获取每个资源
loadFromServer: function (pageSize) {
    follow(client, root, [
        {rel: 'employees', params: {size: pageSize}}]
    ).then(employeeCollection => {
        return client({
            method: 'GET',
            path: employeeCollection.entity._links.profile.href,
            headers: {'Accept': 'application/schema+json'}
        }).then(schema => {
            this.schema = schema.entity;
            this.links = employeeCollection.entity._links;
            return employeeCollection;
        });
    }).then(employeeCollection => {
        return employeeCollection.entity._embedded.employees.map(employee =>
                client({
                    method: 'GET',
                    path: employee._links.self.href
                })
        );
    }).then(employeePromises => {
        return when.all(employeePromises);
    }).done(employees => {
        this.setState({
            employees: employees,
            attributes: Object.keys(this.schema.properties),
            pageSize: pageSize,
            links: this.links
        });
    });
},
  1. follow()函数转到employees集合资源。
  2. then(employeeCollection ⇒ …​)子句创建了一个获取 JSON Schema 数据的调用。它有一个子 then 子句,用于将元数据和导航链接存储在<App/>组件中。
    • 请注意,此嵌入的 Promise 返回了 employeeCollection。这样,集合可以在传递到下一个调用的同时,让您沿途获取元数据。
  3. 第二个then(employeeCollection ⇒ …​)子句将员工集合转换为获取每个单独资源的 GET Promise 数组。这是获取每个员工的 ETag 标头所需的步骤。
  4. then(employeePromises ⇒ …​)子句获取 GET Promise 数组,并使用when.all()将它们合并到单个 Promise 中,在所有 GET Promise 都解析后解析。
  5. loadFromServer使用done(employees ⇒ …​)结束,其中 UI 状态使用此数据集合进行更新。

此链在其他地方也已实现。例如,onNavigate()(用于跳转到不同页面)已更新为获取单个资源。由于它与上面显示的内容基本相同,因此在本节中省略了它。

更新现有资源

在本节中,您将添加一个UpdateDialog React 组件来编辑现有的员工记录。

src/main/resources/static/app.jsx - UpdateDialog 组件
var UpdateDialog = React.createClass({
handleSubmit: function (e) {
    e.preventDefault();
    var updatedEmployee = {};
    this.props.attributes.forEach(attribute =&gt; {
        updatedEmployee[attribute] = React.findDOMNode(this.refs[attribute]).value.trim…

查看新的“使用 Vaadin 创建 CRUD UI”指南

工程 | Greg L. Turnquist | 2015 年 9 月 28 日 | ...

Spring 社区成员您好,

今天,我们发布了一份新的指南:使用 Vaadin 创建 CRUD UI

此指南由 Vaadin 团队撰写,展示了如何很好地构建一个利用 Spring Data 的 UI,而无需编写任何 JavaScript 或 HTML 代码。

Vaadin 非常酷

  • 自带 Spring Boot 启动器
  • 与构造函数注入配合良好
  • 使插入持久性解决方案(如 Spring Data)变得非常简单
  • 配备了一系列插件,可以发展成真正的应用程序

查看这份最新指南,并玩得开心!

继SpringOne 2GX之后,我们发现还需要编写一些指南。云原生故事席卷了开发社区,人们正在寻求更多解决方案。我们的工作不应该只是堆叠服务器或构建基础设施。相反,我们应该专注于提供流畅且易用的解决方案。以**云原生**的方式编写代码……

本周 Spring - 2015年9月22日

工程 | Josh Long | 2015年9月22日 | ...

欢迎来到另一期本周 Spring!本周,在令人难以置信的SpringOne2GX 2015之后,说实话,我有点筋疲力尽了!:D 但是,Spring 依然强大,我本周在阿姆斯特丹与我们的朋友 Martin Deinum一起,帮助一大批开发者使用 Spring Boot、Spring Cloud 和 Cloud Foundry 进行云原生之旅。

Spring Integration 1.1 GA 版 Java DSL 可用

工程 | Artem Bilan | 2015年9月22日 | ...

亲爱的 Spring 社区!

我代表 Spring Integration 团队很高兴地宣布,Spring Integration Java DSL 的 1.1 GA 版现已可从发布仓库Maven Central获取。

对于 Gradle,请使用此依赖项

'org.springframework.integration:spring-integration-java-dsl:1.1.0.RELEASE'

对于 Maven,请使用此依赖项

<dependency>
     <groupId>org.springframework.integration</groupId>
     <artifactId>spring-integration-java-dsl</artifactId>
     <version>1.1.0.RELEASE</version>
</dependency>

首先,非常感谢所有在上周参加我演讲的 SpringOne 2GX 2015 与会者……

使用 Vaadin Spring 1.0 构建单页 Web 应用

工程 | Stéphane Nicoll | 2015年9月17日 | ...

这篇文章是由社区成员Matti Tahvonen (@MattiTahvonen)撰写的客座文章,他曾在Vaadin Ltd担任开发者布道师,该公司最初开发了 Vaadin 框架,并为其提供商业服务和扩展。

Vaadin 的 Spring 集成库自 5 月份以来一直处于测试阶段,并且已被多个生产应用程序使用。今天,我们很自豪地宣布测试阶段结束,稳定的 1.0.0 版本现已发布。

Vaadin是一个基于组件的 Web UI 框架,您的应用程序状态和逻辑驻留在 Java 的内存中……

本周 Spring - 2015年9月15日 - SpringOne2GX 2015 特辑!

工程 | Josh Long | 2015年9月16日 | ...

欢迎来到另一期本周 Spring!本周,我们来到了美丽的华盛顿特区参加精彩的 SpringOne2GX 2015

这是一场精彩的活动,充满了令人惊叹的、关键的时刻,超过一千名与会者(包括来自全球一些最大网站的工程师,如乐天、阿里巴巴和 Netflix 等)加入我们在华盛顿特区,学习并热爱 Pivotal 在 Spring 及其周边所做的一切。

以下是我的一些最喜欢的时刻

get cloud native

  • 今年,Pivotal 在云原生方面取得了比以往任何时候都更快的发展,SpringOne2GX 则成为了这场驱动力的盛大庆祝:拥抱云原生。
  • ……

React.js 和 Spring Data REST:第 2 部分 - 超媒体

工程 | Greg L. Turnquist | 2015年9月15日 | ...
要查看此代码的更新,请访问我们的React.js 和 Spring Data REST 教程

上一节中,您了解了如何建立一个后端工资服务,使用 Spring Data REST 存储员工数据。它缺少的一个关键功能是使用超媒体控件和链接进行导航。相反,它硬编码了查找数据的路径。

欢迎从此存储库中获取代码并跟随操作。本节基于上一节的应用程序,并添加了一些额外内容。

起初,只有数据……然后出现了 REST

越来越多的人将任何基于 HTTP 的接口称为 REST API,这让我感到沮丧。今天的例子是 SocialSite REST API。这就是 RPC。它完全是 RPC……要如何才能使 REST 架构风格明确地体现超文本是约束这一概念?换句话说,如果应用程序状态(以及 API)的引擎不是由超文本驱动的,那么它就不能是 RESTful,也不能是 REST API。就是这样。是否有一些需要修复的错误手册?

那么,超媒体控件(即超文本)究竟是什么,如何使用它们呢?为了弄清楚,让我们退一步,看看 REST 的核心任务。

REST 的概念是借鉴使 Web 如此成功的理念,并将它们应用于 API。尽管 Web 规模庞大、性质动态且客户端(即浏览器)更新率低,但 Web 仍然取得了巨大的成功。Roy Fielding 试图利用其一些约束和特性,看看是否可以类似地扩展 API 的生产和使用。

其中一个约束是限制动词的数量。对于 REST 来说,主要的动词是 GET、POST、PUT、DELETE 和 PATCH。还有其他动词,但这里不再赘述。

  • GET - 获取资源的状态,而不更改系统
  • POST - 创建一个新的资源,而不指定位置
  • PUT - 替换现有资源,覆盖任何其他已存在的内容(如果有)
  • DELETE - 删除现有资源
  • PATCH - 部分修改现有资源

这些是具有良好规范的标准化 HTTP 动词。通过选择和使用现有的 HTTP 操作,我们无需发明新的语言并对行业进行教育。

REST 的另一个约束是使用媒体类型来定义数据的格式。与其每个人都编写自己的信息交换方言,不如开发一些媒体类型。其中一个最受欢迎的媒体类型是 HAL,媒体类型为 application/hal+json。它是 Spring Data REST 的默认媒体类型。一个关键的价值在于,REST 没有集中式的、唯一的媒体类型。相反,人们可以开发媒体类型并将其插入。尝试一下。随着不同需求的出现,行业可以灵活地做出调整。

REST 的一个关键特性是包含指向相关资源的链接。例如,如果您正在查看订单,RESTful API 将包含指向相关客户的链接、指向商品目录的链接,以及可能指向下单商店的链接。在本节中,您将介绍分页,并了解如何使用导航分页链接。

启用后端分页

要开始使用前端超媒体控件,您需要启用一些额外的控件。Spring Data REST 提供了分页支持。要使用它,只需调整存储库定义

src/main/java/com/greglturnquist/payroll/EmployeeRepository.java
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {

}

您的接口现在扩展了PagingAndSortingRepository,它添加了设置页面大小的额外选项,并添加了导航链接以在页面之间跳转。其余后端保持不变(除了一些额外预加载的数据使事情变得有趣)。

重新启动应用程序(./mvnw spring-boot:run)并查看其工作原理。

$ curl localhost:8080/api/employees?size=2
{
  "_links" : {
    "first" : {
      "href" : "https://127.0.0.1:8080/api/employees?page=0&size=2"
    },
    "self" : {
      "href" : "https://127.0.0.1:8080/api/employees"
    },
    "next" : {
      "href" : "https://127.0.0.1:8080/api/employees?page=1&size=2"
    },
    "last" : {
      "href" : "https://127.0.0.1:8080/api/employees?page=2&size=2"
    }
  },
  "_embedded" : {
    "employees" : [ {
      "firstName" : "Frodo",
      "lastName" : "Baggins",
      "description" : "ring bearer",
      "_links" : {
        "self" : {
          "href" : "https://127.0.0.1:8080/api/employees/1"
        }
      }
    }, {
      "firstName" : "Bilbo",
      "lastName" : "Baggins",
      "description" : "burglar",
      "_links" : {
        "self" : {
          "href" : "https://127.0.0.1:8080/api/employees/2"
        }
      }
    } ]
  },
  "page" : {
    "size" : 2,
    "totalElements" : 6,
    "totalPages" : 3,
    "number" : 0
  }
}

默认页面大小为 20,因此要查看其工作原理,请应用?size=2。如预期的那样,仅列出了两个员工。此外,还有一个firstnextlast链接。还有一个self链接,不包含上下文包括页面参数

如果您导航到next链接,您将看到一个prev链接

$ curl "https://127.0.0.1:8080/api/employees?page=1&size=2"
{
  "_links" : {
    "first" : {
      "href" : "https://127.0.0.1:8080/api/employees?page=0&size=2"
    },
    "prev" : {
      "href" : "https://127.0.0.1:8080/api/employees?page=0&size=2"
    },
    "self" : {
      "href" : "https://127.0.0.1:8080/api/employees"
    },
    "next" : {
      "href" : "https://127.0.0.1:8080/api/employees?page=2&size=2"
    },
    "last" : {
      "href" : "https://127.0.0.1:8080/api/employees?page=2&size=2"
    }
  },
...
注意
在 URL 查询参数中使用“&”时,命令行会将其视为换行符。将整个 URL 括在引号中以绕过此问题。

这看起来很不错,但是当您更新前端以利用它时,它会变得更好。

通过关系导航

就是这样!无需对后端进行任何其他更改即可开始使用 Spring Data REST 开箱即用的超媒体控件。(这就是 Spring Data REST 的魅力所在。无需杂乱的控制器更新!)

注意
需要指出的是,此应用程序并非“特定于 Spring Data REST”。相反,它使用HALURI 模板和其他标准。这就是使用 rest.js 非常简单的原因:该库附带了 HAL 支持。

在上一节中,您将路径硬编码为/api/employees。相反,您应该硬编码的唯一路径是根路径。

...
var root = '/api';
...

使用方便的小型follow()函数,您现在可以从根路径开始导航到所需位置!

componentDidMount: function () {
    this.loadFromServer(this.state.pageSize);
},

在上一节中,加载是在componentDidMount()内部直接完成的。在本节中,我们使在更新页面大小时能够重新加载整个员工列表成为可能。为此,我们将内容移到了loadFromServer()中。

loadFromServer: function (pageSize) {
    follow(client, root, [
        {rel: 'employees', params: {size: pageSize}}]
    ).then(employeeCollection => {
        return client({
            method: 'GET',
            path: employeeCollection.entity._links.profile.href,
            headers: {'Accept': 'application/schema+json'}
        }).then(schema => {
            this.schema = schema.entity;
            return employeeCollection;
        });
    }).done(employeeCollection => {
        this.setState({
            employees: employeeCollection.entity._embedded.employees,
            attributes: Object.keys(this.schema.properties),
            pageSize: pageSize,
            links: employeeCollection.entity._links});
    });
},

loadFromServer与上一节非常相似,但它使用follow()

  • follow()函数的第一个参数是用于进行 REST 调用的client对象。
  • 第二个参数是从其开始的根 URI。
  • 第三个参数是要沿其导航的关系数组。每个关系可以是字符串或对象。

关系数组可以像["employees"]一样简单,这意味着在进行第一次调用时,在_links中查找名为employees的关系(或rel)。找到其href并导航到它。如果数组中还有其他关系,则重复此操作。

有时,仅使用 rel 不够。在此代码片段中,它还插入了?size=<pageSize>的查询参数。还有其他可以提供的选项,您将在后面看到。

获取 JSON Schema 元数据

在使用基于大小的查询导航到**employees**后,**employeeCollection** 就唾手可得。在上一节中,我们结束了这一天并显示了<EmployeeList /> 内的数据。今天,您将执行另一个调用以获取一些位于/api/profile/employeesJSON Schema 元数据

您可以自己查看数据

$ curl https://127.0.0.1:8080/api/profile/employees -H 'Accept:application/schema+json'
{
  "title" : "Employee",
  "properties" : {
    "firstName" : {
      "title" : "First name",
      "readOnly" : false,
      "type" : "string"
    },
    "lastName" : {
      "title" : "Last name",
      "readOnly" : false,
      "type" : "string"
    },
    "description" : {
      "title" : "Description",
      "readOnly" : false,
      "type" : "string"
    }
  },
  "definitions" : { },
  "type" : "object",
  "$schema" : "https://json-schema.fullstack.org.cn/draft-04/schema#"
}
注意
/profile/employees 处的元数据默认格式为 ALPS。但是,在这种情况下,您正在使用内容协商来获取 JSON Schema。

通过在`<App />`组件的状态中捕获此信息,您以后在构建输入表单时可以很好地利用它。

创建新记录

有了这些元数据,您现在可以向 UI 添加一些额外的控件。创建一个新的 React 组件,<CreateDialog />

var CreateDialog = React.createClass({
handleSubmit: function (e) {
    e.preventDefault();
    var newEmployee = {};
    this.props.attributes.forEach(attribute =&gt; {
        newEmployee[attribute] = React.findDOMNode(this.refs[attribute]).value.trim();
    });
    this.props.onCreate(newEmployee);

    // clear out the dialog's inputs
    this.props.attributes.forEach(attribute =&gt; {
        React.findDOMNode(this.refs[attribute]).value = '';
    });

    // Navigate away from the dialog to hide it.
    window.location = "#";
},

render: function () {
    var inputs = this.props.attributes.map(attribute…

获取 Spring 时事通讯

与 Spring 时事通讯保持联系

订阅

领先一步

VMware 提供培训和认证,以加速您的进步。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部