Dubbo服务治理篇——改造低版本Dubbo,使其兼容Spring4或Spring5注解配置

Stella981
• 阅读 742

Dubbo服务治理篇——改造低版本Dubbo,使其兼容Spring4或Spring5注解配置

温馨提示:本文有点长,但是满满的干货,建议耐心看完!

特别说明:由于很多网友留言自身使用的Dubbo框架版本比较低,无法兼容Spring4或Spring5的注解配置,故本文只针对低版本Dubbo如何兼容Spring4或Spring5的注解配置给出相应的解决方案,目前,高版本Dubbo已经不存在与Spring4或Spring5的注解配置的兼容性问题。

本文分析低版本Dubbo框架的源码,以找出不兼容Spring4或Spring5的注解配置的问题所在,并通过修改Dubbo源码以使其兼容Spring4或Spring5的注解配置。最后,由于直接修改Dubbo的源码对Dubbo框架的侵入性太强,文中又给出了如何以“外挂”的形式修改Dubbo源码并使其注册到Spring环境。

自从阿里重新维护Dubbo后,一路放大招,将Dubbo推上Apache的顶级开源项目,Dubbo经过不断的维护和发展,也相继发布了N多个版本,成为分布式与微服务领域中屈指可数的服务治理框架。目前, 笔者截稿前Dubbo的最新Release版本为apache-dubbo-2.7.4.1。

Dubbo的Release版本的源码Apache下载链接地址为:https://github.com/apache/dubbo/releases。

不过,目前,也有很多公司或组织内部使用的Dubbo版本比较老旧,很多网友在公众号留言咨询笔者,低版本的Dubbo如何兼容Spring4或Spring5。这里,笔者就借此机会写一些有关Dubbo服务治理框架的文章,供读者参考。

Dubbo本身就是基于Spring环境的,但是Dubbo当年Spring才2.版本。而现如今Spring 已经发展到5。

而随着Spring Boot的大热,Java-Base方式配置Spring也变得越来越流行。

Dubbo + Boot的开发模式,也是较为常见的组合方式。

但是,当使用低版本Dubbo在高版本Spring环境中使用注解方式配置时,会因为一些代码版本的原因导致整合出现问题。

1.Dubbo原生的注解配置

Dubbo本身就是基于Spring的,而且原生就提供注解配置:
服务提供方配置:

<!-- 扫描注解包路径,多个包用逗号分隔,不填pacakge表示扫描当前ApplicationContext中所有的类 -->

服务提供方注解:

import com.alibaba.dubbo.config.annotation.Service;

服务消费方注解:

import com.alibaba.dubbo.config.annotation.Reference;

服务消费方配置:

<!-- 扫描注解包路径,多个包用逗号分隔,不填pacakge表示扫描当前ApplicationContext中所有的类 -->

通过官方的例子,就可以看出Dubbo使用xml配置<dubbo:annotation /> 来开启注解配置,并提供 com.alibaba.dubbo.config.annotation.Service注解进行服务注册,提供com.alibaba.dubbo.config.annotation.Reference注解进行服务注入。

2.实现机制

可以看出,内部机制都是依托于<dubbo:annotation />标签。通过源码分析,Dubbo对于Spring xml解析处理由com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler提供:

DubboNamespaceHandler.java

package com.alibaba.dubbo.config.spring.schema;

通过上面的代码可以很直观的发现,<dubbo:annotation />标签实际是由com.alibaba.dubbo.config.spring.schema.DubboBeanDefinitionParser解析:

DubboBeanDefinitionParser.java

/**

可以看到这个类实现了Spring的org.springframework.beans.factory.xml.BeanDefinitionParser接口,从而完成Spring Bean的解析工作。

而registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));就是将<dubbo:annotation />标签,解析成com.alibaba.dubbo.config.spring.AnnotationBean并注册到Spring中。

3.AnnotationBean分析

先来看看源码:
AnnotationBean.java

package com.alibaba.dubbo.config.spring;

这个AnnotationBean实现了几个Spring生命周期接口,从而完成Dubbo整合Spring 的操作。
org.springframework.beans.factory.config.BeanFactoryPostProcessor
先来看看Spring文档中的介绍:

BeanFactoryPostProcessor operates on the bean configuration metadata; that is, the Spring IoC container allows a BeanFactoryPostProcessor to read the configuration metadata and potentially change it before the container instantiates any beans other than BeanFactoryPostProcessors.

翻译成中文就是:BeanFactoryPostProcessor可以用于在Spring IoC容器实例化Bean之前,对Spring Bean配置信息进行一些操作

通过Spring文档,可以清楚这个接口的功能,那再来看看Dubbo的AnnotationBean是如何实现这个接口的:

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)

源码中已经标出了,Dubbo做了以下几件事:

  • 增加了一个class扫描器,用于处理Dubbo服务类的扫描

  • 增加过滤器,只扫描带有com.alibaba.dubbo.config.annotation.Service注解的class

  • 指定扫描的包,对应这<dubbo:annotation package="com.foo.bar.service" />标签中的package属性。

这个扫描器,就是将那些带有com.alibaba.dubbo.config.annotation.Service注解的class纳入Spring容器中,而这些Dubbo服务类上不需要单独加上Spring的注解,也不需要额外配置Spring Bean定义。

org.springframework.beans.factory.DisposableBean

再来看看这个接口,还是先看看Spring文档:

Implementing the org.springframework.beans.factory.DisposableBean interface allows a bean to get a callback when the container containing it is destroyed. The DisposableBean interface specifies a single method:void destroy() throws Exception;

这个接口实际上就是当Spring容器要销毁时的一个回调接口。
那再来看看Dubbo是如何实现的:

public void destroy() throws Exception {

也比较简单,实际上就是当Spring容器销毁时,对Dubbo服务进行反注册操作。
org.springframework.beans.factory.config.BeanPostProcessor
最后来看看这个接口,照例先看看Spring文档:

The BeanPostProcessor interface defines callback methods that you can implement to provide your own (or override the container’s default) instantiation logic, dependency-resolution logic, and so forth. If you want to implement some custom logic after the Spring container finishes instantiating, configuring, and initializing a bean, you can plug in one or more BeanPostProcessor implementations.

这个接口也是一个回调接口,只是回调时机不同,它发生在Spring容器初始化过程中,Bean实例化前后之时。也可以理解为Spring容器初始化完成后,Bean实例化前后但还没有给容器外使用前。
再来看看Dubbbo在这个时间点上做了什么:

public Object postProcessBeforeInitialization(Object bean, String beanName)

postProcessBeforeInitialization方法,顾名思义,发生Bean实例化之前。通过源码就可以发现,主要用于处理Bean中的com.alibaba.dubbo.config.annotation.Reference注解,从而让Dubbo服务能够注入到Bean中。期间利用JAVA反射机制对Bean的方法和属性进行注入。

public Object postProcessAfterInitialization(Object bean, String beanName)

postProcessAfterInitialization方法,同样从名字就能看出,其发生在Bean实例化之后。通过源码就能发现,其作用就是将带有com.alibaba.dubbo.config.annotation.Service注解的Bean自动注册到Dubbo的注册中心,完成服务注册动作。

4.问题分析

通过上面的分析,对于Dubbo注解方式整合到Spring已经比较清楚了。而在使用高版本Spring时,上面的AnnotationBean会出现一些问题。

问题主要集中在:

  • Dubbo的注解Service有时不能进行服务注册

  • Dubbo的注解Reference有时不能注入服务

从代码分析问题主要集中在postProcessBeforeInitialization和postProcessAfterInitialization方法上。

Service service = bean.getClass().getAnnotation(Service.class);



Method[] methods = bean.getClass().getMethods();

方法中都是利用Java反射来处理Bean的Class。

众所周知,Spring有两大利器IOC和AOP。在早期AOP基本都是利用JAVA动态代理实现的,而随着字节码技术的发展,现在Spring已经基本以ASM为代表的字节码增强框架为基础实现AOP,以及一些Bean的代理。

有兴趣的小伙伴可以打开spring-core-x.x.x.jar,会发现spring已经内嵌了asm以及cglib (org.springframework.asm)(org.springframework.cglib)

而在Spring环境中大量出现经过Cglib增强的Class,这些Class不再是简单的基于接口的代理机制了,其内部大量使用委派方式,以及访问器模式。最常见的就是Spring的事物,对于使用了@Transactional的Bean,都是以这种方式来增加事物处理能力的。

那基于这种方式扩展的Class,再通过反射getClass得到的是经过cglib改写后的class。在这些class上getAnnotation以及getMethods都不再是预期中的结果了。

所以,这也也就导致了上面罗列的两个问题。

5. 解决方案

经过对问题的分析,可以发现,问题主要是集中在Dubbo获取Bean的class上。

在Spring中,提供了很多Utils类。其中org.springframework.aop.support.AopUtils就可以用来对Aop代理的类进行操作的API。

于是,我们可以利用这个类来完成Class的获取。

private Class<?> getBeanClass(Object bean) {

这样获取Class就可以这样:

Class<?> clazz = getBeanClass(bean);



Class<?> clazz = getBeanClass(bean);

修改后完整的代码:
AnnotationBean.java

/*

6. 整合

同样,我不太赞同自己修改Dubbo源码,然后打包。侵入性太强。通过前面几节的分析发现,其实只要AnnotationBean能够注册到Spring环境就行。而AnnotationBean无所谓放在哪个jar中。

所以,完全可以把修改后的AnnotationBean打入到自己写的jar中,然后注入Spring环境,以外挂的方式完成修改。

下面示例代码展示了,在Spring Boot中以外挂方式整合:

package com.roc.dubbo.boot.configuration;

本文分享自微信公众号 - 冰河技术(hacker-binghe)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这