此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Data REST 4.4.0spring-doc.cadn.net.cn

投影和摘录

Spring Data REST 提供您导出的域模型的默认视图。但是,有时,由于各种原因,您可能需要更改该模型的视图。本节介绍如何定义投影和摘录,以提供简化和简化的资源视图。spring-doc.cadn.net.cn

预测

请考虑以下域模型:spring-doc.cadn.net.cn

@Entity
public class Person {

  @Id @GeneratedValue
  private Long id;
  private String firstName, lastName;

  @OneToOne
  private Address address;
  …
}

Personobject 具有多个属性:spring-doc.cadn.net.cn

现在假设我们创建相应的存储库,如下所示:spring-doc.cadn.net.cn

interface PersonRepository extends CrudRepository<Person, Long> {}

默认情况下, Spring Data REST 会导出此域对象,包括其所有属性。firstNamelastName将导出为它们所是的纯数据对象。关于address属性。一个选项是同时为Address对象,如下所示:spring-doc.cadn.net.cn

interface AddressRepository extends CrudRepository<Address, Long> {}

在这种情况下,Personresource 渲染address属性作为其相应Address资源。如果我们在系统中查找 “Frodo”,我们可能会看到如下 HAL 文档:spring-doc.cadn.net.cn

{
  "firstName" : "Frodo",
  "lastName" : "Baggins",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/persons/1"
    },
    "address" : {
      "href" : "http://localhost:8080/persons/1/address"
    }
  }
}

还有另一种方法。如果Addressdomain 对象没有自己的存储库定义,则 Spring Data REST 包括Person资源,如下例所示:spring-doc.cadn.net.cn

{
  "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 服务的使用者提供替代方案。以下示例显示了一个不包含 address 的投影:spring-doc.cadn.net.cn

@Projection(name = "noAddresses", types = { Person.class }) (1)
interface NoAddresses { (2)

  String getFirstName(); (3)

  String getLastName(); (4)
}
1 @Projectionannotation 将此标记为投影。这name属性提供 投影的名称,我们稍后将更详细地介绍。这typesattributes 将此投影定位为仅应用于Person对象。
2 它是一个 Java 接口,使其成为声明性的。
3 它会导出firstName.
4 它会导出lastName.

NoAddressesprojection 只有firstNamelastName,这意味着它不提供任何地址信息。假设您有一个单独的存储库Address资源,Spring Data REST 的默认视图与以前的表示形式略有不同,如下例所示:spring-doc.cadn.net.cn

{
  "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 selfURI 是 URI 模板。

要查看到资源的投影,请向上查找localhost:8080/persons/1?projection=noAddresses.spring-doc.cadn.net.cn

提供给projectionquery 参数与@Projection(name = "noAddress").它与投影的接口名称无关。

您可以有多个投影。spring-doc.cadn.net.cn

请参阅 投影 以查看示例项目。我们鼓励您尝试一下。

Spring Data REST 按如下方式查找投影定义:spring-doc.cadn.net.cn

  • 任何@Projection在与实体定义相同的包(或其子包之一)中找到的接口已注册。spring-doc.cadn.net.cn

  • 您可以使用RepositoryRestConfiguration.getProjectionConfiguration().addProjection(…).spring-doc.cadn.net.cn

无论哪种情况,投影接口都必须具有@Projection注解。spring-doc.cadn.net.cn

查找现有投影

Spring Data REST 公开了应用程序级配置文件语义 (ALPS) 文档,这是一种微元数据格式。要查看 ALPS 元数据,请按照profile链接。如果您向下导航到 ALPS 文档Person资源(即/alps/persons),您可以找到许多有关Person资源。将列出投影,以及有关GETREST 转换,在类似于以下示例的块中:spring-doc.cadn.net.cn

{ …
  "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 文档的这一部分显示了以下详细信息GETPerson资源。
2 这部分包括projection选项。
3 这部分包含noAddresses投影。
4 此投影提供的实际属性包括firstNamelastName.

如果客户符合以下条件,则选择投影定义并使其可供客户使用:spring-doc.cadn.net.cn

  • 标有@Projection注解,并且位于域类型的同一包(或子包)中,或者spring-doc.cadn.net.cn

  • 使用 手动注册RepositoryRestConfiguration.getProjectionConfiguration().addProjection(…).spring-doc.cadn.net.cn

引入隐藏数据

到目前为止,在本节中,我们已经介绍了如何使用投影来减少呈现给用户的信息。投影还可以引入通常看不见的数据。例如, Spring Data REST 忽略标记为@JsonIgnore附注。请考虑以下域对象:spring-doc.cadn.net.cn

@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 的集成。如果您创建一个UserRepositorypasswordfield 通常会被导出,这并不好。在前面的示例中,我们通过应用 Jackson 的@JsonIgnorepassword田。spring-doc.cadn.net.cn

如果 Jackson 执行以下作,则不会将字段序列化为 JSON@JsonIgnore位于字段的相应 getter 函数上。

但是,投影引入了仍可服务于此字段的功能。可以创建以下投影:spring-doc.cadn.net.cn

@Projection(name = "passwords", types = { User.class })
interface PasswordProjection {

  String getPassword();
}

如果创建并使用了这样的投影,它将避开@JsonIgnore指令置于User.password.spring-doc.cadn.net.cn

这个例子可能看起来有点做作,但是使用更丰富的域模型和许多投影,可能会意外泄露这些细节。由于 Spring Data REST 无法辨别此类数据的敏感性,因此您需要避免此类情况。

投影还可以生成虚拟数据。假设您有以下实体定义:spring-doc.cadn.net.cn

@Entity
public class Person {

  ...
  private String firstName;
  private String lastName;

  ...
}

您可以创建一个投影,将前面示例中的两个数据字段组合在一起,如下所示:spring-doc.cadn.net.cn

@Projection(name = "virtual", types = { Person.class })
public interface VirtualProjection {

  @Value("#{target.firstName} #{target.lastName}") (1)
  String getFullName();

}
1 Spring的@Valueannotation 允许您插入一个 SpEL 表达式,该表达式采用目标对象并将其firstNamelastName属性来呈现只读fullName.

摘录

摘录是自动应用于资源集合的投影。例如,您可以更改PersonRepository如下:spring-doc.cadn.net.cn

@RepositoryRestResource(excerptProjection = NoAddresses.class)
interface PersonRepository extends CrudRepository<Person, Long> {}

前面的示例指示 Spring Data REST 使用NoAddresses嵌入时的投影Person将 resources 转换为集合或相关资源。spring-doc.cadn.net.cn

摘录投影不会自动应用于单个资源。它们必须被刻意应用。摘录投影旨在提供集合数据的默认预览,但在获取单个资源时不提供。有关该主题的讨论,请参阅Why is an excerpt projection not automatically applied for a Spring Data REST item resource?

除了更改默认渲染之外,摘录还具有其他渲染选项,如下一节所示。spring-doc.cadn.net.cn

摘录经常访问的数据

REST 服务的常见情况是在组合域对象时出现。例如,Person存储在一个表中,并且它们的相关Address存储在另一个 S 中。默认情况下,Spring Data REST 会提供用户的address作为 URI 进行导航。但是,如果消费者总是获取这些额外的数据是很常见的,那么摘录投影可以将这些额外的数据内联,从而为您节省额外的GET.为此,您可以定义另一个摘录投影,如下所示:spring-doc.cadn.net.cn

@Projection(name = "inlineAddress", types = { Person.class }) (1)
interface InlineAddress {

  String getFirstName();

  String getLastName();

  Address getAddress(); (2)
}
1 此投影已被命名为inlineAddress.
2 此投影将getAddress,这将返回Address田。在投影内部使用时,它会导致信息内联包含。

您可以将其插入PersonRepository定义,如下所示:spring-doc.cadn.net.cn

@RepositoryRestResource(excerptProjection = InlineAddress.class)
interface PersonRepository extends CrudRepository<Person, Long> {}

这样做会导致 HAL 文档显示如下:spring-doc.cadn.net.cn

{
  "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 指向Addressresource 仍然提供,因此仍然可以导航到自己的资源。

请注意,前面的示例是本章前面显示的示例的混合。您可能希望通读它们,以跟踪到最后一个示例的进展。spring-doc.cadn.net.cn

配置@RepositoryRestResource(excerptProjection=…​),则更改默认行为。如果您已经进行了发布,这可能会导致对服务的使用者进行重大更改。