越努力,越幸运

朱季谦

腾讯云社区2022年度最佳作者、阿里云专家博主认证

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这样方式命名会更好些。

posted @ 2021-12-21 00:26  朱季谦  阅读(912)  评论(0编辑  收藏  举报