温馨提示:本文有点长,但是满满的干货,建议耐心看完!
特别说明:由于很多网友留言自身使用的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源创计划”,欢迎正在阅读的你也加入,一起分享。