• Dubbo配置注册中心设置application的name使用驼峰命名法可能存在的隐藏启动异常问题


    原创/朱季谦

    首先,先提一个建议,在SpringBoot+Dubbo项目中,Dubbo配置注册中心设置的application命名name的值,最好使用xxx-xxx-xxx这样格式的,避免随便使用驼峰命名。因为使用驼峰命名法,在Spring的IOC容器当中,很可能会出现一些导致项目启动失败的坑,例如,会出现这样的异常报错:

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userController': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'userService' is expected to be of type 'com.xxx.xxx.xxx.service.UserService' but was actually of type 'org.apache.dubbo.config.ApplicationConfig'

    在说明该问题之前,首先,需要提一下org.apache.dubbo.config.ApplicationConfig这个类,这是一个Dubbo的应用配置类,它用在哪里呢?

    在SpringBoot 2.x+Dubbo项目当中,主流都是使用yaml文件设置项目环境依赖参数,不同的组件,其配置类的实例化各有差异。

    Dubbo初始化配置类主要有以下——

    序号 类名 用途 说明
    1 ApplicationConfig 当前应用配置 提供者或者消费者配置当前应用信息,一般以属性name区分各应用
    2 MonitorConfig 监控中心配置 配置连接监控中心monitor参数
    3 RegistryConfig 连接注册中心配置 配置Dubbo用到的注册中心
    4 ProtocolConfig 服务提供者协议配置 配置提供方的远程通信协议
    ...... ...... ...... ......

    这些配置类的实现原理基本大同小异,本文主要以ApplicationConfig配置类做讲解分析。

    在yaml配置文件里,Dubbo的配置例子如下——

    dubbo:
      application:
        name: userService
      registry:
        address: zookeeper://127.0.0.1:2181
        timeout: 10000
      protocol:
        name: dubbo
        port: 20880
    

    这个配置可以拆开如下图这样看,便能一眼看懂它们分别属于哪个配置类——
    image

    当在yaml文件这样配置后,当项目启动时,会自动获取这些参数,然后初始化到对应的配置类当中,例如,application中的name值就会设置到ApplicationConfig类对象里——
    image

    在SpringBoot中,这个ApplicationConfig对象会在普通bean初始化之前,就已经装载到IOC容器当中,以name的值做该bean名,同时,会以name:className的方式存储在Spring的bean别名缓存aliasMap当中,这就出现一个问题,假如该项目当中存在同名bean注解的话,会出现什么样情况呢?

    例如,当SpringBoot的Dubbo配置如前边一样,以字符串“userService”做ApplicationConfig的name值,同时,controller层有以下代码——

    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        @Resource
        private UserService userService;
      
        ......
    }
    

    我们可以在IOC容器过程的AbstractBeanFactory类中的doGetBean(String name, @Nullable Class requiredType, @Nullable Object[] args, boolean typeCheckOnly)方法截图处打一个针对userService的断点——

    image

    截图里的逻辑,其实是在对注解有@RestController的UserController类做IOC过程中,会对其通过 @Resource设置的属性userService做依赖注入过程,首先,会去bean别名aliasMap缓存当中看是否能查询到,我们进入到transformedBeanName(name)方法底层——

    public String canonicalName(String name) {
       String canonicalName = name;
       // Handle aliasing...
       String resolvedName;
       do {
          resolvedName = this.aliasMap.get(canonicalName);
          if (resolvedName != null) {
             canonicalName = resolvedName;
          }
       }
       while (resolvedName != null);
       return canonicalName;
    }
    

    此时,this.aliasMap缓存里已经有值了,主要都是Dubbo相关的,这说明Dubbo会在普通自定义Bean前就做了IOC注入,我们可以看到,前边提到的ApplicationConfig对象class类名,已经缓存在aliasMap当中,其key值,正好yaml配置文件里设置的name值。当以“userService”字符串取aliasMap获取,是可以拿到值的——

    image

    但是,这里注意一点,此刻debug这一步doGetBean,理应依赖注入的是UserService类而不是ApplicationConfig类——

    image

    然而实际情况是,此时,通过方法Object sharedInstance = getSingleton(beanName)从IOC三级缓存之一的单例池里获取到的则是ApplicationConfig已经初始化成单例bean的对象——

    image

    这将会出现什么情况呢?

    在 doGetBean方法最后,会做一步这样操作,将需要初始化的bean类型requiredType与通过“userService”从单例池里获取到的实际bean类型做比较——

    // Check if required type matches the type of the actual bean instance.
    //检查所需类型是否与实际bean实例的类型匹配
    if (requiredType != null && !requiredType.isInstance(bean)) {
       try {
          T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
          if (convertedBean == null) {
             throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
          }
          return convertedBean;
       }
       catch (TypeMismatchException ex) {
          if (logger.isTraceEnabled()) {
             logger.trace("Failed to convert bean '" + name + "' to required type '" +
                   ClassUtils.getQualifiedName(requiredType) + "'", ex);
          }
          throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
       }
    }
    

    结果可想而知,一个是UserService类,一个是ApplicationConfig类,两者肯定不匹配,那么就会执行抛出异常throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());

    public BeanNotOfRequiredTypeException(String beanName, Class<?> requiredType, Class<?> actualType) {
       super("Bean named '" + beanName + "' is expected to be of type '" + ClassUtils.getQualifiedName(requiredType) +
             "' but was actually of type '" + ClassUtils.getQualifiedName(actualType) + "'");
       this.beanName = beanName;
       this.requiredType = requiredType;
       this.actualType = actualType;
    }
    

    debug到这一步,其错误提示,刚好就是——

    Bean named 'userService' is expected to be of type 'com.xxx.xxx.xxx.service.UserService' but was actually of type 'org.apache.dubbo.config.ApplicationConfig

    因此,就说明一个问题,当Dubbo应用配置application的name使用驼峰命名,例如,本文中的userService,刚好又有某个地方用到类似这样注解的属性依赖注入 private UserService userService,那么,项目在启动过程中,就会出现类似本文中提到的项目启动异常。

    可见,在application的name值使用xxx-xxx-xx这样方式命名会更好些。

    作者:朱季谦
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文链接,否则保留追究法律责任的权利。
  • 相关阅读:
    Python单元测试之unittest基础
    linux--硬链接和软链接
    12-8 istio核心功能实战-----可观察性(程访问遥测插件)
    12-7 istio核心功能实战-----可观察性(网络可视化)
    12-6 istio核心功能实战-----可观察性(分布式追踪)
    12-3 部署面向生产的istio-----核心组件
    12 ServiceMesh代表作istio-----12-1 ServiceMes、Istio架构原理
    11-7 Grafana看板和邮件报警
    11-6 监控落地
    11-4 部署前奏-Helm&Operator
  • 原文地址:https://www.cnblogs.com/zhujiqian/p/15713556.html
Copyright © 2020-2023  润新知