繁体   English   中英

Spring-boot + REST + HATEOAS + HAL

[英]Spring-boot + REST + HATEOAS + HAL

我按照spring.io Pivotal教程获得了一个带有MySQL DB的REST API,并且事情进展顺利。 但是我发现了一个我无法配置或解决的行为。

当我使用内置功能从PagingAndSortingRepository检索我的资源时,生成的REST会自动分页并使用有用的HAL链接封装 (_links,self,search,linked Resources等)。 我想用它。

当我实现我的Controller以自定义PostMapping行为并引入完整性检查,验证等时,GetMapping停止工作。 所以我重新实现了一个利用我的服务层的GetMapping。

不幸的是,这样做打破了之前提供的HATEOAS。

我想要的是能够自定义PostMapping,但保持GetMapping与默认值完全相同。 如果可能的话,我很乐意避免自己编写,因为我知道框架可以提供它。

有办法吗?

控制器:

@RestController
public class PartyMemberController {

    @Autowired
    PartyMemberService partyMemberService;

    @RequestMapping(method = RequestMethod.GET, value = "/partyMembers")
    public ResponseEntity<Iterable<PartyMember>> getAllPartyMembers() {
        Iterable<PartyMember> partyMemberList = partyMemberService.getAll();
        return new ResponseEntity<>(partyMemberList, HttpStatus.OK);
    }

    @RequestMapping(method = RequestMethod.POST, value = "/partyMembers")
    public ResponseEntity<PartyMember> addEmployee(@Valid @RequestBody PartyMember partyMember) {
        if (partyMemberService.exists(partyMember)) {
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        }

        partyMember = partyMemberService.save(partyMember);
        return new ResponseEntity<PartyMember>(partyMember, HttpStatus.CREATED);
    }
}

结果JSON

    [
      {
        "id": 2,
        "ward": {
          "id": 1,
          "name": "Mercier",
          "wardNumber": 42,
          "numberOfMembers": 3
        },
        "firstName": "Cindy",
        "lastName": "Tremblay",
        "partyMemberId": "12-1234-09876",
        "primaryPhone": "514-555-2323",
        "postalAddress": "1155 Robert-Bourassa, Montreal, Quebec, Canada, H3B3A7",
        "emailAddress": null,
        "secondaryPhone": null,
        "bestTimeToContact": null,
        "bestWayToContact": null,
        "membershipExpiry": null,
        "dateOfBirth": null,
        "donationEntries": [],
        "note": null
      },
      {
        "id": 3,
        "ward": {
          "id": 1,
          "name": "Mercier",
          "wardNumber": 42,
          "numberOfMembers": 3
        },
        "firstName": "Robert",
        "lastName": "Paulson",
        "partyMemberId": "12-1234-54321",
        "primaryPhone": "514-555-1212",
        "postalAddress": "440 Rue Saint-Pierre, App 5, Montreal, Quebec, Canada, H2Y2M5",
        "emailAddress": "rpaulson@papermillsoapcompany.com",
        "secondaryPhone": null,
        "bestTimeToContact": null,
        "bestWayToContact": null,
        "membershipExpiry": null,
        "dateOfBirth": null,
        "donationEntries": [],
        "note": null
      },
      {
        "id": 4,
        "ward": {
          "id": 1,
          "name": "Mercier",
          "wardNumber": 42,
          "numberOfMembers": 3
        },
        "firstName": "Richard",
        "lastName": "Schnobb",
        "partyMemberId": "12-4321-09876",
        "primaryPhone": "514-555-2323",
        "postalAddress": "440 Rue Saint-Pierre, App 5, Montreal, Quebec, Canada, H2Y2M5",
        "emailAddress": null,
        "secondaryPhone": null,
        "bestTimeToContact": null,
        "bestWayToContact": null,
        "membershipExpiry": null,
        "dateOfBirth": null,
        "donationEntries": [],
        "note": null
      }
    ]

默认JSON(注意_embedded,_links等)。 这就是我想要的结果。

{
  "_embedded" : {
    "partyMembers" : [ {
      "firstName" : "Cindy",
      "lastName" : "Tremblay",
      "partyMemberId" : "12-1234-09876",
      "primaryPhone" : "514-555-2323",
      "postalAddress" : "1155 Robert-Bourassa, Montreal, Quebec, Canada, H3B3A7",
      "emailAddress" : null,
      "secondaryPhone" : null,
      "bestTimeToContact" : null,
      "bestWayToContact" : null,
      "membershipExpiry" : null,
      "dateOfBirth" : null,
      "donationEntries" : [ ],
      "note" : null,
      "_links" : {
        "self" : {
          "href" : "http://127.0.0.1:8080/partyMembers/2"
        },
        "partyMember" : {
          "href" : "http://127.0.0.1:8080/partyMembers/2"
        },
        "ward" : {
          "href" : "http://127.0.0.1:8080/partyMembers/2/ward"
        }
      }
    }, {
      "firstName" : "Robert",
      "lastName" : "Paulson",
      "partyMemberId" : "12-1234-54321",
      "primaryPhone" : "514-555-1212",
      "postalAddress" : "440 Rue Saint-Pierre, App 5, Montreal, Quebec, Canada, H2Y2M5",
      "emailAddress" : "rpaulson@papermillsoapcompany.com",
      "secondaryPhone" : null,
      "bestTimeToContact" : null,
      "bestWayToContact" : null,
      "membershipExpiry" : null,
      "dateOfBirth" : null,
      "donationEntries" : [ ],
      "note" : null,
      "_links" : {
        "self" : {
          "href" : "http://127.0.0.1:8080/partyMembers/3"
        },
        "partyMember" : {
          "href" : "http://127.0.0.1:8080/partyMembers/3"
        },
        "ward" : {
          "href" : "http://127.0.0.1:8080/partyMembers/3/ward"
        }
      }
    }, {
      "firstName" : "Richard",
      "lastName" : "Schnobb",
      "partyMemberId" : "12-4321-09876",
      "primaryPhone" : "514-555-2323",
      "postalAddress" : "440 Rue Saint-Pierre, App 5, Montreal, Quebec, Canada, H2Y2M5",
      "emailAddress" : null,
      "secondaryPhone" : null,
      "bestTimeToContact" : null,
      "bestWayToContact" : null,
      "membershipExpiry" : null,
      "dateOfBirth" : null,
      "donationEntries" : [ ],
      "note" : null,
      "_links" : {
        "self" : {
          "href" : "http://127.0.0.1:8080/partyMembers/4"
        },
        "partyMember" : {
          "href" : "http://127.0.0.1:8080/partyMembers/4"
        },
        "ward" : {
          "href" : "http://127.0.0.1:8080/partyMembers/4/ward"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://127.0.0.1:8080/partyMembers{?page,size,sort}",
      "templated" : true
    },
    "profile" : {
      "href" : "http://127.0.0.1:8080/profile/partyMembers"
    },
    "search" : {
      "href" : "http://127.0.0.1:8080/partyMembers/search"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 3,
    "totalPages" : 1,
    "number" : 0
  }
}

为了让Spring为您完成大部分工作,您应该使用注释@RepositoryRestController 文档中有更多解释:

有时,您可能希望为特定资源编写自定义处理程序。 要利用Spring Data REST的设置,消息转换器,异常处理等,请使用@RepositoryRestController注释而不是标准的Spring MVC @Controller或@RestController。 使用@RepositoryRestController注释的控制器是从RepositoryRestConfiguration.setBasePath中定义的API基本路径提供的,该路径由所有其他RESTful端点(例如,/ api)使用。


为了简化HATEOAS资源的生成(使用您提到的_links ),您可以利用ResourceAssemblerSupport

由于必须在多个位置使用从实体到资源类型的映射,因此创建负责这样做的专用类是有意义的。 转换当然包含非常自定义的步骤,但也包含一些样板步骤。 (...)Spring Hateoas现在提供了一个ResourceAssemblerSupport基类,有助于减少需要编写的代码量

控制器上的返回类型不是Spring HATEOAS ResourceSupport类型,因此永远不会调用HAL序列化程序。

您应该返回Resource<PartyMember> (对于单个项目)或Resources<Resource<PartyMember>> (对于列表)。 构建您的控制器以返回这些。

进行这些更改,然后将可重用的ResourceAssembler<PartyMember, Resource<PartyMember>>的概念作为将服务层中找到的PartyMember对象转换为用于呈现超媒体的Resource<PartyMember>对象的方法将非常有意义。

要查看基于REST的服务的演变,请查看本教程(几个月前我重写了以正确显示Spring HATEAOS的用法)=> https://spring.io/guides/tutorials/rest/

你对ResponseEntity的控制很糟糕,所以它不再为你自动构建。 这意味着您必须使用LinkBuilder或ControllerLinkBuilder来构建与ResponseEntity相关联的链接。

这里的文档和示例: https//docs.spring.io/spring-hateoas/docs/current/reference/html/#fundamentals.obtaining-links.entity-links

快速样本:

@Getter 
public class PersonResource extends ResourceSupport {
    private final Person person;
    public PersonResource(final Person person) {
        this.person = person;
        final long id = person.getId();
        add(linkTo(PersonController.class).withRel("people"));
        add(linkTo(methodOn(GymMembershipController.class).all(id)).withRel("memberships"));
        add(linkTo(methodOn(PersonController.class).get(id)).withSelfRel());
    }
}

从这个最优秀的tut: https//dzone.com/articles/applying-hateoas-to-a-rest-api-with-spring-boot

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM