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

泛型方法中如何为ParameterizedTypeReference<T>指定T的实际类型?

问题描述

控制器定义了以下方法:

Mono<EntityModel<Some>> readSome(...) {
    // works, simply.
}

Flux<EntityModel<Other>> readOther(...) {
    // works, simply.
}

测试类中原本的测试方法可以正常工作:

// WORKS!
static List<EntityModel<Some>> readSome(...) {
    client...
            .exchange()
            .expectStatus().isOk()
            .expectBodyList(new TypeReferences.EntityModelType<Some>() {
            })
            .returnResult()
            .getResponseBody();
}

// WORKS!
static List<EntityModel<Other>> readOther(...) {
    client...
            .exchange()
            .expectStatus().isOk()
            .expectBodyList(new TypeReferences.EntityModelType<Other>() {
            })
            .returnResult()
            .getResponseBody()
}

但尝试封装泛型方法后,Jackson无法将响应解析为实际类型,而是解析成了LinkedHashMap:

private static <T> List<EntityModel<T>> readList(final WebTestClient client,
                                                 final Function<UriBuilder, URI> uriFunction,
                                                 @Nullable final String accept) {
    final var responseBody = client
            .get()
            .uri(uriFunction)
            .headers(h -> {
                Optional.ofNullable(accept)
                        .map(MediaType::valueOf)
                        .map(List::of)
                        .ifPresent(h::setAccept);
            })
            .exchange()
            .expectStatus().isOk()
            .expectBodyList(new TypeReferences.EntityModelType<T>() { // 无法识别T的实际类型,解析为Map
            })
            .returnResult()
            .getResponseBody();
    return Objects.requireNonNull(responseBody, "responseBody is null");
}

需要解决:如何结合Class<T>与TypeReferences.EntityModelType<T>指定T的实际类型,让Jackson正确解析?


解决方案

核心问题是泛型类型擦除——编译后T的实际类型信息会丢失,导致Jackson无法推断目标类型,只能 fallback 到LinkedHashMap。以下是几种可行的解决方式:

方式1:传入Class<T>并构造带类型信息的TypeReference

修改泛型方法,添加Class<T>参数,通过该类对象构造保留实际类型的EntityModelType:

private static <T> List<EntityModel<T>> readList(final WebTestClient client,
                                                 final Function<UriBuilder, URI> uriFunction,
                                                 @Nullable final String accept,
                                                 final Class<T> elementType) {
    final var responseBody = client
            .get()
            .uri(uriFunction)
            .headers(h -> {
                Optional.ofNullable(accept)
                        .map(MediaType::valueOf)
                        .map(List::of)
                        .ifPresent(h::setAccept);
            })
            .exchange()
            .expectStatus().isOk()
            // 基于传入的Class构造带实际类型的TypeReference
            .expectBodyList(new TypeReferences.EntityModelType<T>() {
                @Override
                public Type getType() {
                    return TypeUtils.parameterize(EntityModel.class, elementType);
                }
            })
            .returnResult()
            .getResponseBody();
    return Objects.requireNonNull(responseBody, "responseBody is null");
}

调用时传入对应类型的Class:

List<EntityModel<Some>> someList = readList(client, uriBuilder -> ..., null, Some.class);
List<EntityModel<Other>> otherList = readList(client, uriBuilder -> ..., null, Other.class);

方式2:直接传入完整类型的TypeReference

让调用方传入包含完整泛型信息的TypeReference,避免类型擦除问题:

private static <T> List<EntityModel<T>> readList(final WebTestClient client,
                                                 final Function<UriBuilder, URI> uriFunction,
                                                 @Nullable final String accept,
                                                 final TypeReference<List<EntityModel<T>>> typeReference) {
    final var responseBody = client
            .get()
            .uri(uriFunction)
            .headers(h -> {
                Optional.ofNullable(accept)
                        .map(MediaType::valueOf)
                        .map(List::of)
                        .ifPresent(h::setAccept);
            })
            .exchange()
            .expectStatus().isOk()
            .expectBody(typeReference)
            .returnResult()
            .getResponseBody();
    return Objects.requireNonNull(responseBody, "responseBody is null");
}

调用示例:

List<EntityModel<Some>> someList = readList(client, uriBuilder -> ..., null, 
    new TypeReference<List<EntityModel<Some>>>() {});

方式3:利用Spring的ParameterizedTypeReference

Spring提供的ParameterizedTypeReference可以更简洁地处理泛型类型:

private static <T> List<EntityModel<T>> readList(final WebTestClient client,
                                                 final Function<UriBuilder, URI> uriFunction,
                                                 @Nullable final String accept,
                                                 final ParameterizedTypeReference<List<EntityModel<T>>> typeRef) {
    final var responseBody = client
            .get()
            .uri(uriFunction)
            .headers(h -> {
                Optional.ofNullable(accept)
                        .map(MediaType::valueOf)
                        .map(List::of)
                        .ifPresent(h::setAccept);
            })
            .exchange()
            .expectStatus().isOk()
            .expectBody(typeRef)
            .returnResult()
            .getResponseBody();
    return Objects.requireNonNull(responseBody, "responseBody is null");
}

调用示例:

List<EntityModel<Some>> someList = readList(client, uriBuilder -> ..., null,
    new ParameterizedTypeReference<List<EntityModel<Some>>>() {});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 17:55:17