在JSON中禁用超文本应用语言(HAL)?

50

在版本为2.0.2.RELEASE中使用Spring Data REST以及JPA。

我该如何在JSON中禁用Hypertext Application Language(HAL)?http://stateless.co/hal_specification.html

我已经尝试了很多方法,但都无济于事。例如,我将Accept和Content-type头设置为“application/json”,而不是“application/hal+json”,但仍然收到带有超链接的JSON内容。

例如,我想要得到类似这样的东西:

{
"name" : "Foo",
"street" : "street Bar",
"streetNumber" : 2,
"streetLetter" : "b",
"postCode" : "D-1253",
"town" : "Munchen",
"country" : "Germany",
"phone" : "+34 4410122000",
"vat" : "000000001",
"employees" : 225,
"sector" : {
     "description" : "Marketing",
     "average profit": 545656665,
     "average employees": 75,
     "average profit per employee": 4556
     }
}

改为:

{
"name" : "Foo",
"street" : "street Bar",
"streetNumber" : 2,
"streetLetter" : "b",
"postCode" : "D-1253",
"town" : "Munchen",
"country" : "Germany",
"phone" : "+34 4410122000",
"vat" : "000000001",
"employees" : 225,
"_links" : {
     "self" : {
          "href" : "http://localhost:8080/app/companies/1"
     },
     "sector" : {
          "href" : "http://localhost:8080/app/companies/1/sector"
     }
}
}

感谢你的帮助。

3个回答

43

(超文本)媒体类型

Spring Data REST的默认设置使用HAL作为默认超媒体表示格式,因此服务器将根据给定的Accept标头返回以下内容:

  • 无标头 -> application/hal+json -> HAL
  • application/hal+json -> application/hal+json -> HAL
  • application/json -> application/json -> HAL (这是默认配置)
  • application/x-spring-data-verbose+json -> application/x-spring-data-verbose+json -> Spring Data特定格式(使用links作为链接容器和content作为集合项的包装器。

如果您将RepositoryRestConfiguration.setDefaultMediaType(…)配置为非HAL格式,则服务器将返回Spring Data特定的JSON格式,除非您明确要求application/hal+json。不可否认,配置选项可能有点误导人,因此我提交了DATAREST-294来改进此问题。该问题在2014年的2.1 RC1(Dijkstra)中得到解决。

请注意,我们实际上需要一个超媒体格式来能够表达受管理资源之间的关系并启用服务器的可发现性。因此,您将无法完全摆脱它。这主要是由于您可能会轻松地暴露具有双向关系或组成庞大对象图的实体而导致服务器崩溃。

内联相关实体

如果您从未想将部门链接在一起并始终将其内联,则一种方法是首先排除SectorRepository作为REST资源进行导出。您可以通过使用@RepositoryRestResource(exported = false)注释存储库接口来实现此目的。

要获得返回为您在下面示例中发布的表示,请查看Spring Data REST 2.1 M1中引入的投影功能。它基本上允许您通过一个简单的接口来创建资源的可选视图,这些视图可以与默认视图不同。

您将基本上定义一个接口:

@Projection(name = "foo", types = YourDomainClass.class)
interface Inlined {

  // list all other properties

  Sector getSector();
}
如果您将此接口放入您的域类的(子)包中或通过RepositoryRestConfiguration.projectionConfiguration()手动注册,则公开YourDomainClass的资源将接受一个请求参数projection,因此在此示例中传递foo会呈现内联表示形式,就像您想要的那样。

此提交总体上有关于此功能的更多信息,此提交定义了一个示例投影。


2
这可能是由于反射API以不同的顺序返回方法所致。但是,通过Jackson注释,投影接口是开放的,因此@JsonPropertyOrder应该允许您根据需要自定义内容。 - Oliver Drotbohm
19
是否可以完全禁用 HAL?我只想检索带有实体ID的JSON。 - Luis Vargas
8
你不觉得这有点儿傻吗?如果我有100个模型,我就需要创建100个投影。我不这么认为。我正在寻找一个配置标志,它可以让我发送Json或HalJson,而不仅仅是HalJson。 - Luis Vargas
1
嗨,当我使用头文件:application/x-spring-data-verbose+json时,内容列表仍然为空。我不知道我做错了什么。更详细的问题在这里:https://dev59.com/z4nda4cB1Zd3GeqPDb9r - Yannic Bürgmann
13
SDR的超媒体本质可能存在问题,因为简单的REST API(即Richardson成熟度模型的第二级)已经是主流和生产实践证明了的。对于超媒体API(第三级)的可用性仍有争议。将SDR与超媒体绑定似乎注定它只能作为实验性质,让需要生产就绪API的人难以接受。或者我有什么遗漏吗? - roger armstrong
显示剩余8条评论

4

所以你想要两件事情:

1)去掉_links字段
2)包括相关的sector字段

可能的解决方案(我用这个方法解决了 :D)

1)去掉_links
为此,请创建以下类:

[... package declaration, imports ...]
public class MyRepositoryRestMvcConfiguration extends RepositoryRestMvcConfiguration {
    public MyRepositoryRestMvcConfiguration(ApplicationContext context, ObjectFactory<ConversionService> conversionService) {
        super(context, conversionService);
    }

    @Bean
    protected LinkCollector linkCollector() {
        return new LinkCollector(persistentEntities(), selfLinkProvider(), associationLinks()) {
            public Links getLinksFor(Object object, List<Link> existingLinks) {
                return new Links();
            }
        };
    }
}

并且可以使用它,例如:

[... package declaration, imports ...]
@SpringBootApplication
@Import({MyRepositoryRestMvcConfiguration.class})
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

我相信(99%,但未经测试)您不需要这个类来删除与_links相关的实体/实体的包含方式如下一点(2)所示。

2)包含相关的sector字段
为此,您可以使用Excerpts(专为此场景设计)。 因为Spring的示例非常简洁,只是将其复制到这里是很傻的。 我会直接指向:https://docs.spring.io/spring-data/rest/docs/3.1.x/reference/html/#projections-excerpts.excerpting-commonly-accessed-data
但仅供记录和方便起见,我将粘贴Spring示例的主要部分:

@Projection(name = "inlineAddress", types = { Person.class }) 
interface InlineAddress {
  String getFirstName();
  String getLastName();
  Address getAddress(); 
}

请参阅Projection javadoc,其中types表示投影类型绑定的类型
可以这样使用摘录
@RepositoryRestResource(excerptProjection = InlineAddress.class)
interface PersonRepository extends CrudRepository<Person, Long> {}

为了能够获得这个(同时使用 MyRepositoryRestMvcConfiguration 时):
{
  "firstName" : "Frodo",
  "lastName" : "Baggins",
  "address" : { 
    "street": "Bag End",
    "state": "The Shire",
    "country": "Middle Earth"
  }
}

对于您来说,sector 相当于 address

最后说明

返回数组时,_links 字段不会被删除(这样做太过冒进);最终你将得到以下结果:

{
    "_embedded" : {
        "persons" : [ {person1}, {person2}, ..., {personN} ]
    },
    "_links" : {
        e.g. first, next, last, self, profile
    },
    "page" : {
      "size" : 1,
      "totalElements" : 10,
      "totalPages" : 10,
      "number" : 0
    }
}

如您所见,即使我们删除了_links,这仍然不足够; 可能还希望将_embedded替换为persons,这会导致代码难以维护(过多的Spring侵入式覆盖)。但是,如果确实需要这些,应该开始检查RepositoryRestMvcConfigurationRepositoryEntityController.getCollectionResource
Spring正在不断发展,因此我觉得有必要指出这至少可以与以下版本一起使用:
spring-data-rest-webmvc 3.1.3.RELEASE

或者,如果您更喜欢Spring Boot版本:
spring-boot-starter-parent 2.1.1.RELEASE

我喜欢你的回答,它为那些不了解摘录的人提供了一个很好的教程,可能会帮助其他人。然而这个问题可以追溯到2014年,现在我已经用更加优雅的方式通过Projections实现了自己的需求,正如Spring Data项目负责人Oliver Drotbohm的回答所说。我刚刚检查了我的代码:为了包含相关的_sector_字段,我的主Projection返回的是SectorProjection而不是Sector实体。通过这种方法,我根本就不需要_links_。如果想保留_links_,你的回答也是有效的。 - jplandrain
1
感谢您的赞赏:)。在第一个点中,我展示了如何删除_links字段;这确实对于数组不起作用,但是通过投影解决方案,我非常确定(99%)它是相同的(也适用于_embedded和page)。 - Adrian
1
是的,最后它们非常相似。但我只能选择一个“好”的答案,抱歉。我相信你的回答会受到社区的赞赏。 - jplandrain
1
从Spring Data Rest 3.6.0开始,LinkCollector现在更容易定制化了:https://github.com/spring-projects/spring-data-rest/commit/161a3a9499f371aa62961193cebb8a9afa9cb3d9 - Dario Seidl

1
如果您想删除_links,请按照以下步骤进行操作(对我很有效):
  1. 进入您的pom.xml并删除以下依赖项:

    spring-boot-starter-data-rest

  2. 进行“更新项目”以更新pom.xml更改。

现在它将使用您自己的控制器来处理api rest,删除_self、_links等内容,类似于这样:
[... package declaration, imports ...]
@RestController
@RequestMapping("/series")
public class SerieController {
    @Autowired
    private SerieRepositorio serieRepositorio;
    
    public SerieController(SerieRepositorio serieRepositorio) {
        this.serieRepositorio = serieRepositorio;
    }

    @GetMapping
    public Iterable<Serie> getAllSeries() {
        return serieRepositorio.findAll();
    }
   }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接