3

我正在尝试编写一个通用函数来执行一些 Webflux 操作,但我遇到了一个我无法弄清楚的类转换异常

     * @param <T> Type of the contract
     * @param <U> Return type of this method
     * @param <V> Return type from the service
public <T, U, V> U sendRequest(String url, T contract, Function<V, U> transform) {

        ParameterizedTypeReference<T> contractType = new ParameterizedTypeReference<T>() {};
        ParameterizedTypeReference<V> returnType = new ParameterizedTypeReference<V>() {};
        final WebClient.ResponseSpec foo = webClient.post()
                .uri(url)
                .body(Mono.just(contract), contractType)
                .retrieve();

        Mono<V> mono =  foo.bodyToMono(returnType);

      final Mono<U> trans = mono.map(m -> transform.apply(m));
      return trans.block();
}

此代码以其非通用形式正常工作。但是当我用这样的东西调用这个泛型方法时

    requestRunner.<Contract, String, ViewModel>sendRequest(url, contract, v->(String)v.getResult().get("outputString"));

我得到一个例外:

java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.torchai.service.common.ui.ViewModel
    at com.torchai.service.orchestration.service.RequestRunner.lambda$sendRequest$0(RequestRunner.java:44)

我正在运行 SpringBoot 2.4.5 版,所以我认为这不适用:https ://github.com/spring-projects/spring-framework/issues/20574

只是为了更多的上下文,在上面的示例中,ViewModel(通用类型<V>)是服务返回其数据的格式。然后我只提取我需要的部分,在这种情况下是一个字符串(通用类型<U>)传入的 lambda 函数从响应中获取相关字符串。但由于某种原因,Mono 没有正确映射到 ViewModel。如果我取出 map() 并返回 ViewModel,它似乎可以工作。

同样,如果我以非通用方式执行此操作,则效果很好。我可以执行 map() 步骤并正确返回一个字符串

更新

只是想明确一点,这适用于像这样的非通用版本:

public String sendRequest(String url, Contract contract, Function<ViewModel, String> transform) {

        ParameterizedTypeReference<Contract> contractType = new ParameterizedTypeReference<Contract>() {};
        ParameterizedTypeReference<ViewModel> returnType = new ParameterizedTypeReference<ViewModel>() {};
        final WebClient.ResponseSpec foo = webClient.post()
                .uri(url)
                .body(Mono.just(contract), contractType)
                .retrieve();

        Mono<ViewModel> mono = foo.bodyToMono(returnType);

        final Mono<String> trans = mono.map(m -> transform.apply(m));
        return trans.block();
}

它被称为这种方式

requestRunner.<Contract, String, ViewModel>sendRequest(textExtractorUrl, cloudContract, v -> (String) v.getResult().get("outputString"));

它正确返回一个字符串,这正是我想要的通用版本

4

2 回答 2

1

我能够在本地重现此问题并开始一些调试,但很难看到 webflux 代码中发生了什么/在哪里发生了什么。

的类型声明ViewModelV在调用者处知道,但在方法中不知道sendRequest。看来使用ParameterizedTypeReference<V>是个问题。在这种情况下V,它只是真实类型的通用占位符,所以 spring 只是捕获了VaParameterizedTypeReference并且不知道应该将响应反序列化为ViewModel. 相反,它会尝试为 type 找到一个反序列化器,V并且它可以找到的最接近的是 type LinkedHashMap。因此,在您的情况下,您最终得到了一个顶级LinkedHashMap(而不是),其中包含一个包含您的结果条目的 type 值的ViewModel键。这就是为什么你得到.resultLinkedHashMapClassCastException

我已经删除ParameterizedTypeReference<V>并使用了显式Class<V>版本,它可以工作。在这个版本中,您不必自己放入泛型类型,只需为方法提供参数,泛型会自动从上下文派生。

public <T, U, V> U sendRequest(String url, T contract, Class<V> responseType, Function<V, U> transform)
{
    WebClient.ResponseSpec foo = webClient.post()
            .uri(url)
            .body(Mono.just(contract), contract.getClass())
            .retrieve();
    Mono<V> mono = foo.bodyToMono(responseType);
    Mono<U> trans = mono.map(transform);
    return trans.block();
}

称呼:

requestRunner.sendRequest(url, contract, ViewModel.class, v -> (String) v.getResult().get("outputString"));

如果有人想进行更深入的调查,这是我最小的可重现示例。

pom.xml

<?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.4.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

DemoApplication.java

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

控制器.java

@RestController
public class Controller
{
    private static class ViewModel
    {
        private LinkedHashMap<String, Object> result = new LinkedHashMap<>();

        public LinkedHashMap<String, Object> getResult()
        {
            return result;
        }

        public void setResult(LinkedHashMap<String, Object> result)
        {
            this.result = result;
        }
    }

    @PostMapping("/fetchViewModel")
    public ViewModel fetchViewModel()
    {
        ViewModel result = new ViewModel();
        result.getResult().put("outputString", "value");
        return result;
    }

    @GetMapping("/start")
    public Mono<String> startSuccess()
    {
        return this.sendRequest("fetchViewModel", "contract", ViewModel.class, v -> (String) v.getResult().get("outputString"));
    }

    private <T, U, V> Mono<U> sendRequest(String url, T contract, Class<V> responseType, Function<V, U> transform)
    {
        WebClient webClient = WebClient.create("http://localhost:8080/");
        WebClient.ResponseSpec foo = webClient.post()
                .uri(url)
                .body(Mono.just(contract), contract.getClass())
                .retrieve();
        Mono<V> mono = foo.bodyToMono(responseType);
        Mono<U> trans = mono.map(transform);
        return trans;
    }

    @GetMapping("/startClassCastException")
    public Mono<String> startClassCastException()
    {
        return this.<String, String, ViewModel> sendRequestClassCastException("fetchViewModel", "contract", v -> (String) v.getResult().get("outputString"));
    }

    private <T, U, V> Mono<U> sendRequestClassCastException(String url, T contract, Function<V, U> transform)
    {
        ParameterizedTypeReference<T> contractType = new ParameterizedTypeReference<T>() {};
        ParameterizedTypeReference<V> responseType = new ParameterizedTypeReference<V>() {};
        WebClient webClient = WebClient.create("http://localhost:8080/");
        WebClient.ResponseSpec foo = webClient.post()
                .uri(url)
                .body(Mono.just(contract), contractType)
                .retrieve();
        Mono<V> mono = foo.bodyToMono(responseType);
        Mono<U> trans = mono.map(transform);  // ClassCastException in Lambda
        return trans;
    }
}
于 2021-06-05T18:28:09.513 回答
0

这很有效,因此您可能不会ViewModel从您的 webClient 正文中获得回复,而是mapViewModel.

public class Play {
    private ViewModel viewModel = new ViewModel(); 
    public static void main(String[] args) {
        new Play().run();
    }
    static class ViewModel {
        private Map<String, Object> result;
        public ViewModel() {
            result = new LinkedHashMap<>(); 
            result.put("outputString", "acb");
        }
        public Map<String, Object> getResult() {
            return result;
        }
    }
    private void run() {
        this.<String, String, ViewModel>sendRequest("http", "contract", v->(String)v.getResult().get("outputString"))
        .subscribe(System.out::println);
    }
    public <T, U, V> Mono<U> sendRequest(String url, T contract, Function<V, U> transform) {
        @SuppressWarnings("unchecked")
        Mono<V> mono =  Mono.just((V)viewModel);

      final Mono<U> trans = mono.map(m -> transform.apply(m));
      return trans; 
    }
}

即,如果我更改为:

        @SuppressWarnings("unchecked")
        Mono<V> mono =  Mono.just((V)viewModel.getResult());

然后我得到

reactor.core.Exceptions$ErrorCallbackNotImplemented:java.lang.ClassCastException:java.util.LinkedHashMap 无法转换为fluxjunk.Play$ViewModel 原因:java.lang.ClassCastException:java.util.LinkedHashMap 无法转换为fluxjunk.Play$ Fluxjunk.Play.lambda$2(Play.java:32) 的 ViewModel

于 2021-06-05T18:18:17.790 回答