解决No qualifying bean of type ‘org.springframework.cloud.client.discovery.DiscoveryClient‘ available

本文介绍了解决Dubbo与Spring Cloud集成时出现的异常情况,详细分析了问题产生的原因,并提供了两种可行的解决方案:一是通过创建自定义Spring Boot Starter项目;二是通过覆盖Dubbo中的类。

异常

Dubbo自动关停自我销毁
11:39:12.242 [main] INFO o.a.d.r.t.AbstractServer - [,73] - [DUBBO] Start NettyServer bind /0.0.0.0:20880, export /10.10.10.2:20880, dubbo version: 2.7.8, current host: 10.10.10.2
11:39:12.264 [main] INFO c.a.c.d.s.DubboMetadataServiceExporter - [unexport,106] - The Dubbo service[<dubbo:service exported=“true” unexported=“true” />] has been unexported.
11:39:12.266 [main] INFO o.a.d.r.s.AbstractRegistryFactory - [destroyAll,81] - [DUBBO] Close all registries [], dubbo version: 2.7.8, current host: 10.10.10.2
11:39:12.266 [main] INFO o.a.d.r.p.d.DubboProtocol - [destroy,615] - [DUBBO] Close dubbo server: /10.10.10.2:20880, dubbo version: 2.7.8, current host: 10.10.10.2
11:39:12.267 [main] INFO o.a.d.r.t.AbstractServer - [close,128] - [DUBBO] Close NettyServer bind /0.0.0.0:20880, export /10.10.10.2:20880, dubbo version: 2.7.8, current host: 10.10.10.2

然后抛出异常:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘org.springframework.cloud.client.discovery.DiscoveryClient’ available

环境

spring-cloud-stream-binder-kafka:3.0.8.RELEASE
spring-cloud-starter-dubbo:2.2.5.RELEASE
spring-cloud-dependencies:Hoxton.SR8
spring-cloud-alibaba-dependencies:2.2.5.RELEASE
nacos-client: 2.0.0

问题分析

那么我是用了spring.cloud.stream.binders配置来设定多个Binder,一旦设定了就会发现启动不了。
经过几轮源码调试,终于发现了问题所在。

  1. Dubbo中的DubboServiceRegistrationApplicationContextInitializer类通过ApplicationContextInitializer方式来注入ApplicationContext应用上下文到SpringCloudRegistryFactory中。

    spring-cloud-starter-dubbo-2.2.5.RELEASE.jar!\META-INF\spring.factories

  2. Spring Cloud项目启动,实例化各种Bean…(包括了DiscoveryClient)

  3. 在Spring Cloud Stream在处理自定义的Binder配置时,会为其特定的environment配置独立一个ApplicationContext应用上下文(这个有点特殊,时间原因,没有细究),然后会触发ApplicationContextInitializer,Dubbo中的DubboServiceRegistrationApplicationContextInitializer类就获取到了这个特殊的ApplicationContext应用上下文(这个是获取不到实例化好的Bean的)。

  4. SpringCloudRegistryFactory然后在注册Dubbo服务时,使用上一步获取到的ApplicationContext来getBean(DiscoveryClient.class),那自然就报错了。

解决方案

针对上面的情况的话,大体思路是有另外一个Bean通过ApplicationContextAware注入最完整的ApplicationContext应用上下文,然后自己再定义一个新的MyDubboServiceRegistrationApplicationContextInitializer类,实际注入到SpringCloudRegistryFactory的ApplicationContext应用上下文就用之前ApplicationContextAware注入的到那个。

当然要注意验证MyDubboServiceRegistrationApplicationContextInitializer的加载顺序必须在Dubbo中的DubboServiceRegistrationApplicationContextInitializer后面才行。

方式一:SpringBoot Starter

其实看到上述提到的源码也看到了DubboServiceRegistrationApplicationContextInitializer类是没有定义Order优先级的,那么他在Spring内部默认优先级就是最低的(也就是等价于@Order(Integer.MAX_VALUE)),所以在这个时候就很难保证我们自定义的Bean一定能排在它之后。

这个时候可能就需要个取巧的方案,把这个类放到另一个自定义的Spring Boot Starter项目中,然后再引入到自己的业务项目里。

这里要注意下依赖包加载优先级问题,我记得Maven加载顺序好像默认以groupId+artifactId的级别深度,深度越浅那么优先级就越高,如果是想自己的自定义依赖包里面的MyDubboServiceRegistrationApplicationContextInitializer类是最后加载处理的话,就需要把groupId写深点。比如下面这样:

Dubbo:

<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>

自定义的:

<dependency>
	<groupId>org.zze0.cloud.dubbo</groupId>
	<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>

代码实现:

package org.zze0;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.Optional;

@Component
public class SpringUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtils.applicationContext = applicationContext;
    }

    public static Optional<ApplicationContext> tryGetApplicationContext() {
        return Optional.ofNullable(applicationContext);
    }
}
package org.zze0;

import com.alibaba.cloud.dubbo.registry.SpringCloudRegistryFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.Order;

@Order
public class MyDubboServiceRegistrationApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        SpringUtils.tryGetApplicationContext()
                .filter(context -> context instanceof ConfigurableApplicationContext)
                .map(context -> (ConfigurableApplicationContext) context)
                .ifPresent(SpringCloudRegistryFactory::setApplicationContext);
    }
}

spring.factories:

org.springframework.context.ApplicationContextInitializer=\
  org.zze0.MyDubboServiceRegistrationApplicationContextInitializer

方式二:覆盖类

除了上述曲线救国的方式,那么有没有另外一种方式是不需要另外开项目的呢?

据我目前了解到的内容和查看源码(org.springframework.web.servlet.FrameworkServlet#applyInitializers)后的感想来看,通过调整DubboServiceRegistrationApplicationContextInitializer类实例化、调用顺序在自定义的之前几乎是做不到的,因为无论是使用@Order注解还是Ordered接口都是需要修改代码才能达到的,而且应用在启动的时候是默认先加载依赖包里的类。我也尝试过使用AOP切面去处理,结果发现其实例化ApplicationContextInitializer的时候并没有使用代理模式生成代理对象,所以基本该方案也做不了。

那么只剩下比较粗暴直接的方式:直接复制DubboServiceRegistrationApplicationContextInitializer类的源码到自己本地项目里面,包名和阿里巴巴的包名一致,相当于覆盖依赖包里面的类。因为遇到同全限定名的类时,类加载器默认会优先加载使用本地项目的类。如下:

package org.zze0;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.Optional;

@Component
public class SpringUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtils.applicationContext = applicationContext;
    }

    public static Optional<ApplicationContext> tryGetApplicationContext() {
        return Optional.ofNullable(applicationContext);
    }
}
package com.alibaba.cloud.dubbo.context;

import com.alibaba.cloud.dubbo.registry.SpringCloudRegistryFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.Order;
import org.zze0.SpringUtils;

public class DubboServiceRegistrationApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        SpringUtils.tryGetApplicationContext()
                .filter(context -> context instanceof ConfigurableApplicationContext)
                .map(context -> (ConfigurableApplicationContext) context)
                .ifPresent(SpringCloudRegistryFactory::setApplicationContext);
    }
}
Spring框架中,当遇到 `No qualifying bean of type 'org.springframework.ai.chat.client.ChatClient' available` 这类错误时,表示Spring容器无法找到符合预期的 `ChatClient` 类型Bean来满足依赖注入的需求。这通常发生在以下几种情况: 1. **缺少必要的Bean定义**:如果没有显式声明一个类型为 `ChatClient` 的Bean,或者没有通过配置类或自动扫描机制导入相关的AI客户端库,Spring就无法创建该类型的Bean[^1]。 2. **多个匹配的Bean导致冲突**:虽然当前报错是“找不到”Bean,但有时如果存在多个候选Bean却未指定首选项,也可能引发类似的歧义问题。例如,在提供的参考信息中,用户遇到了两个 `ChatModel` 类型Bean(`ollamaChatModel` 和 `openAiChatModel`),这种情况下,如果有依赖需要注入一个 `ChatModel`,而未明确指定使用哪一个,则会抛出 `NoUniqueBeanDefinitionException` 异常[^1]。 3. **组件扫描路径不正确**:如果Spring Boot应用的组件扫描路径没有包含定义 `ChatClient` Bean的包,则会导致Bean无法被检测到并注册进IoC容器。 4. **依赖版本或模块缺失**:如果使用的Spring AI或其他第三方库版本中并未提供 `ChatClient` 类,或者相关模块没有添加到项目依赖中(如Maven或Gradle配置文件中缺少对应依赖),也会造成此类错误。 5. **条件化配置未满足**:某些Bean可能依赖于特定的条件注解(如 `@ConditionalOnProperty` 或 `@ConditionalOnClass`)来决定是否创建。如果这些条件未能满足,Bean就不会被加载,从而导致注入失败。 ### 解决方案 - **检查依赖配置**:确保项目中已经正确引入了支持 `ChatClient` 的依赖,比如 `spring-ai-openai` 或其他AI服务提供商的SDK。 - **手动定义Bean**:如果有必要,可以在配置类中通过 `@Bean` 注解显式地定义 `ChatClient` Bean,以确保其存在于Spring上下文中。 - **使用限定符注解**:当存在多个同类型Bean时,可以通过 `@Qualifier` 配合 `@Primary` 来明确指定注入哪一个Bean。 - **启用自动配置**:确认是否启用了Spring AI的自动配置功能,通常是通过主应用程序类上的 `@SpringBootApplication` 注解实现的。 - **调整组件扫描范围**:确保Spring Boot应用的组件扫描覆盖了所有涉及AI功能的包。 示例代码片段展示如何显式定义一个 `ChatClient` Bean(假设使用OpenAI作为后端): ```java @Configuration public class AiConfig { @Bean public ChatClient openAiChatClient(OpenAiProperties properties) { return new OpenAiChatClient(properties); } } ``` 此外,还可以结合日志输出进一步排查具体哪些Bean已经被注册,以及为何Spring认为没有合适的 `ChatClient` 可供注入。通过启用Spring Boot的调试日志(如设置 `logging.level.org.springframework.beans.factory` 为 `DEBUG`),可以更清晰地看到容器内部的Bean创建过程和依赖解析逻辑。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值