©2012-2015原作者
只要您不对这些副本收取任何费用,并且进一步规定,每个副本都包含本版权声明,无论是以印刷版还是电子版分发,本文档的副本可供您自己使用并分发给他人。 |
- 前言
- 参考文献
- 附录
前言
依赖关系
由于个人Spring Data模块的初始日期不同,其中大多数都携带不同的主要和次要版本号。找到兼容版本的最简单的方法是依靠Spring Data发布Train BOM发布的兼容版本。在Maven项目中,您将在POM的<dependencyManagement />
部分声明此依赖关系:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-releasetrain</artifactId>
<version>${release-train}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
目前的发布火车版本是Ingalls-SR6
。火车名称按字母顺序升序,目前可用的火车名称列在这里。版本名称遵循以下模式:${name}-${release}
其中release可以是以下之一:
-
BUILD-SNAPSHOT
- 当前快照 -
M1
,M2
等 - 里程碑 -
RC1
,RC2
等 - 发布候选人 -
RELEASE
- GA发布 -
SR1
,SR2
等 - 服务版本
我们的Spring Data示例存储库中可以找到使用BOMs的一个工作示例。如果这样就可以在<dependencies />
块中声明要使用的Spring Data模块,而没有版本。
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<dependencies>
2.1.使用Spring Boot的依赖关系管理
Spring Boot已经为您选择了最新版本的Spring Data模块。如果您想要升级到较新版本,只需将属性spring-data-releasetrain.version
配置为您要使用的列车名称和迭代。
参考文献
介绍
REST web服务已成为在web上应用程序集成的首选方式。在其核心中,REST定义了系统由客户端交互的资源组成。这些资源以超媒体驱动的方式实现。Spring MVC为构建这些服务提供了坚实的基础。但是,对于多域对象系统,即使实施REST web服务的最简单原则也可能相当乏味,并且导致大量样板代码。
Spring Data REST构建在Spring Data repositories之上,并自动将其导出为REST资源。它利用超媒体来允许客户端查找存储库暴露的功能,并将这些资源自动集成到相关的超媒体功能中。
4.入门
4.1.介绍
Spring Data REST本身就是一个Spring MVC应用程序,它的设计方式应该是尽可能少的集成到现有的Spring MVC应用程序中。现有的(或将来的)服务层可以与Spring Data REST一起运行,只有较小的考虑。
4.2.将Spring Data REST添加到Spring Boot项目
如果您正在构建Spring Boot应用程序,最简单的方法就是开始。这是因为Spring Data REST同时具有启动器以及自动配置。
dependencies {
...
compile("org.springframework.boot:spring-boot-starter-data-rest")
...
}
<dependencies>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
...
</dependencies>
如果您使用Spring Boot Gradle插件或Spring Boot Maven插件,则不必提供版本号。 |
使用Spring Boot时,会自动配置Spring Data REST。
4.3.将Spring Data REST添加到Gradle项目
要将Spring Data REST添加到基于Gradle的项目中,请将spring-data-rest-webmvc
工件添加到编译时依赖项中:
dependencies {
… other project dependencies
compile("org.springframework.data:spring-data-rest-webmvc:2.6.6.RELEASE")
}
4.4.将Spring Data REST添加到Maven项目中
要将Spring Data REST添加到基于Maven的项目中,请将spring-data-rest-webmvc
工件添加到编译时依赖项中:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-webmvc</artifactId>
<version>2.6.6.RELEASE</version>
</dependency>
4.5.配置Spring Data REST
要在现有的Spring MVC应用程序旁边安装Spring Data REST,您需要包含适当的MVC配置。Spring Data REST配置在名为RepositoryRestMvcConfiguration
的类中定义,该类可以导入到应用程序配置中。
如果您使用Spring Boot的自动配置,则此步骤是不必要的。当您将spring-boot-starter-data-rest和依赖关系列表包含起来时,Spring Boot将自动启用Spring Data REST ,您的应用程序已被@SpringBootApplication 或@EnableAutoConfiguration 标记。
|
要自定义配置,请注册RepositoryRestConfigurer
(或扩展RepositoryRestConfigurerAdapter
)并实现或覆盖与您的用例相关的configure…
方法。
确保您还为您使用的商店配置Spring Data repositories。有关详细信息,请参阅相应的Spring Data模块的参考文档。
4.6.Spring Data REST的基本设置
4.6.1.默认情况下哪些存储库被暴露?
Spring Data REST使用RepositoryDetectionStrategy
来确定是否将资源库导出为REST资源。以下策略(RepositoryDiscoveryStrategies
的枚举值)可用:
名称 | 描述 |
---|---|
|
暴露所有公共存储库接口,但考虑 |
|
独立于类型可见性和注释来暴露所有存储库。 |
|
只有使用 |
|
仅公开注释的公共资料库。 |
4.6.2.更改基本URI
默认情况下,Spring Data REST以根URI“/”提供REST资源。有多种方法来改变基本路径。
使用Spring Boot 1.2+,所有需要的是application.properties
中的一个属性:
spring.data.rest.basePath=/api
使用Spring Boot 1.1或更早版本,或者如果您不使用Spring Boot,只需执行以下操作:
@Configuration
class CustomRestMvcConfiguration {
@Bean
public RepositoryRestConfigurer repositoryRestConfigurer() {
return new RepositoryRestConfigurerAdapter() {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setBasePath("/api");
}
};
}
}
或者只是将RepositoryRestConfigurer
的自定义实现注册为Spring bean,并确保它被组件扫描所拾取:
@Component
public class CustomizedRestMvcConfiguration extends RepositoryRestConfigurerAdapter {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setBasePath("/api");
}
}
这两种方法都会将基本路径更改为/api
。
4.6.3.更改其他Spring Data REST属性
有很多属性可以改变:
名称 | 描述 |
---|---|
基本路径 |
Spring Data REST的根URI |
defaultPageSize |
更改在单个页面中投放的默认项目数量 |
maxpagesize可 |
更改单个页面中的最大项目数量 |
pageParamName |
更改用于选择页面的查询参数的名称 |
limitParamName |
更改页面中要显示的项目的查询参数的名称 |
sortParamName |
更改用于排序的查询参数的名称 |
defaultMediaType |
更改默认介质类型以在没有指定时使用 |
returnBodyOnCreate |
如果在创建一个新实体时应该返回一个主体,那么请改变 |
returnBodyOnUpdate |
如果在更新一个实体时应该返回一个机构,则更改 |
4.7.启动应用程序
此时,您还必须配置密钥数据存储。
Spring Data REST正式支持:
以下是一些入门指南,帮助您快速起床和运行:
这些链接指南介绍了如何为相关数据存储添加依赖关系,配置域对象和定义存储库。
您可以将应用程序作为Spring Boot应用程序(上面显示的链接)运行或将其配置为经典的Spring MVC应用程序。
一般来说,Spring Data REST不会向给定的数据存储添加功能。这意味着根据定义,它应该适用于支持Repository编程模型的任何Spring Data项目。上面列出的数据存储只是我们编写的集成测试来验证的。 |
从这一点上,您可以自由地使用各种选项自定义Spring Data REST。
5. Repository资源
5.1.基本面
Spring Data REST的核心功能是导出Spring Data repositories的资源。因此,查看和潜在调整的核心工件可以自定义导出工作的方式是存储库接口。假设以下存储库接口:
public interface OrderRepository extends CrudRepository<Order, Long> { }
对于此存储库,Spring Data REST在/orders
显示集合资源。该路径是从被管理的域类的未资本化,多元化,简单的类名派生的。它还为URI模板/orders/{id}
下的存储库管理的每个项目公开了一个项目资源。
5.1.1.默认状态码
对于暴露的资源,我们使用一组默认状态代码:
-
200 OK
- 适用于纯粹的GET
请求。 -
201 Created
- 针对创建新资源的POST
和PUT
请求。 -
204 No Content
- 对于PUT
,PATCH
和DELETE
请求,如果配置设置为不返回资源更新的响应体(RepositoryRestConfiguration.returnBodyOnUpdate
)。如果配置值设置为包含PUT
的响应,则将返回200 OK
进行更新,PUT
将为PUT
创建的资源返回201 Created
。
如果配置值(RepositoryRestConfiguration.returnBodyOnUpdate
和RepositoryRestConfiguration.returnBodyCreate)
显式设置为null),则会使用HTTP Accept标头的存在来确定响应代码。
5.1.2.资源可发现性
HATEOAS的核心原则是通过发布指向可用资源的链接来发现资源。有几个竞争的事实上的标准如何表示JSON中的链接。默认情况下,Spring Data REST使用HAL来呈现响应。HAL定义要包含在返回文档的属性中的链接。
资源发现从应用程序的顶层开始。通过向部署了Spring Data REST应用程序的根URL发出请求,客户端可以从返回的JSON对象中提取一组链接,这些链接表示客户端可用的下一级资源。
例如,要发现应用程序根目录中有哪些资源可用,请向根URL发出HTTP GET
:
curl -v http://localhost:8080/
< HTTP/1.1 200 OK
< Content-Type: application/hal+json
{ "_links" : {
"orders" : {
"href" : "http://localhost:8080/orders"
},
"profile" : {
"href" : "http://localhost:8080/api/alps"
}
}
}
结果文档的属性本身是由代表HAL中指定的嵌套链接对象的关系类型的键组成的。
有关配置文件链接的更多详细信息,请参阅应用程序级配置文件语义(ALPS)。 |
5.2.收集资源
Spring Data REST公开了在导出的存储库正在处理的域类的未资本化多元化版本之后命名的集合资源。资源和路径的名称都可以使用存储库接口上的@RepositoryRestResource
进行自定义。
5.2.1.支持的HTTP方法
集合资源支持GET
和POST
。所有其他HTTP方法都会导致405 Method Not Allowed
。
得到
通过其findAll(…)
方法返回库服务器的所有实体。如果存储库是分页存储库,我们将在必要时包括分页链接和其他页面元数据。
参数
如果存储库具有分页功能,则该资源需要以下参数:
-
page
- 要访问的页码(索引为0,默认为0)。 -
size
- 请求的页面大小(默认为20)。 -
sort
- 格式为($propertyname,)+[asc|desc]
?的排序指令的集合。
相关资源
-
search
- 如果后台存储库公开查询方法,则为搜索资源。
5.3.项目资源
Spring Data REST将个别收集项目的资源公开为收集资源的子资源。
5.3.1.支持的HTTP方法
项目资源通常支持GET
,PUT
,PATCH
和DELETE
,除非明确的配置阻止(详见下文)。
5.5.搜索资源
搜索资源返回由存储库公开的所有查询方法的链接。可以使用方法声明中的@RestResource
来修改查询方法资源的路径和名称。
5.5.1.支持的HTTP方法
由于搜索资源是只读资源,它仅支持GET
。
得到
返回指向各个查询方法资源的链接列表
相关资源
对于在存储库中声明的每个查询方法,我们公开了查询方法资源。200新X-45旗新新新新新旗新旗200 200 200 200 200 200 200:200 200 200旗新StrEC新旗新200新新200 200 200 200 200 200 200 CEEC 200 X-
分页和排序
这将记录Spring Data REST使用Spring Data Repository寻呼和排序抽象。要了解这些功能,请参阅Spring Data您正在使用的Repository实现的文档。
6.1.分页
而不是从大型结果集中返回所有内容,Spring Data REST会识别一些会影响页面大小和起始页码的URL参数。
如果您扩展PagingAndSortingRepository<T, ID>
并访问所有实体的列表,您将获得前20个实体的链接。要将页面大小设置为任何其他数字,请添加size
参数:
HTTP://本地主机:8080元/人/尺寸= 5
这将将页面大小设置为5。
要使用您自己的查询方法中的分页,您需要更改方法签名以接受其他Pageable
参数并返回Page
而不是List
。例如,以下查询方法将导出到/people/search/nameStartsWith
并支持分页:
@RestResource(path = "nameStartsWith", rel = "nameStartsWith")
public Page findByNameStartsWith(@Param("name") String name, Pageable p);
Spring Data REST出口商将会识别返回的Page
,并在响应的正文中给出结果,就像使用非分页响应一样,但是会向资源添加额外的链接来表示前一个和下一页数据。
6.1.1.上一个和下一个链接
每个寻呼响应将返回链接到基于使用IANA定义链接关系当前页面上的结果的前面和后面的页面prev
和next
。不过,如果您目前在第一页,则不会显示prev
链接。结果的最后一页也是如此:不会呈现next
链接。
看下面的例子,我们将页面大小设置为5:
curl localhost:8080 / people?size = 5
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons{&sort,page,size}", (1)
"templated" : true
},
"next" : {
"href" : "http://localhost:8080/persons?page=1&size=5{&sort}", (2)
"templated" : true
}
},
"_embedded" : {
... data ...
},
"page" : { (3)
"size" : 5,
"totalElements" : 50,
"totalPages" : 10,
"number" : 0
}
}
在顶部,我们看到_links
:
1 | 这个self 链接提供了一些选项的整个集合 |
2 | 假设相同的页面大小,此next 链接指向下一页。 |
3 | 底部是关于页面设置的额外数据,包括页面的大小,总元素,总页面和当前查看的页面编号。 |
在命令行中使用curl这样的工具时,如果您的语句中有“&”,则使用引号将整个URI包起来。 |
同样重要的是注意到self
和next
URI实际上是URI模板。他们不仅接受size
,也接受page
,sort
作为可选标志。
如上所述,在HAL文档的底部,是关于该页面的详细信息的集合。这些额外的信息使您很容易地配置UI工具,如滑块或指示器,以反映用户正在查看数据的整体位置。例如,上面的文档显示我们正在查看第一页(页码为0为第一页)。
如果我们遵循next
链接会怎么样?
$ curl“http:// localhost:8080 / persons?page = 1&size = 5”
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons{&sort,projection,page,size}",
"templated" : true
},
"next" : {
"href" : "http://localhost:8080/persons?page=2&size=5{&sort,projection}", (1)
"templated" : true
},
"prev" : {
"href" : "http://localhost:8080/persons?page=0&size=5{&sort,projection}", (2)
"templated" : true
}
},
"_embedded" : {
... data ...
},
"page" : {
"size" : 5,
"totalElements" : 50,
"totalPages" : 10,
"number" : 1 (3)
}
}
这看起来非常相似,除了以下差异:
1 | 现在next 链接指向另一个页面,表明它与self 链接相对。 |
2 | 现在出现prev 链接,给我们前一页的路径。 |
3 | 现在的号码现在是1(表示第二页)。 |
此功能使得将屏幕上的可选按钮映射到这些超级媒体控件变得非常容易,因此可以轻松地为UI体验导航功能,而无需对URI进行硬编码。实际上,用户可以从页面大小列表中进行选择,动态地更改所提供的内容,而无需重写顶部或底部的next
和“prev”控件。
6.2.排序
Spring Data REST识别将使用Repository排序支持的排序参数。
要根据特定属性对结果进行排序,请添加一个sort
URL参数,其中包含要对结果进行排序的属性的名称。您可以通过将,
添加到属性名称加上asc
或desc
来控制排序方向。以下将使用PersonRepository
定义的所有Person
实体的findByNameStartsWith
查询方法,其名称以字母“K”开头,并添加排序数据,将结果命令在name
属性降序:
curl -v“http:// localhost:8080 / people / search / nameStartsWith?name = K&sort = name,desc”
要对多个属性进行结果排序,请继续添加尽可能多的sort=PROPERTY
参数。它们将按照它们在查询字符串中显示的顺序添加到Pageable
。结果可以通过顶级和嵌套属性进行排序。使用属性路径表示法表达嵌套的排序属性。不支持按可链接关联排序(即资源到顶级资源)。
7.域对象表示
7.1.对象映射
Spring Data REST返回与HTTP请求中指定的请求的Accept
类型对应的域对象的表示。
目前,只支持JSON表示。今后可以通过添加适当的转换器并使用适当的内容类型更新控制器方法来支持其他表示类型。
有时,Spring Data REST的ObjectMapper的行为已被特别配置为使用可以将域对象转换为链接并重新返回的智能串行器,可能无法正确处理您的域模型。有很多方法可以构建您的数据,您可能会发现自己的域模型没有被正确地转换为JSON。在这些情况下,有时不习惯以通用方式尝试和支持复杂的域模型。有时,根据复杂性,甚至不可能提供通用的解决方案。
7.1.1.将自定义(de)序列化程序添加到Jackson的ObjectMapper中
为了适应用例的最大百分比,Spring Data REST非常难以正确地呈现对象图。它将尝试将非托管bean作为普通POJO进行序列化,并且将尝试创建与需要的托管bean的链接。但是,如果您的域名模型不容易读取或编写纯粹的JSON,则可能需要使用自己的自定义类型映射和(de)序列化来配置Jackson的ObjectMapper。
抽象班注册
当您在域模型中使用抽象类(或接口)时,您可能需要钩住的一个关键配置点。杰克逊默认不知道为接口创建什么实现。举个例子:
@Entity
public class MyEntity {
@OneToMany
private List<MyInterface> interfaces;
}
在默认配置中,杰克逊不知道在向出口商发布新数据时要实例化什么类。这是您需要通过注释来告诉杰克逊,或者更干净地通过使用Module
注册类型映射。
要将您自己的Jackson配置添加到Spring Data REST使用的ObjectMapper
中,请覆盖configureJacksonObjectMapper
方法。该方法将传递一个具有特殊模块的ObjectMapper
实例来处理“PersistentEntity”的序列化和反序列化。您也可以注册自己的模块,如下例所示。
@Override
protected void configureJacksonObjectMapper(ObjectMapper objectMapper) {
objectMapper.registerModule(new SimpleModule("MyCustomModule") {
@Override
public void setupModule(SetupContext context) {
context.addAbstractTypeResolver(
new SimpleAbstractTypeResolver().addMapping(MyInterface.class,
MyInterfaceImpl.class)
);
}
});
}
一旦您访问了您的Module
中的SetupContext
对象,您可以做各种各样的酷事来配置杰克逊的JSON映射。您可以阅读更多关于Module
在杰克逊维基上的工作:http : //wiki.fasterxml.com/JacksonFeatureModules
为域类型添加自定义序列化程序
如果要以特殊方式(de)序列化域类型,您可以使用Jackson的ObjectMapper
注册自己的实现,并且Spring Data REST导出器将正确地透明地处理这些域对象。要从setupModule
方法实现中添加序列化程序,请执行以下操作:
@Override
public void setupModule(SetupContext context) {
SimpleSerializers serializers = new SimpleSerializers();
SimpleDeserializers deserializers = new SimpleDeserializers();
serializers.addSerializer(MyEntity.class, new MyEntitySerializer());
deserializers.addDeserializer(MyEntity.class, new MyEntityDeserializer());
context.addSerializers(serializers);
context.addDeserializers(deserializers);
}
8.预测和摘录
Spring Data REST显示您要导出的域模型的默认视图。但有时候,由于各种原因,您可能需要更改该模型的视图。在本节中,您将学习如何定义预测和摘录,以便简化和简化资源查看。
8.1.预测
看下面的域名模型:
@Entity
public class Person {
@Id @GeneratedValue
private Long id;
private String firstName, lastName;
@OneToOne
private Address address;
…
}
这个Person
有几个属性:
-
id
是主键 -
firstName
和lastName
是数据属性 -
address
是指向另一个域对象的链接
现在假设我们创建一个相应的存储库如下:
interface PersonRepository extends CrudRepository<Person, Long> {}
默认情况下,Spring Data REST将导出该域对象,包括其所有属性。firstName
和lastName
将作为原始数据对象导出。有关address
属性的两个选项。一个选择是也定义一个这样的Address
对象的仓库:
interface AddressRepository extends CrudRepository<Address, Long> {}
在这种情况下,Person
资源会将address
属性显示为相应的Address
资源的URI。如果我们在系统中查找“Frodo”,我们可以期待看到如下的HAL文档:
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/1"
},
"address" : {
"href" : "http://localhost:8080/persons/1/address"
}
}
}
还有另一条路线。如果Address
域对象没有自己的存储库定义,Spring Data REST将内联Person
资源内的数据域。
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"address" : {
"street": "Bag End",
"state": "The Shire",
"country": "Middle Earth"
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/1"
}
}
}
但是,如果您不想要address
详细信息呢?再次,默认情况下,Spring Data REST将导出其所有属性(id
除外)。您可以通过定义一个或多个投影来为REST服务的消费者提供替代方案。
@Projection(name = "noAddresses", types = { Person.class }) (1)
interface NoAddresses { (2)
String getFirstName(); (3)
String getLastName(); (4)
}
此投影具有以下细节:
1 | @Projection 注释标记为投影。name 属性提供投影的名称,您将看到如何使用投影。types 属性将此投影定位为仅适用于Person 对象。 |
2 | 它是一个Java接口,使其声明性。 |
3 | 它导出firstName 。 |
4 | 它导出lastName 。 |
NoAddresses
投影只有firstName
和lastName
的getter,这意味着它不会提供任何地址信息。假设您有Address
资源的独立存储库,则Spring Data REST的默认视图略有不同,如下所示:
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/1{?projection}", (1)
"templated" : true (2)
},
"address" : {
"href" : "http://localhost:8080/persons/1/address"
}
}
}
1 | 此资源有一个新选项{?projection} 。 |
2 | self URI是URI模板。 |
要将投影应用于资源,请查找http://localhost:8080/persons/1?projection=noAddresses
。
提供给projection 查询参数的值与@Projection(name = "noAddress") 中指定的值相同。它与投影界面的名称无关。
|
可以有多个投影。
访问投影以查看您可以尝试的示例项目。 |
Spring Data REST如何找到投影定义?
-
与实体定义(或其中一个子包)相同的包中找到的任何
@Projection
接口都已注册。 -
您可以通过
RepositoryRestConfiguration.getProjectionConfiguration().addProjection(…)
手动注册。
在任一情况下,与您的投影的界面必须具有@Projection
注释。
8.1.1.查找现有预测
Spring Data REST公开了应用级配置文件语义(ALPS)文档,一种微元数据格式。要查看ALPS元数据,请按照根资源公开的profile
链接。如果您向下导航至Person
资源(即/alps/persons
)的ALPS文档,则可以找到有关Person
资源的许多详细信息。将列出投影以及关于GET
REST转换的详细信息,如下所示:
{ …
"id" : "get-person", (1)
"name" : "person",
"type" : "SAFE",
"rt" : "#person-representation",
"descriptors" : [ {
"name" : "projection", (2)
"doc" : {
"value" : "The projection that shall be applied when rendering the response. Acceptable values available in nested descriptors.",
"format" : "TEXT"
},
"type" : "SEMANTIC",
"descriptors" : [ {
"name" : "noAddresses", (3)
"type" : "SEMANTIC",
"descriptors" : [ {
"name" : "firstName", (4)
"type" : "SEMANTIC"
}, {
"name" : "lastName", (4)
"type" : "SEMANTIC"
} ]
} ]
} ]
},
…
1 | ALPS文档的这一部分显示了有关GET 和Person 资源的详细信息。 |
2 | 更进一步的是projection 选项。 |
3 | 进一步下来,您可以看到列出的投影noAddresses 。 |
4 | 此投影提供的实际属性包括firstName 和lastName 。 |
投影定义将被提取并提供给客户,如果它们是:
|
8.1.2.隐藏数据
到目前为止,您已经看到如何使用投影来减少向用户呈现的信息。投影也可以带来通常看不见的数据。例如,Spring Data REST将忽略标有@JsonIgnore
注释的字段或getter。看下面的域对象:
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String name;
@JsonIgnore private String password; (1)
private String[] roles;
…
1 | Jackson的@JsonIgnore 用于阻止password 字段序列化为JSON。 |
此User
类可用于存储用户信息以及与Spring Security的集成。如果您创建UserRepository
,则通常会导出password
字段。不好!在这个例子中,我们通过在password
字段上应用Jackson的@JsonIgnore
来防止这种情况发生。
如果@JsonIgnore 在字段的相应的getter函数上,Jackson也不会将该字段序列化为JSON。
|
然而,预测介绍了仍然服务于这一领域的能力。可以创建如下投影:
@Projection(name = "passwords", types = { User.class })
interface PasswordProjection {
String getPassword();
}
如果创建和使用这样的投影,它将侧面放置在User.password
上的@JsonIgnore
指令。
这个例子可能看起来有点诡异,但是可以通过更丰富的域模型和许多预测来意外泄露这些细节。由于Spring Data REST无法辨别这些数据的敏感度,所以由开发人员来避免这种情况。 |
投影还可以生成虚拟数据。想像你有以下实体定义:
@Entity
public class Person {
...
private String firstName;
private String lastName;
...
}
您可以创建一个将这两个数据字段组合在一起的投影,如下所示:
@Projection(name = "virtual", types = { Person.class })
public interface VirtualProjection {
@Value("#{target.firstName} #{target.lastName}") (1)
String getFullName();
}
1 | Spring的@Value 注释让我们插入一个采用目标对象的SpEL表达式,并将其firstName 和lastName 属性拼接成一个只读fullName 。 |
8.2.摘录
摘录是自动应用于资源集合的投影。举个例子,你可以改变PersonRepository
如下:
@RepositoryRestResource(excerptProjection = NoAddresses.class)
interface PersonRepository extends CrudRepository<Person, Long> {}
当Person
资源嵌入到集合或相关资源中时,这将指示Spring Data REST使用NoAddresses
投影。
摘录投影不会自动应用于单个资源。他们必须刻意应用。摘录预测旨在提供收集数据的默认预览,但不提取单个资源。看看为什么没有为Spring Data REST项目资源自动应用摘录投影?讨论这个问题。 |
除了更改默认渲染外,摘录还有其他渲染选项,如下所示。
8.3.摘录常用数据
组合域对象时,会出现REST服务的常见情况。例如,Person
存储在一个表中,其相关的Address
存储在另一个表中。默认情况下,Spring Data REST将作为客户端必须导航的URI提供该人的address
。但是,如果消费者常常获取这些额外的数据,那么可以选择一个摘录投影,并将这些额外的数据放在一起,从而节省了额外的GET
。为了做到这一点,我们来定义另一个摘录:
@Projection(name = "inlineAddress", types = { Person.class }) (1)
interface InlineAddress {
String getFirstName();
String getLastName();
Address getAddress(); (2)
}
1 | 这个投影被命名为inlineAddress 。 |
2 | 该投影增加了getAddress ,返回Address 字段。当在投影中使用时,会导致信息内联。 |
我们可以将其插入PersonRepository
定义如下:
@RepositoryRestResource(excerptProjection = InlineAddress.class)
interface PersonRepository extends CrudRepository<Person, Long> {}
这将导致HAL文档显示如下:
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"address" : { (1)
"street": "Bag End",
"state": "The Shire",
"country": "Middle Earth"
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/1"
},
"address" : { (2)
"href" : "http://localhost:8080/persons/1/address"
}
}
}
这应该是你现在看到的结果。
1 | 直接内联address 数据,因此您无需浏览即可。 |
2 | 仍然提供了到Address 资源的链接,使其仍然可以导航到自己的资源。 |
为存储库配置@RepositoryRestResource(excerptProjection=…) 会更改默认行为。如果您已经发布了这个消息,这可能会导致服务消费者的变化。谨慎使用。
|
9.与标题有条件的操作
本节将介绍Spring Data REST如何使用标准的HTTP标头来提高性能,对操作进行条件化,并轻松地为更复杂的前端做出贡献。
9.1.ETag,If-Match和If-None-Match头
该ETag头提供了一种方法来标记资源。这可以防止客户端相互覆盖,同时也可以减少不必要的呼叫。
public class Sample {
@Version Long version; (1)
Sample(Long version) {
this.version = version;
}
}
1 | Spring Data Commons的@Version 注释将此字段标记为版本标记。 |
该POJO由Spring Data REST提供为REST资源时,将具有带有版本字段值的ETag
标头。
如果我们提供这样的If-Match
标题,我们可以有条件地PUT
,PATCH
或DELETE
该资源:
curl -v -X PATCH -H'If-Match:<以前的ETag的值>'...
只有当资源的当前ETag状态与此If-Match
头匹配时,才能执行操作。这样可以防止客户彼此踩踏。两个不同的客户端可以获取资源并具有相同的ETag。如果一个客户端更新资源,它将在响应中获取一个新的ETag。但是第一个客户端仍然有旧的头。如果该客户端尝试使用If-Match
标头进行更新,则更新将失败,因为它们不再匹配。相反,该客户端将收到要发回的HTTP 412 Precondition Failed
消息。然而,客户端可以赶上,但是是必要的。
术语“版本”可以使用不同的数据存储,甚至在应用程序中使用不同的语义来处理不同的语义。Spring Data REST有效地委托给数据存储的元模型,以辨别字段是否被版本化,如果是,则只允许列表的更新如果ETag匹配。 |
所述如果-None-Match头提供了一种替代。If-None-Match
代替条件更新,允许进行条件查询。
curl -v -H'If-None-Match:<value of previous etag>'...
此命令(默认情况下)执行GET
。Spring Data REST将在执行GET时检查If-None-Match
标题。如果头衔匹配ETag,它将不会完成任何改变,而不是发送资源的副本,而是发回HTTP 304 Not Modified
状态码。从语法上看,它显示为“如果这个提供的头值与服务器端版本不匹配,那么给我发送整个资源,否则不要给我任何东西。”
该POJO来自于基于ETag的单元测试,因此在应用程序代码中没有预期的@Entity (JPA)或@Document (MongoDB)注释。它只关注如何使用@Version 的字段导致一个ETag头。
|
9.2.if-Modified-Since头
该的If-Modified-Since头提供了一种方法来检查,如果资源已经自上次请求更新,以避免重新发送相同的数据。
@Document
public class Receipt {
public @Id String id;
public @Version Long version;
public @LastModifiedDate Date date; (1)
public String saleItem;
public BigDecimal amount;
}
1 | Spring Data Commons的@LastModifiedDate 注释允许以多种格式捕获此信息(JodaTime的DateTime ,旧版Java Date 和Calendar ,JDK8日期/时间类型以及long / Long )。 |
使用此字段,Spring Data REST将返回一个Last-Modified
标题,如下所示:
最后修改:Wed,24 Jun 2015 20:28:15 GMT
该值可以被捕获并用于后续查询,以避免在没有更新的时候提取相同的数据两次。
卷曲-H“If-Modified-Since:Wed,24 Jun 2015 20:28:15 GMT”...
使用这个简单的命令,您要求仅在此时才更改资源。如果是这样,您将得到一个修改后的Last-Modified
头来更新客户端。如果没有,您将收到HTTP 304 Not Modified
状态码。
标题格式完美,可以发送给将来的查询。
不要混合和匹配头值与不同的查询。结果可能是灾难性的。当您请求完全相同的URI和参数时,只能使用头值。 |
9.3.建立一个更有效率的前端
ETags与If-Match
和If-None-Match
标题相结合,使您能够构建对消费者的数据计划和移动电池寿命更加友好的前端。
-
识别需要锁定并添加版本属性的实体。HTML5很好地支持data- *属性,因此将其存储在DOM中,例如
data-etag
属性。 -
识别可以通过跟踪最近的更新而获益的动机。获取这些资源时,请将
Last-Modified
存储在DOM(data-last-modified
)中)。 -
在获取资源时,也可以将自身URI插入DOM节点(也许
data-uri
或data-self
),因此很容易返回资源。 -
调整
PUT
/PATCH
/DELETE
操作以使用If-Match
并处理HTTP412 Precondition Failed
状态代码。 -
调整
GET
操作以使用If-None-Match
,If-Modified-Since
,并处理HTTP304 Not Modified
状态码。
通过将ETags
和Last-Modified
值嵌入到您的DOM中(或者可能在本地移动应用程序的其他位置),您可以通过不再检索相同的东西来减少数据/电池的消耗。您还可以避免与其他客户端冲突,而应在需要调和差异时收到警报。
因此,只要稍微调整您的前端和一些实体级编辑,后端将提供时间敏感的细节,您可以在构建客户友好的客户时获得现金。
10.验证
在Spring Data REST中注册一个Validator
实例有两种方式:通过bean名称进行连线或手动注册验证器。对于大多数情况,简单的bean名称前缀样式就足够了。
为了告诉Spring Data REST你想要一个特定的Validator
分配给一个特定的事件,你只需要在你感兴趣的事件之前加上bean名称,例如,验证Person
类的实例在新的保存到存储库之前,您将在ApplicationContext
中声明一个具有bean名称“beforeCreatePersonValidator”的Validator<Person>
的实例。由于前缀“beforeCreate”与已知的Spring Data REST事件匹配,因此该验证器将连接到正确的事件。
10.1.手动分配验证器
如果您不想使用bean名称前缀方法,那么您只需要在正确的事件之后,注册一个验证器的实例,该bean的作业是调用验证器。在您实现RepositoryRestConfigurer
或子类Spring Data REST RepositoryRestConfigurerAdapter
的子类的配置中,覆盖configureValidatingRepositoryEventListener
方法并在ValidatingRepositoryEventListener
上调用addValidator
,将要验证的事件传递给被触发和验证器的一个实例。
@Override
protected void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener v) {
v.addValidator("beforeSave", new BeforeSaveValidator());
}
11. Events
在整个实体的整个过程中,REST出口商发布了八个不同的事件。那些是:
-
BeforeCreateEvent
-
AfterCreateEvent
-
BeforeSaveEvent
-
AfterSaveEvent
-
BeforeLinkSaveEvent
-
AfterLinkSaveEvent
-
BeforeDeleteEvent
-
AfterDeleteEvent
11.1.编写ApplicationListener
有一个可以子类的抽象类,它可以监听这些类型的事件,并根据事件类型调用适当的方法。您只需覆盖您感兴趣的事件的方法。
public class BeforeSaveEventListener extends AbstractRepositoryEventListener {
@Override
public void onBeforeSave(Object entity) {
... logic to handle inspecting the entity before the Repository saves it
}
@Override
public void onAfterDelete(Object entity) {
... send a message that this entity has been deleted
}
}
然而,使用这种方法需要注意的一点是,它不会根据实体的类型进行区分。你必须自己检查。
11.2.编写一个注释处理程序
另一种方法是使用注释处理程序,该处理程序根据域类型过滤事件。
要声明一个处理程序,创建一个POJO并在其上放置@RepositoryEventHandler
注释。这告诉BeanPostProcessor
这个类需要检查处理程序的方法。
一旦找到了一个带有这个注释的bean,它会遍历暴露的方法,并查找与你感兴趣的事件相对应的注释。例如,为了在不同种类的域类型的注释POJO中处理`BeforeSaveEvent`,你可以这样定义你的课程:
@RepositoryEventHandler (1)
public class PersonEventHandler {
@HandleBeforeSave
public void handlePersonSave(Person p) {
// … you can now deal with Person in a type-safe way
}
@HandleBeforeSave
public void handleProfileSave(Profile p) {
// … you can now deal with Profile in a type-safe way
}
}
1 | 可以通过使用@RepositoryEventHandler(Person.class) 来缩小此处理程序应用的类型。 |
您感兴趣的事件的域类型是根据注释方法的第一个参数的类型确定的。
要注册您的事件处理程序,请使用Spring的@Component
定型之一来标记该类,以便它可以由@SpringBootApplication
或@ComponentScan
提取。或者在您的ApplicationContext
中声明一个注释bean的实例。然后在RepositoryRestMvcConfiguration
中创建的BeanPostProcessor
将检查bean的处理程序并将其连接到正确的事件。
@Configuration
public class RepositoryConfiguration {
@Bean
PersonEventHandler personEventHandler() {
return new PersonEventHandler();
}
}
Spring Data REST事件是自定义的Spring应用程序事件。默认情况下,Spring事件是同步的,除非它们跨越边界重新发布(如发布WebSocket事件或跨线程)。 |
整合
本节将详细介绍与Spring Data REST组件集成的各种方法,无论是使用Spring Data REST或其他方式的Spring应用程序。
12.1.程式化链接
有时您需要在您自己定制的Spring MVC控制器中添加导出资源的链接。有三个基本的链接级别可用:
-
手动组装链接
-
使用Spring HATEOAS的LinkBuilder与
linkTo()
,slash()
等 -
使用Spring Data REST实现RepositoryEntityLinks。
第一个建议是可怕的,应该不惜一切代价避免。它使您的代码变得脆弱和高风险。第二个在创建与其他手写的Spring MVC控制器的链接时很方便。最后一个,您稍后会看到,有助于查找由Spring Data REST导出的资源链接。
假设您已经配置了代码以使用Spring的自动布线,
public class MyWebApp {
private RepositoryEntityLinks entityLinks;
@Autowired
public MyWebApp(RepositoryEntityLinks entityLinks) {
this.entityLinks = entityLinks;
}
}
...然后可以使用以下操作:
方法 | 描述 |
---|---|
|
提供指向该类型的收集资源的链接。 |
|
提供一个链接到一个资源。 |
|
提供到分页资源的链接。 |
|
提供由相应的存储库公开的所有finder方法的链接列表。 |
|
通过rel提供finder链接,即finder的名称。 |
所有基于搜索的链接都支持分页和排序的额外参数。结算RepositoryEntityLinks的具体细节。还有linkFor(Class<?> type) ,但是返回一个Spring HATEOAS LinkBuilder ,返回到较低级别的API。尝试使用其他的。
|
元数据
本节详细介绍了基于Spring Data REST的应用程序提供的各种元数据形式。
13.1.应用级配置文件语义(ALPS)
ALPS是用于定义应用程序级语义的简单描述的数据格式,类似于HTML微格式的复杂性。可以使用ALPS文档作为配置文件来解释具有应用无关媒体类型(如HTML,HAL,Collection + JSON,Siren等)的文档的应用程序语义。这增加了介质类型之间的配置文件的可重用性。
http://tools.ietf.org/html/draft-amundsen-richardson-foster-alps-00
Spring Data REST为每个导出的存储库提供一个ALPS文档。它包含有关RESTful转换以及每个存储库的属性的信息。
Spring Data REST应用程序的根目录是一个配置文件链接。假设您有一个包含人员和相关地址的应用程序,根文档将如下所示:
{
"_links" : {
"persons" : {
"href" : "http://localhost:8080/persons"
},
"addresses" : {
"href" : "http://localhost:8080/addresses"
},
"profile" : {
"href" : "http://localhost:8080/profile"
}
}
}
如果您导航到localhost:8080/profile
的个人资料链接,您会看到如下:
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/profile"
},
"persons" : {
"href" : "http://localhost:8080/profile/persons"
},
"addresses" : {
"href" : "http://localhost:8080/profile/addresses"
}
}
}
在根级别,配置文件是单个链接,因此无法处理多个应用程序配置文件。这就是为什么您必须导航到/profile 以找到每个资源元数据的链接。
|
我们浏览到/profile/persons
并查看Person
资源的配置文件数据。
{
"version" : "1.0",
"descriptors" : [ {
"id" : "person-representation", (1)
"descriptors" : [ {
"name" : "firstName",
"type" : "SEMANTIC"
}, {
"name" : "lastName",
"type" : "SEMANTIC"
}, {
"name" : "id",
"type" : "SEMANTIC"
}, {
"name" : "address",
"type" : "SAFE",
"rt" : "http://localhost:8080/profile/addresses#address"
} ]
}, {
"id" : "create-persons", (2)
"name" : "persons", (3)
"type" : "UNSAFE", (4)
"rt" : "#person-representation" (5)
}, {
"id" : "get-persons",
"name" : "persons",
"type" : "SAFE",
"rt" : "#person-representation"
}, {
"id" : "delete-person",
"name" : "person",
"type" : "IDEMPOTENT",
"rt" : "#person-representation"
}, {
"id" : "patch-person",
"name" : "person",
"type" : "UNSAFE",
"rt" : "#person-representation"
}, {
"id" : "update-person",
"name" : "person",
"type" : "IDEMPOTENT",
"rt" : "#person-representation"
}, {
"id" : "get-person",
"name" : "person",
"type" : "SAFE",
"rt" : "#person-representation"
} ]
}
1 | 顶部是Person 资源的属性的详细列表,标识为#person-representation 。它列出了属性的名称。 |
2 | 资源表示是所有支持的操作之后。这就是如何创建一个新的Person 。 |
3 | 这个名字是个人,这表示一个POST应该应用于整个集合,而不是一个人。 |
4 | 的类型为UNSAFE ,因为这种操作可能改变该系统的状态。 |
此JSON文档的媒体类型为application/alps+json 。这与之前的JSON文档不同,后者的媒体类型为application/hal+json 。这些格式不同,受不同规格的约束。
|
当您查看收集资源时,您还会找到_links集合中显示的“个人资料”链接。
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons" (1)
},
... other links ...
"profile" : {
"href" : "http://localhost:8080/profile/persons" (2)
}
},
...
}
1 | 这个HAL文件代表Person 集合。 |
2 | 它具有与元数据相同的URI 的配置文件链接。 |
该配置文件链接,再次,将默认成为了ALPS或者如果您使用的接受头的应用/阿尔卑斯山+ JSON。
13.1.1.超媒体控件类型
ALPS显示每个超媒体控件的类型。他们包括:
类型 | 描述 |
---|---|
语义 |
状态元素(例如HTML.SPAN,HTML.INPUT等)。 |
安全 |
触发安全,幂等状态转换的超媒体控件(例如GET或HEAD)。 |
幂等 |
触发不安全,幂等状态转换(例如PUT或DELETE)的超媒体控件。 |
不安全 |
触发不安全,非幂等状态转换(例如POST)的超媒体控件。 |
在上面的表示部分中,来自应用程序的数据位被标记为SEMANTIC。该地址字段是涉及安全的链接GET以检索。因此,它被标记为SAFE。超媒体操作本身映射到如表所示的类型。
13.1.2.ALPS与投影
如果您定义任何投影,它们也列在ALPS元数据中。假设我们还定义了inlineAddress和noAddresses,它们将出现在相关操作中,即GET的整个集合以及单个资源的GET。以下显示了get-persons子代码的替代版本:
...
{
"id" : "get-persons",
"name" : "persons",
"type" : "SAFE",
"rt" : "#person-representation",
"descriptors" : [ { (1)
"name" : "projection",
"doc" : {
"value" : "The projection that shall be applied when rendering the response. Acceptable values available in nested descriptors.",
"format" : "TEXT"
},
"type" : "SEMANTIC",
"descriptors" : [ {
"name" : "inlineAddress", (2)
"type" : "SEMANTIC",
"descriptors" : [ {
"name" : "address",
"type" : "SEMANTIC"
}, {
"name" : "firstName",
"type" : "SEMANTIC"
}, {
"name" : "lastName",
"type" : "SEMANTIC"
} ]
}, {
"name" : "noAddresses", (3)
"type" : "SEMANTIC",
"descriptors" : [ {
"name" : "firstName",
"type" : "SEMANTIC"
}, {
"name" : "lastName",
"type" : "SEMANTIC"
} ]
} ]
} ]
}
...
1 | 一个新的属性,描述符,出现包含一个数组与一个条目,投影。 |
2 | 里面projection.descriptors我们可以看到inLineAddress上市。它将渲染地址,firstName和lastName。投影内部呈现的关系会导致数据字段的内联。 |
3 | 还发现noAddresses,它提供了一个包含firstName和lastName的子集。 |
有了所有这些信息,客户端应该不仅可以推断出RESTful转换的可用性,而且在某种程度上可以推断出需要进行交互的数据元素。
13.1.3.在ALPS描述中添加自定义详细信息
可以创建出现在ALPS元数据中的自定义消息。只需创建rest-messages.properties
:
rest.description.person=A collection of people
rest.description.person.id=primary key used internally to store a person (not for RESTful usage)
rest.description.person.firstName=Person's first name
rest.description.person.lastName=Person's last name
rest.description.person.address=Person's address
您可以看到,这定义了为Person
资源显示的详细信息。他们改变人物代表的ALPS格式如下:
...
{
"id" : "person-representation",
"doc" : {
"value" : "A collection of people", (1)
"format" : "TEXT"
},
"descriptors" : [ {
"name" : "firstName",
"doc" : {
"value" : "Person's first name", (2)
"format" : "TEXT"
},
"type" : "SEMANTIC"
}, {
"name" : "lastName",
"doc" : {
"value" : "Person's last name", (3)
"format" : "TEXT"
},
"type" : "SEMANTIC"
}, {
"name" : "id",
"doc" : {
"value" : "primary key used internally to store a person (not for RESTful usage)", (4)
"format" : "TEXT"
},
"type" : "SEMANTIC"
}, {
"name" : "address",
"doc" : {
"value" : "Person's address", (5)
"format" : "TEXT"
},
"type" : "SAFE",
"rt" : "http://localhost:8080/profile/addresses#address"
} ]
}
...
通过提供这些属性设置,每个字段都有一个额外的doc属性。
1 | rest.description.person 的值映射到整个表示。 |
2 | rest.description.person.firstName 的值映射到firstName属性。 |
3 | rest.description.person.lastName 的值映射到lastName属性。 |
4 | rest.description.person.id 的值映射到id属性,一个字段不正常显示。 |
5 | rest.description.person.address 的值映射到地址属性。 |
Spring MVC(这是Spring Data REST应用程序的本质)支持语言环境,这意味着您可以使用不同的消息捆绑多个属性文件。 |
13.2.JSON Schema
JSON Schema是Spring Data REST支持的另一种形式的元数据。根据他们的网站,JSON Schema具有以下优点:
-
描述您现有的数据格式
-
清晰的人机阅读文件
-
完整的结构验证,可用于自动测试和验证客户提交的数据
如上一节所示,您可以通过从根URI导航到“配置文件”链接来访问此数据。
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/profile"
},
"persons" : {
"href" : "http://localhost:8080/profile/persons"
},
"addresses" : {
"href" : "http://localhost:8080/profile/addresses"
}
}
}
这些链接与前面所示的相同。要检索JSON Schema,您可以使用Accept header application / schema + json调用它们。
在这种情况下,如果您执行curl -H 'Accept:application/schema+json' http://localhost:8080/profile/persons
,您会看到如下:
{
"title" : "org.springframework.data.rest.webmvc.jpa.Person", (1)
"properties" : { (2)
"firstName" : {
"readOnly" : false,
"type" : "string"
},
"lastName" : {
"readOnly" : false,
"type" : "string"
},
"siblings" : {
"readOnly" : false,
"type" : "string",
"format" : "uri"
},
"created" : {
"readOnly" : false,
"type" : "string",
"format" : "date-time"
},
"father" : {
"readOnly" : false,
"type" : "string",
"format" : "uri"
},
"weight" : {
"readOnly" : false,
"type" : "integer"
},
"height" : {
"readOnly" : false,
"type" : "integer"
}
},
"descriptors" : { },
"type" : "object",
"$schema" : "http://json-schema.org/draft-04/schema#"
}
1 | 导出的类型 |
2 | 属性列表 |
如果您的资源具有其他资源的链接,则会有更多详细信息。
当您查看收集资源时,您还会找到_links集合中显示的“个人资料”链接。
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons" (1)
},
... other links ...
"profile" : {
"href" : "http://localhost:8080/profile/persons" (2)
}
},
...
}
1 | 该HAL文件代表Person 集合。 |
2 | 它具有与元数据相同的URI 的配置文件链接。 |
14.安全
Spring Data REST与Spring Security的效果相当。本节将展示如何使用方法级安全性来保护您的Spring Data REST服务的示例。
14.1.@Pre和@Post安全
Spring Data REST的测试套件中的以下示例显示了Spring Security的PreAuthorization模型,即最复杂的版本:
@PreAuthorize("hasRole('ROLE_USER')") (1)
public interface PreAuthorizedOrderRepository extends CrudRepository<Order, UUID> {
@PreAuthorize("hasRole('ROLE_ADMIN')") (2)
@Override
void delete(UUID aLong);
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Override
void delete(Order order);
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Override
void delete(Iterable<? extends Order> orders);
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Override
void deleteAll();
}
这是通过一些关键更改扩展CrudRepository
的标准Spring Data存储库定义。
1 | 此Spring Security注释保护整个存储库。的Spring Security SpEL表达式指示主体必须具有ROLE_USER他角色的收藏。 |
2 | 要更改方法级设置,必须覆盖方法签名并应用Spring Security注释。在这种情况下,该方法将覆盖存储库级设置,要求用户具有ROLE_ADMIN才能执行删除。 |
Repository和方法级安全设置不组合。相反,方法级设置会覆盖存储库级设置。 |
上一个例子说明了CrudRepository
其实有四个删除方法。您必须覆盖所有删除方法才能正确保护它。
14.2.@安全保障
以下示例显示Spring Security较旧的@Secured
注释,它纯粹基于角色:
@Secured("ROLE_USER") (1)
@RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface SecuredPersonRepository extends CrudRepository<Person, UUID> {
@Secured("ROLE_ADMIN") (2)
@Override
void delete(UUID aLong);
@Secured("ROLE_ADMIN")
@Override
void delete(Person person);
@Secured("ROLE_ADMIN")
@Override
void delete(Iterable<? extends Person> persons);
@Secured("ROLE_ADMIN")
@Override
void deleteAll();
}
1 | 这导致了相同的安全检查,但灵活性较低。它只允许角色作为限制访问的手段。 |
2 | 同样,这表明删除方法需要ROLE_ADMIN。 |
如果您开始使用新项目或首先应用Spring Security,则建议使用@PreAuthorize 解决方案。如果您的应用程序的其他部分已经使用Spring Security与@Secured ,则可以在该路径上继续,而无需重写所有内容。
|
14.3.启用方法级安全性
要配置方法级安全性,以下是来自Spring Data REST测试套件的简要摘录:
@Configuration (1)
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) (2)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter { (3)
...
}
1 | 这是一个Spring配置类。 |
2 | 它使用Spring Security的@EnableGlobalMethodSecurity 注释来启用@Secured 和@Pre / @Post 支持。注意:您不必同时使用。这种特殊情况用于证明这两个版本与Spring Data REST一起工作。 |
3 | 该类扩展了Spring Security的WebSecurityConfigurerAdapter ,用于纯Java配置的安全性。 |
配置类的其余部分未列出,因为它遵循您可以在Spring Security参考文档中阅读的标准做法。
工具
15.1.HAL浏览器
HAL规范的开发人员有一个有用的应用程序:HAL浏览器。这是一个web应用程序,在一些HAL驱动的JavaScript中激起。您可以将其指向任何Spring Data REST API,并使用它来导航应用程序并创建新的资源。
您不需要拖放文件,将它们嵌入到应用程序中,而是制作Spring MVC控制器来提供服务,您只需添加一个依赖关系即可。
在Maven:
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-browser</artifactId>
</dependency>
</dependencies>
在毕业:
dependencies {
compile 'org.springframework.data:spring-data-rest-hal-browser'
}
如果您使用Spring Boot或Spring Data BOM(物料清单),则无需指定版本。 |
当您在浏览器中访问应用程序的根URI时,此依赖关系将自动配置HAL浏览器以提供服务。(注意:http:// localhost:8080已插入浏览器,并重定向到如下所示的URL。)
上面的屏幕截图显示了API的根路径。右侧是包括标题和主体(HAL文档)的响应的详细信息。
HAL浏览器从响应中读取链接,并将它们放在左侧的列表中。您可以单击GET按钮并导航到其中一个集合,也可以单击非GET选项进行更改。
HAL浏览器会说URI模板。您可能会注意到GET旁边的人物有一个问号图标。如果您选择如下导航,将会弹出一个扩展对话框:
当您单击非GET按钮时,会弹出一个弹出对话框。默认情况下,显示POST。该字段可以调整为PUT或PATCH。这些标题被填写正确地提交一个新的JSON文档。
在URI,方法和标题下面都是字段。这些根据资源的元数据自动提供,由Spring Data REST自动生成。更新您的域对象,弹出窗口将反映出来。
16.定制Spring Data REST
有很多选择来定制Spring Data REST。这些小节显示如何。
16.1.自定义项目资源URI
默认情况下,项目资源的URI由用于附加了数据库标识符的收集资源的路径段组成。这允许我们使用存储库的findOne(…)
方法来查找实体实例。从Spring Data REST 2.5开始,可以使用RepositoryRestConfiguration
上的配置API(Java 8中的首选)或通过在应用程序中将EntityLookup
的实现注册为Spring bean来定制。Spring Data REST将根据它们的实现来挑选并调整URI生成。
假设User
具有唯一标识的username
属性。另外,假设我们在相应的存储库中有一个方法Optional<User> findByUsername(String username)
。
在Java 8中,我们可以简单地将映射方法注册为弱引用URI的方法引用,如下所示:
@Component
public class SpringDataRestCustomization extends RepositoryRestConfigurerAdapter {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.withCustomEntityLookup().//
forRepository(UserRepository.class, User::getUsername, UserRepository::findByUsername);
}
}
forRepository(…)
将存储库类型作为第一个参数,将存储库域类型映射到某些目标类型的方法引用,以及使用提及的作为第一个参数的存储库将该值映射到另一个方法引用。
如果您没有运行Java 8或更好的版本,则可以使用该方法,但需要使用一些相当详细的匿名内部类才能使用。这就是为什么在较旧的Java版本上,您可能更喜欢实现UserEntityLookup
,如下所示:
@Component
public class UserEntityLookup extends EntityLookupSupport<User> {
private final UserRepository repository;
public UserEntityLookup(UserRepository repository) {
this.repository = repository;
}
@Override
public Serializable getResourceIdentifier(User entity) {
return entity.getUsername();
}
@Override
public Object lookupEntity(Serializable id) {
return repository.findByUsername(id.toString());
}
}
请注意,getResourceIdentifier(…)
如何返回URI创建所使用的用户名。要通过从该方法返回的值加载实体实例,我们现在使用UserRepository
上可用的查询方法实现lookupEntity(…)
。
16.2.配置REST URL路径
配置导出JPA存储库的资源的URL路径的段很简单。您只需在类级别和/或查询方法级别添加注释。
默认情况下,导出器将使用域类的名称来显示您的CrudRepository
。Spring Data REST还应用Evo Inflector来复数这个词。所以存储库定义如下:
interface PersonRepository extends CrudRepository<Person, Long> {}
默认情况下,将会显示在URL http://localhost:8080/persons/
下面
要更改存储库的导出方式,请在类级别添加@RestResource
注释:
@RepositoryRestResource(path = "people")
interface PersonRepository extends CrudRepository<Person, Long> {}
现在可以通过URL访问存储库:http://localhost:8080/people/
如果您定义了查询方法,那么它们也默认以其名称公开:
interface PersonRepository extends CrudRepository<Person, Long> {
List<Person> findByName(String name);
}
所有查询方法资源都显示在资源search 下。
|
要更改此查询方法所在的URL的段,请再次使用@RestResource
注释:
@RepositoryRestResource(path = "people")
interface PersonRepository extends CrudRepository<Person, Long> {
@RestResource(path = "names")
List<Person> findByName(String name);
}
现在这个查询方法将在URL下显示:http://localhost:8080/people/search/names
16.2.1.处理相关
由于这些资源都是可发现的,您还可以影响“rel”属性在导出程序发出的链接中的显示方式。
例如,在默认配置中,如果您向http://localhost:8080/persons/search
发出请求,以了解哪些查询方法被公开,您将获得一个链接列表:
{
"_links" : {
"findByName" : {
"href" : "http://localhost:8080/persons/search/findByName"
}
}
}
要更改rel值,请使用@RestResource
注释上的rel
属性:
@RepositoryRestResource(path = "people")
interface PersonRepository extends CrudRepository<Person, Long> {
@RestResource(path = "names", rel = "names")
List<Person> findByName(String name);
}
这将导致链接值为:
{
"_links" : {
"names" : {
"href" : "http://localhost:8080/persons/search/names"
}
}
}
JSON的这些片段假设您使用的是Spring Data REST的默认格式的HAL。可以关闭HAL,这将导致输出看起来不同。但是,您覆盖rel名称的能力完全独立于渲染格式。 |
@RepositoryRestResource(path = "people", rel = "people")
interface PersonRepository extends CrudRepository<Person, Long> {
@RestResource(path = "names", rel = "names")
List<Person> findByName(String name);
}
更改一个Repository的rel将更改顶级名称:
{
"_links" : {
"people" : {
"href" : "http://localhost:8080/people"
},
…
}
}
在上面的顶层片段:
-
path = "people"
将href
中的值从/persons
更改为/people
-
rel = "people"
将该链接的名称从persons
更改为people
当您导航到此存储库的搜索资源时,finder-method的@RestResource
注释已更改路径,如下所示:
{
"_links" : {
"names" : {
"href" : "http://localhost:8080/people/search/names"
}
}
}
您的Repository定义中的这些注释集合引起了以下更改:
-
Repository级别注释的
path = "people"
反映在基本URI中,具有/people
-
作为一个finder方法为您提供
/people/search
-
path = "names"
创建一个/people/search/names
的URI -
rel = "names"
将该链接的名称从findByNames
更改为names
16.2.2.隐藏某些存储库,查询方法或字段
您可能不想要一个存储库,存储库上的查询方法,或者实体导出的一个字段。示例包括在User
对象或类似的敏感数据上隐藏password
等字段。要告诉导出程序不导出这些项目,请使用@RestResource
注释它们并设置exported = false
。
例如,要跳过导出Repository:
@RepositoryRestResource(exported = false)
interface PersonRepository extends CrudRepository<Person, Long> {}
要跳过导出查询方法:
@RepositoryRestResource(path = "people", rel = "people")
interface PersonRepository extends CrudRepository<Person, Long> {
@RestResource(exported = false)
List<Person> findByName(String name);
}
或者跳过导出字段:
@Entity
public class Person {
@Id @GeneratedValue private Long id;
@OneToMany
@RestResource(exported = false)
private Map<String, Profile> profiles;
}
投影提供了改变出口的方法,并有效地侧面地执行这些设置。如果您针对同一个域对象创建任何投影,则不负责导出字段。看到 |
16.2.3.隐藏存储库CRUD方法
如果您不想在CrudRepository
上公开保存或删除方法,则可以使用@RestResource(exported = false)
设置来覆盖要关闭的方法并将注释放在覆盖版本上。例如,为了防止HTTP用户调用CrudRepository
的删除方法,请覆盖所有这些删除方法,并将注释添加到覆盖方法中。
@RepositoryRestResource(path = "people", rel = "people")
interface PersonRepository extends CrudRepository<Person, Long> {
@Override
@RestResource(exported = false)
void delete(Long id);
@Override
@RestResource(exported = false)
void delete(Person entity);
}
重要的是您可以覆盖这两种删除方法,因为导出程序目前使用一些较为幼稚的算法来确定用于更快运行时性能的CRUD方法。目前不可能关闭取消ID的删除版本,但是导出了采用实体实例的版本。目前,您可以导出删除方法。如果你想关闭它们,那么请记住,你必须使用exported = false 注释两个版本。
|
16.3.将Spring Data REST添加到现有的Spring MVC应用程序
如果使用Spring Boot,则不需要执行以下步骤。添加spring-boot-starter-data-rest将使其自动添加到应用程序中。 |
如果您有一个现有的Spring MVC应用程序,而您希望集成Spring Data REST,那其实很简单。
您的Spring MVC配置(很可能在配置MVC资源的地方)的某处会向负责配置RepositoryRestController
的JavaConfig类添加一个bean引用。班级名称为org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration
。
在Java中,这样就像:
import org.springframework.context.annotation.Import;
import org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration;
@Configuration
@Import(RepositoryRestMvcConfiguration.class)
public class MyApplicationConfiguration {
…
}
在XML中,它将如下所示:
<bean class="org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration"/>
当您的ApplicationContext遇到此bean定义时,它将引导必要的Spring MVC资源来完全配置控制器,以导出其在ApplicationContext
和任何父上下文中找到的存储库。
16.3.1.更多关于所需配置
有一些Spring Data REST依赖于Spring Data REST的资源必须配置正确才能在现有的Spring MVC应用程序中工作。我们试图将这些资源与应用程序中已经存在的任何类似资源隔离开来,但是您可能希望通过修改这些MVC组件来自定义Spring Data REST的一些行为。
我们特别为Spring Data REST使用的最重要的事情包括:
RepositoryRestHandlerMapping
我们注册一个只响应RepositoryRestController
的自定义HandlerMapping
实例,只有路径是由Spring Data REST处理的。为了保持由应用程序处理的路径与Spring Data REST处理的路径不同,此自定义HandlerMapping
检查URL路径,并检查是否以该名称导出Repository 。如果有,它允许请求由Spring Data REST处理。如果在该名称下没有导出Repository,则返回null
,这意味着“让其他HandlerMapping
实例尝试为此请求提供服务”。
Spring Data REST HandlerMapping
配置为order=(Ordered.LOWEST_PRECEDENCE - 100)
,这意味着当映射URL路径时,通常会首先在行。您现有的应用程序永远不会有机会为存储库的请求提供服务。例如,如果您以“person”的名称导出存储库,则以/person
开头的所有应用程序请求将由Spring Data REST处理,您的应用程序将永远不会看到该请求。如果您的存储库以不同的名称导出(例如“人”),则/people
的请求将转到Spring Data REST,并且“/ person”的请求将由您的应用程序处理。
16.4.覆盖Spring Data REST响应处理程序
有时您可能想为特定资源编写一个自定义处理程序。要利用Spring Data REST的设置,消息转换器,异常处理等,请使用@RepositoryRestController
注释而不是标准Spring MVC @Controller
或@RestController
:
@RepositoryRestController
public class ScannerController {
private final ScannerRepository repository;
@Autowired
public ScannerController(ScannerRepository repo) { (1)
repository = repo;
}
@RequestMapping(method = GET, value = "/scanners/search/listProducers") (2)
public @ResponseBody ResponseEntity<?> getProducers() {
List<String> producers = repository.listProducers(); (3)
//
// do some intermediate processing, logging, etc. with the producers
//
Resources<String> resources = new Resources<String>(producers); (4)
resources.add(linkTo(methodOn(ScannerController.class).getProducers()).withSelfRel()); (5)
// add other links as needed
return ResponseEntity.ok(resources); (6)
}
}
该控制器将由所有其他RESTful端点(例如/ api)使用的RepositoryRestConfiguration.setBasePath
中定义的相同API基础路径提供。它也具有以下特点:
1 | 此示例使用构造函数注入。 |
2 | 该处理程序插入一个Spring Data finder方法的自定义处理程序。 |
3 | 该处理程序正在使用底层存储库来获取数据,然后在将最终数据集返回给客户端之前执行某种形式的后处理。 |
4 | 结果需要包含在一个Spring HATEOAS Resources 对象中以返回一个集合,但只有一个Resource 。 |
5 | 添加一个链接到这个确切的方法作为一个“自”链接。 |
6 | 使用Spring MVC的ResponseEntity 包装器返回集合,确保收集被正确地包装并以适当的接受类型呈现。 |
Resources
用于收集,而Resource
用于单个项目。这些类型可以组合。如果您知道集合中每个项目的链接,请使用Resources<Resource<String>>
(或任何核心域类型)。这可以让您为每个项目以及整个集合组合链接。
在此示例中,组合路径将为RepositoryRestConfiguration.getBasePath() + /scanners/search/listProducers 。
|
如果您对实体特定的操作不感兴趣,但仍希望在basePath
之下构建自定义操作,例如Spring MVC视图,资源等,请使用@BasePathAwareController
。
如果您使用@Controller 或@RestController ,该代码将完全超出Spring Data REST的范围。这扩展到请求处理,消息转换器,异常处理等
|
16.5.自定义JSON输出
有时在您的应用程序中,您需要提供来自特定实体的其他资源的链接。例如,Customer
响应可能会丰富与当前购物车的链接,或链接以管理与该实体相关的资源。Spring Data REST提供与Spring HATEOAS的集成,并为用户提供一个扩展挂钩来更改向客户端出来的资源的表示。
16.5.1.ResourceProcessor接口
Spring HATEOAS定义了一个用于处理实体的ResourceProcessor<>
接口。类型为ResourceProcessor<Resource<T>>
的所有bean将自动由Spring Data REST导出器拾取,并在序列化类型为T
的实体时触发。
例如,要为Person
实体定义一个处理器,请向您的“ApplicationContext”添加一个@Bean
,如下所示(从Spring Data REST测试中获取):
@Bean
public ResourceProcessor<Resource<Person>> personProcessor() {
return new ResourceProcessor<Resource<Person>>() {
@Override
public Resource<Person> process(Resource<Person> resource) {
resource.add(new Link("http://localhost:8080/people", "added-link"));
return resource;
}
};
}
此示例硬编码到http://localhost:8080/people 的链接。如果您的应用程序中有一个Spring MVC端点,您希望链接到,请考虑使用Spring HATEOAS的linkTo(…) 方法来避免管理该URL。
|
16.5.3.自定义表示
Spring Data REST导出器使用内部ConversionService
执行任何发现的
实例。这是负责创建引用实体的链接的组件(例如对象的JSON表示中_links属性下的对象)。它需要ResourceProcessor
`s before it creates the output representation. It does this by registering a `Converter<Entity, Resource>@Entity
并迭代其属性,为由Repository
管理的属性创建链接,并复制任何嵌入或简单属性。
但是,如果您的项目需要使用不同格式的输出,则可以使用自己的完全替换默认的传出JSON表示。如果您在ApplicationContext中注册自己的ConversionService
并注册自己的Converter<Person, Resource>
,那么您可以返回您选择的Resource
实现。
16.6.将自定义(de)序列化程序添加到Jackson的ObjectMapper中
有时,Spring Data REST的ObjectMapper
特性配置为使用可以将域对象转换成链接并重新返回的智能串行器的行为可能无法正确处理您的域模型。有很多方法可以构建您的数据,您可能会发现自己的域模型没有被正确地转换为JSON。在这些情况下,有时不习惯以通用方式尝试和支持复杂的域模型。有时,根据复杂性,甚至不可能提供通用的解决方案。
为了适应用例的最大百分比,Spring Data REST非常难以正确地呈现对象图。它将尝试将非托管bean作为普通POJO进行序列化,并且将尝试创建与需要的托管bean的链接。但是,如果您的域名模型不容易读取或编写纯粹的JSON,则可能需要使用自己的自定义类型映射和(de)序列化来配置Jackson的ObjectMapper。
16.6.1.抽象班注册
当您在域模型中使用抽象类(或接口)时,您可能需要钩住的一个关键配置点。杰克逊默认不知道为接口创建什么实现。举个例子:
@Entity
public class MyEntity {
@OneToMany
private List<MyInterface> interfaces;
}
在默认配置中,杰克逊不知道在向出口商发布新数据时要实例化什么类。这是您需要通过注释来告诉杰克逊,或者更干净地通过使用模块注册类型映射。
在ApplicationContext
范围内声明的任何Module
bean将由导出器接收并注册到其ObjectMapper
。要添加这个特殊的抽象类类型映射,创建Module
bean,并在setupModule
方法中添加适当的TypeResolver
:
public class MyCustomModule extends SimpleModule {
private MyCustomModule() {
super("MyCustomModule", new Version(1, 0, 0, "SNAPSHOT"));
}
@Override
public void setupModule(SetupContext context) {
context.addAbstractTypeResolver(
new SimpleAbstractTypeResolver().addMapping(MyInterface.class,
MyInterfaceImpl.class));
}
}
一旦你访问了你的Module
中的SetupContext
对象,你可以做各种很酷的事情来配置Jackon的JSON映射。您可以阅读更多关于模块在杰克逊维基上的工作。
16.6.2.为域类型添加自定义序列化程序
如果要以特殊方式(de)序列化域类型,可以使用Jackson的ObjectMapper
注册自己的实现,并且Spring Data REST导出器将会正确地透明地处理这些域对象。
要添加序列化程序,请从您的setupModule
方法实现中执行以下操作:
public class MyCustomModule extends SimpleModule {
…
@Override
public void setupModule(SetupContext context) {
SimpleSerializers serializers = new SimpleSerializers();
SimpleDeserializers deserializers = new SimpleDeserializers();
serializers.addSerializer(MyEntity.class, new MyEntitySerializer());
deserializers.addDeserializer(MyEntity.class, new MyEntityDeserializer());
context.addSerializers(serializers);
context.addDeserializers(deserializers);
}
}
现在,Spring Data REST将正确处理您的域对象,以防万一它们对于Spring Data REST尝试覆盖的80%通用用例来说太复杂了。
16.7.配置CORS
出于安全考虑,浏览器禁止AJAX调用驻留在当前来源之外的资源。在使用浏览器发出的客户端HTTP请求时,您需要启用特定的HTTP资源才能访问。
Spring Data REST 通过Spring的CORS支持支持2.6的跨源资源共享(CORS)。
16.7.1.Repository接口CORS配置
您可以向存储库接口添加@CrossOrigin
注释,以便为整个存储库启用CORS。默认情况下,@CrossOrigin
允许所有来源和HTTP方法:
@CrossOrigin
interface PersonRepository extends CrudRepository<Person, Long> {}
在上面的例子中,对整个PersonRepository
启用了CORS支持。@CrossOrigin
提供配置CORS支持的属性。
@CrossOrigin(origins = "http://domain2.com",
methods = { RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE },
maxAge = 3600)
interface PersonRepository extends CrudRepository<Person, Long> {}
该示例使得CORS支持整个PersonRepository
提供一个起始点,限于GET
,POST
和DELETE
方法,最大年龄为3600秒。
16.7.2.Repository REST控制器方法CORS配置
Spring Data REST完全支持在自定义REST控制器上共享存储库基本路径的MVC的Controller方法配置Spring Web。
@RepositoryRestController
public class PersonController {
@CrossOrigin(maxAge = 3600)
@RequestMapping(path = "/people/xml/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_XML_VALUE)
public Person retrieve(@PathVariable Long id) {
// …
}
}
使用@RepositoryRestController 注释的控制器从其关联的存储库继承@CrossOrigin 配置。
|
16.7.3.全局CORS配置
除了细粒度,基于注释的配置,您也可能想要定义一些全局CORS配置。这与Spring Web MVC的CORS配置类似,但可以在Spring Data REST中声明,并结合细粒度@CrossOrigin
配置。默认情况下,所有来源和GET
,HEAD
和POST
方法都是允许的。
现有的Spring Web MVC CORS配置不适用于Spring Data REST。 |
@Component
public class SpringDataRestCustomization extends RepositoryRestConfigurerAdapter {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.getCorsRegistry().addMapping("/person/**")
.allowedOrigins("http://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(false).maxAge(3600);
}
}
附录
附录B:Spring Data REST示例项目
本附录包含Spring Data REST示例应用程序的列表。每个示例的确切版本不能保证与该参考手册的版本相匹配。
要获取所有这些,请访问https://github.com/spring-projects/spring-data-examples,并克隆或下载zipball。这将为您提供所有支持的Spring Data项目的示例应用程序。只需导航到spring-data-examples/rest 。
|
多店铺的例子
这个例子展示了如何混合几个基本的Spring Data项目。
Spring Data REST + Spring Security
此示例显示了如何使用Spring Security以多种方式保护Spring Data REST应用程序。
星巴克的例子
此示例通过RESTful API公开了10843家星巴克咖啡店,该API可以以超媒体方式访问商店,并公开资源以执行咖啡店的地理位置搜索。