异常

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);
    }
}
Logo

Kafka开源项目指南提供详尽教程,助开发者掌握其架构、配置和使用,实现高效数据流管理和实时处理。它高性能、可扩展,适合日志收集和实时数据处理,通过持久化保障数据安全,是企业大数据生态系统的核心。

更多推荐