使用 spring-boot-2.2.1 删除 HATEOAS link 中的 _embedded
Remove _embedded in HATEOAS link using spring-boot-2.2.1
我正在使用 spring-boot-2.2.1 和 spring-HATEOAS。超媒体链接工作正常,但
我在 returning 链接时看到 _embedded
属性,请在 github here,
中找到以下代码以供参考和项目
端点:
a) 将 return CollectionModel
=> localhost:8099/api/v1/capability/list/noembedded
和
b) 将return列出>localhost:8099/api/v1/capability/list/
<?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>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring-boot-unittest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-unittest</name>
<description>Demo project for Spring Boot unit test</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-mongodb</artifactId>
<version>4.1.4</version>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>4.1.4</version>
</dependency>
<!-- Embedded MongoDB for Testing -->
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.2.21</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<!--<processor>com.querydsl.mongodb.morphia.MorphiaAnnotationProcessor</processor>-->
<processor>org.springframework.data.mongodb.repository.support.MongoAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Controller.Java
@RestController
@RequestMapping(value = "/api/v1/capability")
@RequiredArgsConstructor
@CrossOrigin
public class CapabilityController {
private final CapabilityService capabilityService;
private final CapabilityResourceAssembler capabilityResourceAssembler;
@GetMapping(value = "/list")
public CollectionModel<EntityModel<Capability>> getAllCapabilities() {
List<EntityModel<Capability>> capabilities = capabilityService.listCapabilities().stream()
.map(capability -> new EntityModel<>(capability,
linkTo(methodOn(CapabilityController.class).getCapabilityById(capability.getId())).withRel("getThisCapability"),
linkTo(methodOn(CapabilityController.class).getAllCapabilities()).withRel("getAllCapabilities")
)).collect(Collectors.toList());
return new CollectionModel<>(capabilities);
}
}
实际回复
{
"_embedded": {
"capabilityList": [
{
"id": "sample",
"techStack": "Java",
"numOfDevelopers": 25,
"numOfAvailableDevelopers": 10,
"_links": {
"getThisCapability": {
"href": "http://localhost:8099/api/v1/capability/sample"
},
"getAllCapabilities": {
"href": "http://localhost:8099/api/v1/capability/list"
},
"deleteThisCapability": {
"href": "http://localhost:8099/api/v1/capability/sample"
},
"createCapability": {
"href": "localhost:8099/api/v1/capability"
}
}
}
]
}
}
预期响应:
[
{
"id": "sample",
"techStack": "Java",
"numOfDevelopers": 25,
"numOfAvailableDevelopers": 10,
"_links": {
"getThisCapability": {
"href": "http://localhost:8099/api/v1/capability/sample"
},
"getAllCapabilities": {
"href": "http://localhost:8099/api/v1/capability/list"
}
}
}
]
我试过了
spring.data.rest.defaultMediaType = application/json
spring.hateoas.use-hal-as-default-json-media-type=false
但运气不好,我仍然能够在响应中看到 _embedded
属性。
谁能帮我找出问题所在。
我在使用 spring-boot-1.5.10
之前我可以在没有 _embedded
的情况下正确呈现链接,请参考 here。
我在主 class 中添加以下注释后工作正常,如果我 return List
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
localhost:9771/api/v1/capability/list
这会产生以下结果:
[
{
"capabilityId": "sample",
"techStack": "Java",
"numOfDevelopers": 25,
"numOfAvailableDevelopers": 10,
"_links": {
"getThisCapability": {
"href": "http://localhost:9771/api/v1/capability/sample"
},
"getAllCapabilities": {
"href": "http://localhost:9771/api/v1/capability/list"
}
}
}
]
不幸的是,它在最新版本中不起作用。
任何帮助将不胜感激。
如果我理解HAL specification correctly that would be invalid HAL, which is why Spring HATEOAS will not produce this result as long as you return a CollectionModel
in your request. Mind that it is possible for the collection to have links as well, which would be next to the _embedded
property in a _links
property as depicted in this example document.
如果你真的想摆脱 _embedded
属性 并生成一个 EntityModel<T>
的列表,那么如果你将代码修改为 [=32 它应该可以工作=]一个List<EntityModel<Capability>>
。然后,您将丢失 Spring HATEOAS 生成的 _embedded
和 _links
属性。这是您修改后的代码片段:
@RestController
@RequestMapping(value = "/api/v1/capability")
@RequiredArgsConstructor
@CrossOrigin
public class CapabilityController {
private final CapabilityService capabilityService;
private final CapabilityResourceAssembler capabilityResourceAssembler;
@GetMapping(value = "/list")
public List<EntityModel<Capability>> getAllCapabilities() {
List<EntityModel<Capability>> capabilities = capabilityService.listCapabilities().stream()
.map(capability -> new EntityModel<>(capability,
linkTo(methodOn(CapabilityController.class).getCapabilityById(capability.getId())).withRel("getThisCapability"),
linkTo(methodOn(CapabilityController.class).getAllCapabilities()).withRel("getAllCapabilities")
)).collect(Collectors.toList());
return capabilities;
}
}
我强烈建议您不要这样做,因为您会失去 Spring HATEOAS 的优势。
此外,我建议使用您的 CapabilityResourceAssembler
并通过使用 capabilityResourceAssembler.toModel(...)
创建 EntityModel<Capability>
个实例,这样您就不必重复您在.map(...)
函数。
使用 Spring Boot 1.5 时,您依赖于 Spring HATEOAS 的限制,这导致 Jackson ObjectMapper
被用作特定于 HAL 的应用范围 ObjectMapper
。这种污染意味着 HAL 格式在不应该的情况下应用于响应。
该限制已在 Spring HATEOAS 1.0 中解决,其 HAL 特定 ObjectMapper
不再污染整个应用程序。如果您希望主应用程序 ObjectMapper
应用 HAL 样式的序列化,您可以通过自定义它来选择重新加入:
@Bean
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder,
HypermediaMappingInformation mappingInformation) {
ObjectMapper objectMapper = builder.build();
mappingInformation.configureObjectMapper(objectMapper);
return objectMapper;
}
虽然我认为上述方法可行,但我会回应 Daniel 在他们的回答中提出的关于响应格式和 HAL 规范合规性的问题。
我建议使用注释为@JsonValue 的类型集合字段扩展 CollectionModel,然后在您的汇编器中将 toCollectionModel 覆盖为 return 自定义 CollectionModel
public class CustomCollectionModel<T> extends CollectionModel<CollectionModel<T>> {
@JsonValue
private Collection<T> content;
protected CustomCollectionModel() {
this(new ArrayList<>());
}
public CustomCollectionModel(Iterable<T> content, Link... links) {
this(content, Arrays.asList(links));
}
public CustomCollectionModel(Iterable<T> content, Iterable<Link> links) {
Assert.notNull(content, "Content must not be null!");
this.content = new ArrayList<>();
for (T element : content) {
this.content.add(element);
}
this.add(links);
}}
然后在你的汇编程序中
@Override
public CustomCollectionModel toCollectionModel(Iterable<? extends T> entities) {
List<EntityModel<T>> resourceList = new ArrayList<>();
for (T entity : entities) {
resourceList.add(toModel(entity));
}
return new CustomCollectionModel(resourceList);
}
这对您来说仍然是个问题吗?
通过将 false 作为参数传递给 RepositoryRestConfiguration class.
中的 useHalAsDefaultJsonMediaType 方法,我能够删除 _embedded 属性
我正在使用 spring-boot-2.2.1 和 spring-HATEOAS。超媒体链接工作正常,但
我在 returning 链接时看到 _embedded
属性,请在 github here,
端点:
a) 将 return CollectionModel
=> localhost:8099/api/v1/capability/list/noembedded
和
b) 将return列出
<?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>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring-boot-unittest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-unittest</name>
<description>Demo project for Spring Boot unit test</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-mongodb</artifactId>
<version>4.1.4</version>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>4.1.4</version>
</dependency>
<!-- Embedded MongoDB for Testing -->
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.2.21</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<!--<processor>com.querydsl.mongodb.morphia.MorphiaAnnotationProcessor</processor>-->
<processor>org.springframework.data.mongodb.repository.support.MongoAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Controller.Java
@RestController
@RequestMapping(value = "/api/v1/capability")
@RequiredArgsConstructor
@CrossOrigin
public class CapabilityController {
private final CapabilityService capabilityService;
private final CapabilityResourceAssembler capabilityResourceAssembler;
@GetMapping(value = "/list")
public CollectionModel<EntityModel<Capability>> getAllCapabilities() {
List<EntityModel<Capability>> capabilities = capabilityService.listCapabilities().stream()
.map(capability -> new EntityModel<>(capability,
linkTo(methodOn(CapabilityController.class).getCapabilityById(capability.getId())).withRel("getThisCapability"),
linkTo(methodOn(CapabilityController.class).getAllCapabilities()).withRel("getAllCapabilities")
)).collect(Collectors.toList());
return new CollectionModel<>(capabilities);
}
}
实际回复
{
"_embedded": {
"capabilityList": [
{
"id": "sample",
"techStack": "Java",
"numOfDevelopers": 25,
"numOfAvailableDevelopers": 10,
"_links": {
"getThisCapability": {
"href": "http://localhost:8099/api/v1/capability/sample"
},
"getAllCapabilities": {
"href": "http://localhost:8099/api/v1/capability/list"
},
"deleteThisCapability": {
"href": "http://localhost:8099/api/v1/capability/sample"
},
"createCapability": {
"href": "localhost:8099/api/v1/capability"
}
}
}
]
}
}
预期响应:
[
{
"id": "sample",
"techStack": "Java",
"numOfDevelopers": 25,
"numOfAvailableDevelopers": 10,
"_links": {
"getThisCapability": {
"href": "http://localhost:8099/api/v1/capability/sample"
},
"getAllCapabilities": {
"href": "http://localhost:8099/api/v1/capability/list"
}
}
}
]
我试过了
spring.data.rest.defaultMediaType = application/json
spring.hateoas.use-hal-as-default-json-media-type=false
但运气不好,我仍然能够在响应中看到 _embedded
属性。
谁能帮我找出问题所在。
我在使用 spring-boot-1.5.10
之前我可以在没有 _embedded
的情况下正确呈现链接,请参考 here。
我在主 class 中添加以下注释后工作正常,如果我 return List
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
localhost:9771/api/v1/capability/list
这会产生以下结果:
[
{
"capabilityId": "sample",
"techStack": "Java",
"numOfDevelopers": 25,
"numOfAvailableDevelopers": 10,
"_links": {
"getThisCapability": {
"href": "http://localhost:9771/api/v1/capability/sample"
},
"getAllCapabilities": {
"href": "http://localhost:9771/api/v1/capability/list"
}
}
}
]
不幸的是,它在最新版本中不起作用。 任何帮助将不胜感激。
如果我理解HAL specification correctly that would be invalid HAL, which is why Spring HATEOAS will not produce this result as long as you return a CollectionModel
in your request. Mind that it is possible for the collection to have links as well, which would be next to the _embedded
property in a _links
property as depicted in this example document.
如果你真的想摆脱 _embedded
属性 并生成一个 EntityModel<T>
的列表,那么如果你将代码修改为 [=32 它应该可以工作=]一个List<EntityModel<Capability>>
。然后,您将丢失 Spring HATEOAS 生成的 _embedded
和 _links
属性。这是您修改后的代码片段:
@RestController
@RequestMapping(value = "/api/v1/capability")
@RequiredArgsConstructor
@CrossOrigin
public class CapabilityController {
private final CapabilityService capabilityService;
private final CapabilityResourceAssembler capabilityResourceAssembler;
@GetMapping(value = "/list")
public List<EntityModel<Capability>> getAllCapabilities() {
List<EntityModel<Capability>> capabilities = capabilityService.listCapabilities().stream()
.map(capability -> new EntityModel<>(capability,
linkTo(methodOn(CapabilityController.class).getCapabilityById(capability.getId())).withRel("getThisCapability"),
linkTo(methodOn(CapabilityController.class).getAllCapabilities()).withRel("getAllCapabilities")
)).collect(Collectors.toList());
return capabilities;
}
}
我强烈建议您不要这样做,因为您会失去 Spring HATEOAS 的优势。
此外,我建议使用您的 CapabilityResourceAssembler
并通过使用 capabilityResourceAssembler.toModel(...)
创建 EntityModel<Capability>
个实例,这样您就不必重复您在.map(...)
函数。
使用 Spring Boot 1.5 时,您依赖于 Spring HATEOAS 的限制,这导致 Jackson ObjectMapper
被用作特定于 HAL 的应用范围 ObjectMapper
。这种污染意味着 HAL 格式在不应该的情况下应用于响应。
该限制已在 Spring HATEOAS 1.0 中解决,其 HAL 特定 ObjectMapper
不再污染整个应用程序。如果您希望主应用程序 ObjectMapper
应用 HAL 样式的序列化,您可以通过自定义它来选择重新加入:
@Bean
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder,
HypermediaMappingInformation mappingInformation) {
ObjectMapper objectMapper = builder.build();
mappingInformation.configureObjectMapper(objectMapper);
return objectMapper;
}
虽然我认为上述方法可行,但我会回应 Daniel 在他们的回答中提出的关于响应格式和 HAL 规范合规性的问题。
我建议使用注释为@JsonValue 的类型集合字段扩展 CollectionModel,然后在您的汇编器中将 toCollectionModel 覆盖为 return 自定义 CollectionModel
public class CustomCollectionModel<T> extends CollectionModel<CollectionModel<T>> {
@JsonValue
private Collection<T> content;
protected CustomCollectionModel() {
this(new ArrayList<>());
}
public CustomCollectionModel(Iterable<T> content, Link... links) {
this(content, Arrays.asList(links));
}
public CustomCollectionModel(Iterable<T> content, Iterable<Link> links) {
Assert.notNull(content, "Content must not be null!");
this.content = new ArrayList<>();
for (T element : content) {
this.content.add(element);
}
this.add(links);
}}
然后在你的汇编程序中
@Override
public CustomCollectionModel toCollectionModel(Iterable<? extends T> entities) {
List<EntityModel<T>> resourceList = new ArrayList<>();
for (T entity : entities) {
resourceList.add(toModel(entity));
}
return new CustomCollectionModel(resourceList);
}
这对您来说仍然是个问题吗? 通过将 false 作为参数传递给 RepositoryRestConfiguration class.
中的 useHalAsDefaultJsonMediaType 方法,我能够删除 _embedded 属性