报错背景描述 组件版本信息:
Spring Cloud:2021.0.5
Spring Cloud Alibaba:2021.0.5.0
Nacos:2.2.3
项目采用基于Spring Cloud Alibaba + Nacos的微服务架构,生产环境部署时服务部署到阿里云ACK容器集群中,并使用阿里云MSE云原生网关作为接入层。 出于成本考虑,在测试环境并未直接采购阿里云ACK集群和MSE云原生网关,所以使用Spring Cloud Gateway作为测试环境的网关服务。生产环境部署时,在MSE云原生网关处对请求接口做了全局鉴权,因此也需要在Spring Cloud Gateway服务做相同的事情,如下所示:
当网关接收到客户端请求服务A的接口时,先要到鉴权服务校验请求参数是否正确(如:对于需要登录才能访问的接口,必须携带正确的token参数)。如果鉴权失败,在网关处直接将错误信息响应给客户端;只有鉴权成功后,网关再将请求转发给服务A,并将服务A的响应结果返回给客户端。
由于网关和服务A都注册到了同一个Nacos注册中心,因此网关可以通过Feign框架发起基于HTTP协议的RPC调用,添加如下依赖配置:
1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-loadbalancer</artifactId > </dependency >
另外,在发起对鉴权服务的HTTP接口调用时,还需要使用“异步转同步”的思路:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 RpcResult resultVO = this .syncInvoke(CompletableFuture.supplyAsync(() -> authRemoteClient.validateToken(os, token, appVersion)));private RpcResult syncInvoke (CompletableFuture<ResponseEntity<String>> future) { if (future == null ) { return RpcResult.error(); } try { ResponseEntity<String> response = future.get(); String body = response.getBody(); RpcResult result = JsonUtil.fromJson(body, RpcResult.class); if (result.isError()) { return result; } HttpHeaders headers = response.getHeaders(); String uid = headers.getFirst("uid" ); RpcResult rpcResult = RpcResult.success(); rpcResult.setData(uid); return rpcResult; } catch (Exception e) { logger.info("同步调用执行出错:{}" , e.getMessage(), e); throw new RuntimeException (e); } }
运行时调用鉴权服务会发生如下报错:
1 Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration]; nested exception is java.lang.IllegalArgumentException: Could not find class [org.springframework.boot.autoconfigure.condition.OnPropertyCondition]
问题解决 经过检索后得知,这是一个已知的BUG,详见:Use classloader from class
解决办法:
将调用方法CompletableFuture.supplyAsync(Supplier<U> supplier)改为调用CompletableFuture.supplyAsync(Supplier<U> supplier,Executor executor),主动传递一个线程池参数即可。
1 2 3 4 5 6 7 8 9 private Executor asynchronousExecutor = new ThreadPoolExecutor (1 , 1 , 0L , TimeUnit.MILLISECONDS, new LinkedBlockingQueue <Runnable>(EXECUTOR_QUEUE_SIZE), new AbortPolicy ()); CompletableFuture future = CompletableFuture.supplyAsync( () -> authRemoteClient.validateToken(os, token, appVersion), asynchronousExecutor); RpcResult resultVO = this .syncInvoke(future);
【参考】Spring Cloud 疑难杂症之 CompletableFuture 与 Openfeign 一起使用的问题