You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring HATEOAS场景下如何调用带@RequestParam的Controller方法且不传参

问题描述

我有一个GET接口用于从仓库获取分页歌曲,接口包含page和size两个非必填的@RequestParam参数。当使用Spring HATEOAS生成全量歌曲的链接时,调用该Controller方法并传入null作为参数,得到的是带模板参数的链接(http://localhost:8080/api/songcollection/songs/{?page,size}),但我希望生成不带任何参数的纯链接(http://localhost:8080/api/songcollection/songs)。

现有Controller代码如下:

@GetMapping("/songs/")
public CollectionModel<EntityModel<SongAlbumDTO>> getAllSongs(
        @RequestParam(name = "page", required = false) Integer page,
        @RequestParam(name = "size",required = false) Integer size
) {
    if (page != null && size != null) {
        List<SongAlbumDTO> dtoSongList =  songService.findPaginatedSong(page,size).stream()
                .map(SongAlbumAdapter::toSongAlbumDTO)
                .collect(Collectors.toList());
        List<EntityModel<SongAlbumDTO>> songs = dtoSongList.stream()
                .map(songAlbum -> EntityModel.of(songAlbum,
                        linkTo(methodOn(SongAlbumController.class).getSongById(songAlbum.getId())).withSelfRel(),
                        linkTo(methodOn(SongAlbumController.class).getAllSongs(null,null)) // 这里需要生成无参数链接
                                .withRel("songs"))).collect(Collectors.toList());
        return CollectionModel.of(songs,
                linkTo(methodOn(SongAlbumController.class).getAllSongs(null, null)).withSelfRel()); // 这里需要生成无参数链接
    }
    else {
        List<SongAlbumDTO> list = songService.getAllSongs().stream()
                .map(SongAlbumAdapter::toSongAlbumDTO)
                .collect(Collectors.toList());
        List<EntityModel<SongAlbumDTO>> songs = list.stream()
                .map(songAlbum -> EntityModel.of(songAlbum,
                        linkTo(methodOn(SongAlbumController.class).getSongById(songAlbum.getId())).withSelfRel(),
                        linkTo(methodOn(SongAlbumController.class).getAllSongs(null, null)).withRel("songs"))).collect(Collectors.toList()); // 这里需要生成无参数链接
        return CollectionModel.of(songs,
                linkTo(methodOn(SongAlbumController.class).getAllSongs(null,null)).withSelfRel()); // 这里需要生成无参数链接
    }
}

当前返回的链接格式:

"_links": {
                "self": {
                    "href": "http://localhost:8080/api/songcollection/songs/1/"
                },
                "songs": {
                    "href": "http://localhost:8080/api/songcollection/songs/{?page,size}",
                    "templated": true
                }
            }

期望的songs链接:

http://localhost:8080/api/songcollection/songs
解决方案

方案1:直接用linkTo路径构建,跳过methodOn

Spring HATEOAS的linkTo支持直接通过Controller类+路径构建链接,不需要调用带参数的方法,这样就能生成无参数的纯路径:

把所有生成链接的代码,比如:

linkTo(methodOn(SongAlbumController.class).getAllSongs(null,null)).withRel("songs")

替换为:

linkTo(SongAlbumController.class).slash("songs").withRel("songs")

如果你的Controller类上有@RequestMapping("/api/songcollection")这类前缀,也可以用:

linkTo(methodOn(SongAlbumController.class).getClass()).slash("songs").withRel("songs")

这样生成的链接就会是不带参数的目标地址。

方案2:新增无参数接口方法,共用路径

为全量查询单独写一个无参数的方法,和分页方法映射到同一个路径,调用这个无参数方法生成链接时,就不会带模板参数:

// 新增无参数全量查询方法
@GetMapping("/songs/")
public CollectionModel<EntityModel<SongAlbumDTO>> getAllSongs() {
    List<SongAlbumDTO> list = songService.getAllSongs().stream()
            .map(SongAlbumAdapter::toSongAlbumDTO)
            .collect(Collectors.toList());
    List<EntityModel<SongAlbumDTO>> songs = list.stream()
            .map(songAlbum -> EntityModel.of(songAlbum,
                    linkTo(methodOn(SongAlbumController.class).getSongById(songAlbum.getId())).withSelfRel(),
                    linkTo(methodOn(SongAlbumController.class).getAllSongs()).withRel("songs")))
            .collect(Collectors.toList());
    return CollectionModel.of(songs,
            linkTo(methodOn(SongAlbumController.class).getAllSongs()).withSelfRel());
}

// 保留原有的分页方法
@GetMapping("/songs/")
public CollectionModel<EntityModel<SongAlbumDTO>> getAllSongs(
        @RequestParam(name = "page", required = false) Integer page,
        @RequestParam(name = "size",required = false) Integer size
) {
    if (page != null && size != null) {
        List<SongAlbumDTO> dtoSongList =  songService.findPaginatedSong(page,size).stream()
                .map(SongAlbumAdapter::toSongAlbumDTO)
                .collect(Collectors.toList());
        List<EntityModel<SongAlbumDTO>> songs = dtoSongList.stream()
                .map(songAlbum -> EntityModel.of(songAlbum,
                        linkTo(methodOn(SongAlbumController.class).getSongById(songAlbum.getId())).withSelfRel(),
                        linkTo(methodOn(SongAlbumController.class).getAllSongs()).withRel("songs")))
                .collect(Collectors.toList());
        return CollectionModel.of(songs,
                linkTo(methodOn(SongAlbumController.class).getAllSongs(page, size)).withSelfRel());
    } else {
        // 无参数时直接调用全量方法
        return getAllSongs();
    }
}

此时调用methodOn(SongAlbumController.class).getAllSongs()生成的就是无参数的链接。

用UriComponentsBuilder手动拼接路径,再转换成Spring HATEOAS的Link对象,适合路径固定的场景:

// 在需要生成链接的位置,手动构建路径
UriComponents uriComponents = ServletUriComponentsBuilder.fromCurrentContextPath()
        .path("/api/songcollection/songs")
        .build();
Link songsLink = Link.of(uriComponents.toUriString()).withRel("songs");

把这个songsLink替换原来用linkTo生成的链接即可。

内容的提问来源于stack exchange,提问作者varo111

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 11:46:04