原创/朱季谦

首先,先提一个建议,在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的相关配置类是如何通过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

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

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

在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的断点——

截图里的逻辑,其实是在对注解有@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获取,是可以拿到值的——

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

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

这将会出现什么情况呢?

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

Dubbo配置注册中心设置application的name使用驼峰命名法存在的隐藏项目启动异常问题的更多相关文章

  1. Javaconfig形式配置Dubbo多注册中心

    多注册中心,一般用不到,但是某些情况下的确能解决不少问题,可以将某些dubbo服务注册到2套dubbo系统中,实现服务在2套系统间的共用. 网上的配置说明很多,但包括dubbo官方说明文档都是以xml ...

  2. Dubbo多注册中心和Zookeeper服务的迁移

    一.Dubbo多注册中心 1. 应用场景 例如阿里有些服务来不及在青岛部署,只在杭州部署,而青岛的其它应用需要引用此服务,就可以将服务同时注册到两个注册中心. consumer.xml <?xm ...

  3. 如果有人问你 Dubbo 中注册中心工作原理,就把这篇文章给他

    注册中心作用 开篇首先想思考一个问题,没有注册中心 Dubbo 还能玩下去吗? 当然可以,只要知道服务提供者地址相关信息,消费者配置之后就可以调用.如果只有几个服务,这么玩当然没问题.但是生产服务动辄 ...

  4. Dubbo Multicast 注册中心即相关代码实现

    Dubbo 的 Multicast注册中心有下面特点: 不需要启动任何中心节点,只要广播地址一样,就可以互相发现 组播受网络结构限制,只适合小规模应用或开发阶段使用. 组播地址段: 224.0.0.0 ...

  5. Dubbo及注册中心原理

    本文首发于微信公众号[猿灯塔],转载引用请说明出处 今天是猿灯塔“365天原创计划”第4天. 今天呢!灯塔君跟大家讲: 一.Dubbo意义 网站应用的架构变化经历了一个从所有服务分布在一台服务器上(A ...

  6. Dubbo多注册中心

    一.创建提供者08-provider-registers (1) 创建工程 直接复制05-provider-group工程,并命名为08-provider-registers (2) 修改配置文件 二 ...

  7. dubbo 多注册中心

    这个我调试了下,多个注册中心在创建代理的时候,每个注册中心对应一个invoker,持有一个RegistryDirectory对应一个zkClinet,并且维护这样一个map: 那些不正确zk在创建代理 ...

  8. SpringCloud:Eureka注册中心设置显示IP路径

    未设置下的Eureka平台 可以看到Status显示的是 计算机名称! 解决方法: 在每一个需要注册的服务配置内加上如下几行配置 instance: prefer-ip-address: true # ...

  9. spring cloud配置注册中心显示服务的ip地址和端口

    1.在springcloud中服务的 Instance ID 默认值是: ${spring.cloud.client.hostname}:${spring.application.name}:${sp ...

随机推荐

  1. [atARC098F]Donation

    贪心,一定在最后一次经过某节点时付出$b_{u}$,条件是付出后$W\ge \max(a_{i}-b_{i},0)$(同时也可以仅考虑这个限制,因为$W$在过程中不会增大) 假设"最后一次经 ...

  2. HTML四种常见的定位-相对定位

    相对定位 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset=&q ...

  3. 咸阳市大数据管理局使用Rainbond作为智慧城市底座的实践

    使用 Rainbond 作为智慧城市底座之后,给我们带来了成倍的运维效率提升. -- 咸阳市大数据管理局 熊礼智 咸阳市大数据管理局负责全市信息共享工作的组织领导,协调解决与政府信息共享有关的重大问题 ...

  4. 痞子衡嵌入式:深扒IAR启动函数流程之段初始化实现中可用的压缩选项

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是IAR启动函数流程里段初始化实现中可用的压缩选项. 接着 <IAR启动函数流程之段初始化函数__iar_data_init3实现& ...

  5. 洛谷 P3246 - [HNOI2016]序列(单调栈+前缀和)

    题面传送门 这道题为什么我就没想出来呢/kk 对于每组询问 \([l,r]\),我们首先求出区间 \([l,r]\) 中最小值的位置 \(x\),这个可以用 ST 表实现 \(\mathcal O(n ...

  6. Matlab指针数组

    Matlab指针数组 前面博客Matlab指针中介绍了如何在Matlab中使用handle类型对象作为指针使用,本文则介绍一些使用这些类型指针的小技巧. 自定义类型的指针数组 在大部分编程语言中,我们 ...

  7. CUDA计算矩阵相乘

    1.最简单的 kernel 函数 __global__ void MatrixMulKernel( float* Md, float* Nd, float* Pd, int Width) { int ...

  8. Bedtools如何比较两个参考基因组注释版本的基因?

    目录 问题 思路 问题 原问题来自:How to calculate overlapping genes between two genome annotation versions? 其实可分为两个 ...

  9. R包对植物进行GO,KEGG注释

    1.安装,加载所用到到R包 用BiocManager安装,可同时加载依赖包 source("https://bioconductor.org/biocLite.R") BiocMa ...

  10. C语言 自定义函数按行读入文件2

    再改进下上次的读入一行函数,利用zlib库的gzgtec函数读取文件,动态分配内存,最后没有多出空行. 1 #include <stdio.h> 2 #include <stdlib ...