此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Data REST 4.4.0! |
投影和摘录
Spring Data REST 提供您导出的域模型的默认视图。但是,有时,由于各种原因,您可能需要更改该模型的视图。本节介绍如何定义投影和摘录,以提供简化和简化的资源视图。
预测
请考虑以下域模型:
@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> {}
在这种情况下,资源将属性呈现为其相应资源的 URI。如果我们在系统中查找 “Frodo”,我们可能会看到如下 HAL 文档:Person
address
Address
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/1"
},
"address" : {
"href" : "http://localhost:8080/persons/1/address"
}
}
}
还有另一种方法。如果域对象没有自己的存储库定义,则 Spring Data REST 会在资源中包含数据字段,如下例所示:Address
Person
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"address" : {
"street": "Bag End",
"state": "The Shire",
"country": "Middle Earth"
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/1"
}
}
}
但是,如果您根本不想要细节怎么办?同样,默认情况下, Spring Data REST 会导出其所有属性(除了 )。您可以通过定义一个或多个投影来为 REST 服务的使用者提供替代方案。以下示例显示了一个不包含 address 的投影:address
id
@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 |
投影只有 和 的 getter,这意味着它不提供任何地址信息。假设你有一个单独的资源存储库,Spring Data REST 的默认视图与前面的表示形式略有不同,如下例所示:NoAddresses
firstName
lastName
Address
{
"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 | URI 是 URI 模板。self |
要查看到资源的投影,请查找 。localhost:8080/persons/1?projection=noAddresses
提供给 query 参数的值与 中指定的值相同。它与投影的接口名称无关。projection @Projection(name = "noAddress") |
您可以有多个投影。
请参阅 投影 以查看示例项目。我们鼓励您尝试一下。 |
Spring Data REST 按如下方式查找投影定义:
-
在与实体定义相同的包(或其子包之一)中找到的任何接口都将被注册。
@Projection
-
您可以使用 手动注册投影。
RepositoryRestConfiguration.getProjectionConfiguration().addProjection(…)
无论哪种情况,projection 接口都必须具有 Comments。@Projection
查找现有投影
Spring Data REST 公开了应用程序级配置文件语义 (ALPS) 文档,这是一种微元数据格式。要查看 ALPS 元数据,请点击根资源公开的链接。如果您向下导航到 resources 的 ALPS 文档(即 ),您可以找到有关资源的许多详细信息。投影以及有关 REST 转换的详细信息在类似于以下示例的块中列出:profile
Person
/alps/persons
Person
GET
{ …
"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 |
如果客户符合以下条件,则选择投影定义并使其可供客户使用:
|
引入隐藏数据
到目前为止,在本节中,我们已经介绍了如何使用投影来减少呈现给用户的信息。投影还可以引入通常看不见的数据。例如, Spring Data REST 会忽略用 Comments 标记的字段或 getter。请考虑以下域对象:@JsonIgnore
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String name;
@JsonIgnore private String password; (1)
private String[] roles;
…
1 | Jackson's 用于防止字段序列化为 JSON。@JsonIgnore password |
前面示例中的类可用于存储用户信息以及与 Spring Security 的集成。如果创建 ,则通常会导出该字段,这并不好。在前面的示例中,我们通过在场上应用 Jackson's 来防止这种情况发生。User
UserRepository
password
@JsonIgnore
password
如果位于字段的相应 getter 函数上,Jackson 也不会将字段序列化为 JSON。@JsonIgnore |
但是,投影引入了仍可服务于此字段的功能。可以创建以下投影:
@Projection(name = "passwords", types = { User.class })
interface PasswordProjection {
String getPassword();
}
如果创建并使用了这样的投影,它将回避对 .@JsonIgnore
User.password
这个例子可能看起来有点做作,但是使用更丰富的域模型和许多投影,可能会意外泄露这些细节。由于 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 的 Comments 允许您插入一个 SpEL 表达式,该表达式采用目标对象并将其 and 属性拼接在一起以呈现只读。@Value firstName lastName fullName |
摘录
摘录是自动应用于资源集合的投影。例如,您可以按如下方式更改 :PersonRepository
@RepositoryRestResource(excerptProjection = NoAddresses.class)
interface PersonRepository extends CrudRepository<Person, Long> {}
前面的示例指示 Spring Data REST 在将资源嵌入到集合或相关资源时使用投影。NoAddresses
Person
摘录投影不会自动应用于单个资源。它们必须被刻意应用。摘录投影旨在提供集合数据的默认预览,但在获取单个资源时不提供。有关该主题的讨论,请参阅Why is an excerpt projection not automatically applied for a Spring Data REST item resource?。 |
除了更改默认渲染之外,摘录还具有其他渲染选项,如下一节所示。
摘录经常访问的数据
REST 服务的常见情况是在组合域对象时出现。例如,a 存储在一个表中,而其相关项存储在另一个表中。默认情况下, Spring Data REST 将人员的 URI 作为客户端必须导航的 URI 提供。但是,如果消费者总是获取这些额外的数据是很常见的,则摘录投影可以将这些额外的数据内联,从而为您节省额外的 .为此,您可以定义另一个摘录投影,如下所示:Person
Address
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=…) |