该指南将引导你创建通过基于超媒体的 RESTful 前端访问 Pivotal GemFire 的数据。
我们将构建一个 Spring 应用,该应用允许我们使用 Spring Data REST 创建和检索存储在 Pivotal GemFire 内存数据网格中的 Person
对象。Spring Data REST 具有 Spring HATEOAS 和 Spring Data for Pivotal GemFire 的功能,并将它们自动结合在一起。
Spring Data REST 还支持将 Spring Data JPA、Spring Data MongoDB 和 Spring Data Neo4j 作为后端数据存储,但这些都不属于该指南的一部分。
有关 Pivotal GemFire 概念和从 Pivotal GemFire 访问数据的更多常规知识,请通读使用 Pivotal GemFire 访问数据的指南。
像大多数的 Spring 入门指南一样,你可以从头开始并完成每个步骤,也可以绕过你已经熟悉的基本设置步骤。如论哪种方式,你最终都有可以工作的代码。
待一切就绪后,可以检查一下 gs-accessing-gemfire-data-rest/complete
目录中的代码。
首先,我们设置一个基本的构建脚本。在使用 Spring 构建应用时可以使用任何喜欢的构建系统,但此处包含使用 Gradle 和 Maven 所需的代码。如果你都不熟悉,请参阅使用 Gradle 构建 Java 项目或使用 Maven 构建 Java 项目。
在我们选择的项目目录中,创建以下自目录结构;例如,在 *nix 系统上使用 mkdir -p src/main/java/hello
:
└── src
└── main
└── java
└── hello
以下是初始 Gradle 构建文件。
build.gradle
buildscript {
repositories {
mavenCentral()
maven { url "https://repo.spring.io/libs-release" }
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:2.2.1.RELEASE")
}
}
plugins {
id "io.spring.dependency-management" version "1.0.5.RELEASE"
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
sourceCompatibility = 1.8
targetCompatibility = 1.8
bootJar {
baseName = 'gs-accessing-gemfire-data-rest'
version = '0.1.0'
}
repositories {
mavenCentral()
maven { url "https://repo.spring.io/libs-release" }
}
dependencies {
compile("org.springframework.boot:spring-boot-starter-data-rest") {
exclude group: "org.springframework.boot", module: "spring-boot-starter-logging"
}
compile("org.springframework.data:spring-data-gemfire")
compile("org.projectlombok:lombok")
runtime("org.springframework.shell:spring-shell:1.2.0.RELEASE")
testCompile("org.springframework.boot:spring-boot-starter-test") {
exclude group: "org.springframework.boot", module: "spring-boot-starter-logging"
}
}
Spring Boot gradle 插件提供了许多方便的功能:
public static void main()
方法并将其标记为可运行类;首先,我们搭建一个基本的构建脚本。使用 Spring 构建应用时,可以使用任何喜欢的构建系统,但是此处包含了使用 Maven 所需的弟阿玛。如果你不熟悉 Maven,请参阅使用 Maven 构建 Java 项目。
在我们选择的项目目录中,创建以下自目录结构;例如,在 *nix 系统上使用 mkdir -p src/main/java/hello
:
└── src
└── main
└── java
└── hello
以下是初始 Maven 构建文件。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<groupId>org.springframework</groupId>
<artifactId>gs-accessing-gemfire-data-rest</artifactId>
<version>0.1.0</version>
<properties>
<spring-shell.version>1.2.0.RELEASE</spring-shell.version>
</properties>
<repositories>
<repository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-gemfire</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell</artifactId>
<version>${spring-shell.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Spring Boot Maven 插件提供了许多方便的功能:
public static void main()
方法并将其标记为可运行类;创建一个新的域对象来展示一个人。
src/main/java/hello/Person.java
package hello;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.gemfire.mapping.annotation.Region;
import lombok.Data;
@Data
@Region("People")
public class Person {
private static AtomicLong COUNTER = new AtomicLong(0L);
@Id
private Long id;
private String firstName;
private String lastName;
@PersistenceConstructor
public Person() {
this.id = COUNTER.incrementAndGet();
}
}
Person
对象有一个名字和一个姓氏。Pivotal GemFire 域对象需要一个 ID,因此每次创建 Person
对象时都会使用 AtomicLong
进行递增。
Person
存储库接下来,我们需要创建一个简单的存储库,如以下示例所示(在 src/main/java/hello/PersonRepository.java
中):
package hello;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends CrudRepository<Person, Long> {
List<Person> findByLastName(@Param("name") String name);
}
该存储库是一个接口,将允许我们执行涉及 Person
对象的各种数据访问操作(例如基本 CRUD 和简单查询)。它通过扩展 CrudRepository
获得这些操作。
在运行时,用于 Pivotal GemFire 的 Spring Data 将自动创建该接口的实现。然后,Spring Data REST 将使用 @RepositoryRestResource 注解来指示 Spring MVC 在 /people
处创建基于 REST 的端点。
导出存储库不需要
@RepositoryRestResource
。它仅用于更改导出详细信息,例如使用/people
代替/persons
默认值。
这里,我们还定义了一个自定义查询,以基于 lastName
值检索 Person
对象的列表。我们可以在该指南的后续部分中看到如何调用它。
尽管可以将服务打包为传统的 WAR 文件以部署到外部应用服务器,但是下面演示的更简单的方法创建了一个独立的应用。我们将所有内容打包在一个可执行的 JAR 文件中,由一个经典的 Java main() 方法驱动。在该过程中,我们将使用 Spring 的支持将 Tomcat servlet 容器作为 HTTP 运行时嵌入,而不是部署到外部 servlet 容器。
src/main/java/hello/Application.java
package hello;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
import org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions;
import org.springframework.data.gemfire.repository.config.EnableGemfireRepositories;
@SpringBootApplication
@ClientCacheApplication(name = "AccessingGemFireDataRestApplication", logLevel = "error")
@EnableEntityDefinedRegions(basePackageClasses = Person.class,
clientRegionShortcut = ClientRegionShortcut.LOCAL)
@EnableGemfireRepositories
@SuppressWarnings("unused")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@SpringBootApplication
是一个便利的注解,它添加了以下所有内容:
@Configuration
:将类标记为应用上下文 Bean 定义的源;@EnableAutoConfiguration
:告诉 Spring Boot 根据类路径配置、其他 bean 以及各种属性的配置来添加 bean。@ComponentScan
:告知 Spring 在 hello
包中寻找他组件、配置以及服务。main()
方法使用 Spring Boot 的 SpringApplication.run()
方法启动应用。
@EnableGemfireRepositories
注解可激活 Spring Data for Pivotal GemFire 存储库。Pivotal GemFire 的 Spring 数据将创建 PersonRepository
接口的具体实现,并将其配置为与 Pivotal GemFire 嵌入式实例进行通信。
我们可以结合 Gradle 或 Maven 来从命令行运行该应用。我们还可以构建一个包含所有必须依赖项、类以及资源的可执行 JAR 文件,然后运行该文件。在整个开发生命周期中,跨环境等等情况下,构建可执行 JAR 可以轻松地将服务作为应用进行发布、版本化以及部署。
如果使用 Gradle,则可以借助 ./gradlew bootRun
来运行应用。或通过借助 ./gradlew build
来构建 JAR 文件,然后运行 JAR 文件,如下所示:
java -jar build/libs/gs-accessing-gemfire-data-rest-0.1.0.jar
如果使用 Maven,则可以借助 ./mvnw spring-boot:run
来运行该用。或可以借助 ./mvnw clean package
来构建 JAR 文件,然后运行 JAR 文件,如下所示:
java -jar target/gs-accessing-gemfire-data-rest-0.1.0.jar
我们还可以将 JAR 应用转换成 WAR 应用。
显示日志记录输出。该服务应在几秒内启动并运行。
现在该应用正在运行,我们可以对其进行测试。我们可以使用任何喜欢的 REST 客户端。以下示例使用 curl
。
首先,我们要查看顶级服务。以下示例(带有输出)显示了如何执行该操作:
curl http://localhost:8080
{
"_links" : {
"people" : {
"href" : "http://localhost:8080/people"
}
}
}
这里,我们可以初步了解该服务器所提供的功能。在 http://localhost:8080/people 上有一个 people 链接。Spring Data for Pivotal GemFire 不像其他 Spring Data REST 指南那样支持分页,因此没有多于的导航连接。
Spring Data REST 使用 HAL 格式进行 JSON 输出。它非常灵活,并提供了一种便捷的方式来提供与所提供数据相邻的链接。
使用 people 链接时,我们会在数据库中看到 Person
的记录(目前没有):
curl http://localhost:8080/people
{
"_links" : {
"search" : {
"href" : "http://localhost:8080/people/search"
}
}
}
当前没有任何元素,所以是时候创建一个新的 Person
了!
curl -i -X POST -H "Content-Type:application/json" -d '{ "firstName" : "Frodo", "lastName" : "Baggins" }' http://localhost:8080/people
HTTP/1.1 201 Created
Server: Apache-Coyote/1.1
Location: http://localhost:8080/people/1
Content-Length: 0
Date: Wed, 05 Mar 2014 20:16:11 GMT
-i
:确保我们可以看到包括标题的响应消息。显示新创建的 Person
的 URI;-X POST
表示这是用于创建新条目的 POST
;-H "Content-Type:application/json"
:设置内容类型,以便应用知道有效负载包含 JSON 对象;-d'{"firstName: "Frodo", "lastName": "Barggins""}'
:被发送的数据;请注意,对
POST
操作的响应如何包含Location
标头。它包含新创建资源的 URI。Spring Data REST 还具有两个方法(RepositoryRestConfiguration.setReturnBodyOnCreate(…)
与setReturnBodyOnUpdate(…)
),我们可以使用它们来配置框架以立即返回刚刚创建的资源的表示形式。
RepositoryRestConfiguration.setReturnBodyForPutAndPost(…)
是一种启用创建和更新操作的表示形式响应的快捷方式。
我们可以查询所有人,如以下示例所示:
curl http://localhost:8080/people
{
"_links" : {
"search" : {
"href" : "http://localhost:8080/people/search"
}
},
"_embedded" : {
"persons" : [ {
"firstName" : "Frodo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/people/1"
}
}
} ]
}
}
people
对象包含一个包含 Frodo
的列表。注意它是如何包含一个 self
链接的。Spring Data REST 还使用 Evo Inflector 来对实体名称进行复数以进行分组。
我们可以直接查询单个记录,如下所示:
curl http://localhost:8080/people/1
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/people/1"
}
}
}
这似乎完全是基于 Web 的,但是后台有一个嵌入式的 Pivotal GemFire 数据库。
在该指南中,只有一个域对象。在域对象相互关联的更复杂的系统中,Spring Data REST 展示了更多链接,以帮助导航至连接的记录。
我们可以通过运行以下命令(及其输出显示)找到所有自定义查询:
curl http://localhost:8080/people/search
{
"_links" : {
"findByLastName" : {
"href" : "http://localhost:8080/people/search/findByLastName{?name}",
"templated" : true
}
}
}
我们可以看到查询的 URL,包括 HTTP 查询参数,name
。请注意,这与接口中嵌入的 @Param("name")
注解匹配。
以下示例显示了如何使用 findByLastName
查询:
curl http://localhost:8080/people/search/findByLastName?name=Baggins
{
"_embedded" : {
"persons" : [ {
"firstName" : "Frodo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/people/1"
}
}
} ]
}
}
因为我们已将其定义为在代码中返回 List<Person>
,所以它将返回所有结果。如果已将其定义为仅返回 Person
,则它将选择要返回的 Person
对象之一。由于这可能是不可预测的,因此对于可能返回多个条目的查询,我们可能不想这样做。
我还可以发出 PUT
、PATCH
和 DELETE
REST 调用来分别替换、更新或删除现有记录。以下示例使用 PUT
调用:
curl -X PUT -H "Content-Type:application/json" -d '{ "firstName": "Bilbo", "lastName": "Baggins" }' http://localhost:8080/people/1
curl http://localhost:8080/people/1
{
"firstName" : "Bilbo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/people/1"
}
}
}
下面示例使用 PATCH
调用:
curl -X PATCH -H "Content-Type:application/json" -d '{ "firstName": "Bilbo Jr." }' http://localhost:8080/people/1
curl http://localhost:8080/people/1
{
"firstName" : "Bilbo Jr.",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/people/1"
}
}
}
PUT
替换整个记录。未提供的字段将替换为null
。我们可以使用PATCH
更新项的子集。
curl -X DELETE http://localhost:8080/people/1
curl http://localhost:8080/people
{
"_links" : {
"search" : {
"href" : "http://localhost:8080/people/search"
}
}
}
该超媒体驱动接口的一个方便方面是,我们可以使用 curl
(或您喜欢的任何REST客户端)来发现所有 RESTful 端点。我们无需与客户交换正式契约或接口文件。
恭喜你!我们已经开发了具有基于超媒体的 RESTful 前端和基于 Pivotal GemFire 后端的应用。
以下指南也可能会有所帮助:
想看指南的其他内容?请访问该指南的所属专栏:《Spring 官方指南》