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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 特别说明:由于很多网友留言自身使用的Dubbo框架版本比较低,无法兼容Spring4或Spring5的注解配置,故本文只针对低版本Dubbo如何兼容Spring4或Spring5的注解配置给出相应的解决方案,目前,高版本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中所有的类 -->
<dubbo:annotation package="com.foo.bar.service" />


服务提供方注解:


import com.alibaba.dubbo.config.annotation.Service;
@Service(version="1.0.0")
public class FooServiceImpl implements FooService {
    // ......
}


服务消费方注解:


import com.alibaba.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Component;
@Component
public class BarAction {
    @Reference(version="1.0.0")
    private FooService fooService;
}


服务消费方配置:


<!-- 扫描注解包路径,多个包用逗号分隔,不填pacakge表示扫描当前ApplicationContext中所有的类 -->
<dubbo:annotation package="com.foo.bar.action" />


通过官方的例子,就可以看出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;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import com.alibaba.dubbo.common.Version;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ConsumerConfig;
import com.alibaba.dubbo.config.ModuleConfig;
import com.alibaba.dubbo.config.MonitorConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.ProviderConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.AnnotationBean;
import com.alibaba.dubbo.config.spring.ReferenceBean;
import com.alibaba.dubbo.config.spring.ServiceBean;
/**
 * DubboNamespaceHandler
 * 
 * @author william.liangf
 * @export
 */
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }
    public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
    }


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

DubboBeanDefinitionParser.java


**
 * AbstractBeanDefinitionParser
 * 
 * @author william.liangf
 * @export
 */
public class DubboBeanDefinitionParser implements BeanDefinitionParser {
    private static final Logger logger = LoggerFactory.getLogger(DubboBeanDefinitionParser.class);
    private final Class<?> beanClass;
    private final boolean required;
    public DubboBeanDefinitionParser(Class<?> beanClass, boolean required) {
        this.beanClass = beanClass;
        this.required = required;
    }
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        return parse(element, parserContext, beanClass, required);
    }
    @SuppressWarnings("unchecked")
    private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
        //略
    }


可以看到这个类实现了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
 * 
 * @author william.liangf
 * @export
 */
public class AnnotationBean extends AbstractConfig implements DisposableBean, BeanFactoryPostProcessor, BeanPostProcessor, ApplicationContextAware {
    private static final long serialVersionUID = -7582802454287589552L;
    private static final Logger logger = LoggerFactory.getLogger(Logger.class);
    private String annotationPackage;
    private String[] annotationPackages;
    private final Set<ServiceConfig<?>> serviceConfigs = new ConcurrentHashSet<ServiceConfig<?>>();
    private final ConcurrentMap<String, ReferenceBean<?>> referenceConfigs = new ConcurrentHashMap<String, ReferenceBean<?>>();
    public String getPackage() {
        return annotationPackage;
    }
    public void setPackage(String annotationPackage) {
        this.annotationPackage = annotationPackage;
        this.annotationPackages = (annotationPackage == null || annotationPackage.length() == 0) ? null
                : Constants.COMMA_SPLIT_PATTERN.split(annotationPackage);
    }
    private ApplicationContext applicationContext;
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
            throws BeansException {
        if (annotationPackage == null || annotationPackage.length() == 0) {
            return;
        }
        if (beanFactory instanceof BeanDefinitionRegistry) {
            try {
                // init scanner
                Class<?> scannerClass = ReflectUtils.forName("org.springframework.context.annotation.ClassPathBeanDefinitionScanner");
                Object scanner = scannerClass.getConstructor(new Class<?>[] {BeanDefinitionRegistry.class, boolean.class}).newInstance(new Object[] {(BeanDefinitionRegistry) beanFactory, true});
                // add filter
                Class<?> filterClass = ReflectUtils.forName("org.springframework.core.type.filter.AnnotationTypeFilter");
                Object filter = filterClass.getConstructor(Class.class).newInstance(Service.class);
                Method addIncludeFilter = scannerClass.getMethod("addIncludeFilter", ReflectUtils.forName("org.springframework.core.type.filter.TypeFilter"));
                addIncludeFilter.invoke(scanner, filter);
                // scan packages
                String[] packages = Constants.COMMA_SPLIT_PATTERN.split(annotationPackage);
                Method scan = scannerClass.getMethod("scan", new Class<?>[]{String[].class});
                scan.invoke(scanner, new Object[] {packages});
            } catch (Throwable e) {
                // spring 2.0
            }
        }
    }
    public void destroy() throws Exception {
        for (ServiceConfig<?> serviceConfig : serviceConfigs) {
            try {
                serviceConfig.unexport();
            } catch (Throwable e) {
                logger.error(e.getMessage(), e);
            }
        }
        for (ReferenceConfig<?> referenceConfig : referenceConfigs.values()) {
            try {
                referenceConfig.destroy();
            } catch (Throwable e) {
                logger.error(e.getMessage(), e);
            }
        }
    }
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        if (! isMatchPackage(bean)) {
            return bean;
        }
        Service service = bean.getClass().getAnnotation(Service.class);
        if (service != null) {
            ServiceBean<Object> serviceConfig = new ServiceBean<Object>(service);
            if (void.class.equals(service.interfaceClass())
                    && "".equals(service.interfaceName())) {
                if (bean.getClass().getInterfaces().length > 0) {
                    serviceConfig.setInterface(bean.getClass().getInterfaces()[0]);
                } else {
                    throw new IllegalStateException("Failed to export remote service class " + bean.getClass().getName() + ", cause: The @Service undefined interfaceClass or interfaceName, and the service class unimplemented any interfaces.");
                }
            }
            if (applicationContext != null) {
                serviceConfig.setApplicationContext(applicationContext);
                if (service.registry() != null && service.registry().length > 0) {
                    List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();
                    for (String registryId : service.registry()) {
                        if (registryId != null && registryId.length() > 0) {
                            registryConfigs.add((RegistryConfig)applicationContext.getBean(registryId, RegistryConfig.class));
                        }
                    }
                    serviceConfig.setRegistries(registryConfigs);
                }
                if (service.provider() != null && service.provider().length() > 0) {
                    serviceConfig.setProvider((ProviderConfig)applicationContext.getBean(service.provider(),ProviderConfig.class));
                }
                if (service.monitor() != null && service.monitor().length() > 0) {
                    serviceConfig.setMonitor((MonitorConfig)applicationContext.getBean(service.monitor(), MonitorConfig.class));
                }
                if (service.application() != null && service.application().length() > 0) {
                    serviceConfig.setApplication((ApplicationConfig)applicationContext.getBean(service.application(), ApplicationConfig.class));
                }
                if (service.module() != null && service.module().length() > 0) {
                    serviceConfig.setModule((ModuleConfig)applicationContext.getBean(service.module(), ModuleConfig.class));
                }
                if (service.provider() != null && service.provider().length() > 0) {
                    serviceConfig.setProvider((ProviderConfig)applicationContext.getBean(service.provider(), ProviderConfig.class));
                } else {
                }
                if (service.protocol() != null && service.protocol().length > 0) {
                    List<ProtocolConfig> protocolConfigs = new ArrayList<ProtocolConfig>();
                    for (String protocolId : service.registry()) {
                        if (protocolId != null && protocolId.length() > 0) {
                            protocolConfigs.add((ProtocolConfig)applicationContext.getBean(protocolId, ProtocolConfig.class));
                        }
                    }
                    serviceConfig.setProtocols(protocolConfigs);
                }
                try {
                    serviceConfig.afterPropertiesSet();
                } catch (RuntimeException e) {
                    throw (RuntimeException) e;
                } catch (Exception e) {
                    throw new IllegalStateException(e.getMessage(), e);
                }
            }
            serviceConfig.setRef(bean);
            serviceConfigs.add(serviceConfig);
            serviceConfig.export();
        }
        return bean;
    }
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        if (! isMatchPackage(bean)) {
            return bean;
        }
        Method[] methods = bean.getClass().getMethods();
        for (Method method : methods) {
            String name = method.getName();
            if (name.length() > 3 && name.startsWith("set")
                    && method.getParameterTypes().length == 1
                    && Modifier.isPublic(method.getModifiers())
                    && ! Modifier.isStatic(method.getModifiers())) {
                try {
                    Reference reference = method.getAnnotation(Reference.class);
                    if (reference != null) {
                        Object value = refer(reference, method.getParameterTypes()[0]);
                        if (value != null) {
                            method.invoke(bean, new Object[] {  });
                        }
                    }
                } catch (Throwable e) {
                    logger.error("Failed to init remote service reference at method " + name + " in class " + bean.getClass().getName() + ", cause: " + e.getMessage(), e);
                }
            }
        }
        Field[] fields = bean.getClass().getDeclaredFields();
        for (Field field : fields) {
            try {
                if (! field.isAccessible()) {
                    field.setAccessible(true);
                }
                Reference reference = field.getAnnotation(Reference.class);
                if (reference != null) {
                    Object value = refer(reference, field.getType());
                    if (value != null) {
                        field.set(bean, value);
                    }
                }
            } catch (Throwable e) {
                logger.error("Failed to init remote service reference at filed " + field.getName() + " in class " + bean.getClass().getName() + ", cause: " + e.getMessage(), e);
            }
        }
        return bean;
    }
    private Object refer(Reference reference, Class<?> referenceClass) { //method.getParameterTypes()[0]
        String interfaceName;
        if (! "".equals(reference.interfaceName())) {
            interfaceName = reference.interfaceName();
        } else if (! void.class.equals(reference.interfaceClass())) {
            interfaceName = reference.interfaceClass().getName();
        } else if (referenceClass.isInterface()) {
            interfaceName = referenceClass.getName();
        } else {
            throw new IllegalStateException("The @Reference undefined interfaceClass or interfaceName, and the property type " + referenceClass.getName() + " is not a interface.");
        }
        String key = reference.group() + "/" + interfaceName + ":" + reference.version();
        ReferenceBean<?> referenceConfig = referenceConfigs.get(key);
        if (referenceConfig == null) {
            referenceConfig = new ReferenceBean<Object>(reference);
            if (void.class.equals(reference.interfaceClass())
                    && "".equals(reference.interfaceName())
                    && referenceClass.isInterface()) {
                referenceConfig.setInterface(referenceClass);
            }
            if (applicationContext != null) {
                referenceConfig.setApplicationContext(applicationContext);
                if (reference.registry() != null && reference.registry().length > 0) {
                    List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();
                    for (String registryId : reference.registry()) {
                        if (registryId != null && registryId.length() > 0) {
                            registryConfigs.add((RegistryConfig)applicationContext.getBean(registryId, RegistryConfig.class));
                        }
                    }
                    referenceConfig.setRegistries(registryConfigs);
                }
                if (reference.consumer() != null && reference.consumer().length() > 0) {
                    referenceConfig.setConsumer((ConsumerConfig)applicationContext.getBean(reference.consumer(), ConsumerConfig.class));
                }
                if (reference.monitor() != null && reference.monitor().length() > 0) {
                    referenceConfig.setMonitor((MonitorConfig)applicationContext.getBean(reference.monitor(), MonitorConfig.class));
                }
                if (reference.application() != null && reference.application().length() > 0) {
                    referenceConfig.setApplication((ApplicationConfig)applicationContext.getBean(reference.application(), ApplicationConfig.class));
                }
                if (reference.module() != null && reference.module().length() > 0) {
                    referenceConfig.setModule((ModuleConfig)applicationContext.getBean(reference.module(), ModuleConfig.class));
                }
                if (reference.consumer() != null && reference.consumer().length() > 0) {
                    referenceConfig.setConsumer((ConsumerConfig)applicationContext.getBean(reference.consumer(), ConsumerConfig.class));
                }
                try {
                    referenceConfig.afterPropertiesSet();
                } catch (RuntimeException e) {
                    throw (RuntimeException) e;
                } catch (Exception e) {
                    throw new IllegalStateException(e.getMessage(), e);
                }
            }
            referenceConfigs.putIfAbsent(key, referenceConfig);
            referenceConfig = referenceConfigs.get(key);
        }
        return referenceConfig.get();
    }
    private boolean isMatchPackage(Object bean) {
        if (annotationPackages == null || annotationPackages.length == 0) {
            return true;
        }
        String beanClassName = bean.getClass().getName();
        for (String pkg : annotationPackages) {
            if (beanClassName.startsWith(pkg)) {
                return true;
            }
        }
        return false;
    }
}


这个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)
            throws BeansException {
    if (annotationPackage == null || annotationPackage.length() == 0) {
        return;
    }
    if (beanFactory instanceof BeanDefinitionRegistry) {
        try {
            // init scanner
            Class<?> scannerClass = ReflectUtils.forName("org.springframework.context.annotation.ClassPathBeanDefinitionScanner");
            Object scanner = scannerClass.getConstructor(new Class<?>[] {BeanDefinitionRegistry.class, boolean.class}).newInstance(new Object[] {(BeanDefinitionRegistry) beanFactory, true});
            // add filter
            Class<?> filterClass = ReflectUtils.forName("org.springframework.core.type.filter.AnnotationTypeFilter");
            Object filter = filterClass.getConstructor(Class.class).newInstance(Service.class);
            Method addIncludeFilter = scannerClass.getMethod("addIncludeFilter", ReflectUtils.forName("org.springframework.core.type.filter.TypeFilter"));
            addIncludeFilter.invoke(scanner, filter);
            // scan packages
            String[] packages = Constants.COMMA_SPLIT_PATTERN.split(annotationPackage);
            Method scan = scannerClass.getMethod("scan", new Class<?>[]{String[].class});
            scan.invoke(scanner, new Object[] {packages});
        } catch (Throwable e) {
            // spring 2.0
        }
    }


源码中已经标出了,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 {
    for (ServiceConfig<?> serviceConfig : serviceConfigs) {
        try {
            serviceConfig.unexport();
        } catch (Throwable e) {
            logger.error(e.getMessage(), e);
        }
    }
    for (ReferenceConfig<?> referenceConfig : referenceConfigs.values()) {
        try {
            referenceConfig.destroy();
        } catch (Throwable e) {
            logger.error(e.getMessage(), e);
        }
    }
}


也比较简单,实际上就是当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)
            throws BeansException {
    if (! isMatchPackage(bean)) {
        return bean;
    }
    Method[] methods = bean.getClass().getMethods();
    for (Method method : methods) {
        String name = method.getName();
        if (name.length() > 3 && name.startsWith("set")
                && method.getParameterTypes().length == 1
                && Modifier.isPublic(method.getModifiers())
                && ! Modifier.isStatic(method.getModifiers())) {
            try {
                Reference reference = method.getAnnotation(Reference.class);
                if (reference != null) {
                    Object value = refer(reference, method.getParameterTypes()[0]);
                    if (value != null) {
                        method.invoke(bean, new Object[] {  });
                    }
                }
            } catch (Throwable e) {
                logger.error("Failed to init remote service reference at method " + name + " in class " + bean.getClass().getName() + ", cause: " + e.getMessage(), e);
            }
        }
    }
    Field[] fields = bean.getClass().getDeclaredFields();
    for (Field field : fields) {
        try {
            if (! field.isAccessible()) {
                field.setAccessible(true);
            }
            Reference reference = field.getAnnotation(Reference.class);
            if (reference != null) {
                Object value = refer(reference, field.getType());
                if (value != null) {
                    field.set(bean, value);
                }
            }
        } catch (Throwable e) {
            logger.error("Failed to init remote service reference at filed " + field.getName() + " in class " + bean.getClass().getName() + ", cause: " + e.getMessage(), e);
        }
    }
    return bean;
}


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


public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
    if (! isMatchPackage(bean)) {
        return bean;
    }
    Service service = bean.getClass().getAnnotation(Service.class);
    if (service != null) {
        ServiceBean<Object> serviceConfig = new ServiceBean<Object>(service);
        if (void.class.equals(service.interfaceClass())
                && "".equals(service.interfaceName())) {
            if (bean.getClass().getInterfaces().length > 0) {
                serviceConfig.setInterface(bean.getClass().getInterfaces()[0]);
            } else {
                throw new IllegalStateException("Failed to export remote service class " + bean.getClass().getName() + ", cause: The @Service undefined interfaceClass or interfaceName, and the service class unimplemented any interfaces.");
            }
        }
        if (applicationContext != null) {
            serviceConfig.setApplicationContext(applicationContext);
            if (service.registry() != null && service.registry().length > 0) {
                List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();
                for (String registryId : service.registry()) {
                    if (registryId != null && registryId.length() > 0) {
                        registryConfigs.add((RegistryConfig)applicationContext.getBean(registryId, RegistryConfig.class));
                    }
                }
                serviceConfig.setRegistries(registryConfigs);
            }
            if (service.provider() != null && service.provider().length() > 0) {
                serviceConfig.setProvider((ProviderConfig)applicationContext.getBean(service.provider(),ProviderConfig.class));
            }
            if (service.monitor() != null && service.monitor().length() > 0) {
                serviceConfig.setMonitor((MonitorConfig)applicationContext.getBean(service.monitor(), MonitorConfig.class));
            }
            if (service.application() != null && service.application().length() > 0) {
                serviceConfig.setApplication((ApplicationConfig)applicationContext.getBean(service.application(), ApplicationConfig.class));
            }
            if (service.module() != null && service.module().length() > 0) {
                serviceConfig.setModule((ModuleConfig)applicationContext.getBean(service.module(), ModuleConfig.class));
            }
            if (service.provider() != null && service.provider().length() > 0) {
                serviceConfig.setProvider((ProviderConfig)applicationContext.getBean(service.provider(), ProviderConfig.class));
            } else {
            }
            if (service.protocol() != null && service.protocol().length > 0) {
                List<ProtocolConfig> protocolConfigs = new ArrayList<ProtocolConfig>();
                for (String protocolId : service.registry()) {
                    if (protocolId != null && protocolId.length() > 0) {
                        protocolConfigs.add((ProtocolConfig)applicationContext.getBean(protocolId, ProtocolConfig.class));
                    }
                }
                serviceConfig.setProtocols(protocolConfigs);
            }
            try {
                serviceConfig.afterPropertiesSet();
            } catch (RuntimeException e) {
                throw (RuntimeException) e;
            } catch (Exception e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
        }
        serviceConfig.setRef(bean);
        serviceConfigs.add(serviceConfig);
        serviceConfig.export();
    }
    return bean;
}


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<?> clazz = bean.getClass();
    if (AopUtils.isAopProxy(bean)) {
        clazz = AopUtils.getTargetClass(bean);
    }
    return clazz;
}


这样获取Class就可以这样:


Class<?> clazz = getBeanClass(bean);
Service service = clazz.getAnnotation(Service.class);



Class<?> clazz = getBeanClass(bean);


修改后完整的代码:

AnnotationBean.java


/*
 * Copyright 1999-2012 Alibaba Group.
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.roc.dubbo.config.spring;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.common.utils.ConcurrentHashSet;
import com.alibaba.dubbo.common.utils.ReflectUtils;
import com.alibaba.dubbo.config.AbstractConfig;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ConsumerConfig;
import com.alibaba.dubbo.config.ModuleConfig;
import com.alibaba.dubbo.config.MonitorConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.ProviderConfig;
import com.alibaba.dubbo.config.ReferenceConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.ServiceConfig;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.dubbo.config.spring.ReferenceBean;
import com.alibaba.dubbo.config.spring.ServiceBean;
/**
 * AnnotationBean
 * 
 * @author william.liangf
 * @export
 * 
 *         >>>修复@com.alibaba.dubbo.config.annotation.Service
 *         与Spring @Transactional 冲突
 */
public class AnnotationBean extends AbstractConfig
        implements DisposableBean, BeanFactoryPostProcessor, BeanPostProcessor, ApplicationContextAware {
    private static final long serialVersionUID = -7582802454287589552L;
    private static final Logger logger = LoggerFactory.getLogger(Logger.class);
    private String annotationPackage;
    private String[] annotationPackages;
    private final Set<ServiceConfig<?>> serviceConfigs = new ConcurrentHashSet<ServiceConfig<?>>();
    private final ConcurrentMap<String, ReferenceBean<?>> referenceConfigs = new ConcurrentHashMap<String, ReferenceBean<?>>();
    public String getPackage() {
        return annotationPackage;
    }
    public void setPackage(String annotationPackage) {
        this.annotationPackage = annotationPackage;
        this.annotationPackages = (annotationPackage == null || annotationPackage.length() == 0) ? null
                : Constants.COMMA_SPLIT_PATTERN.split(annotationPackage);
    }
    private ApplicationContext applicationContext;
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if (annotationPackage == null || annotationPackage.length() == 0) {
            logger.debug("未定义注解扫描包");
            return;
        }
        logger.info("扫描:" + annotationPackage);
        if (beanFactory instanceof BeanDefinitionRegistry) {
            try {
                // init scanner
                Class<?> scannerClass = ReflectUtils
                        .forName("org.springframework.context.annotation.ClassPathBeanDefinitionScanner");
                Object scanner = scannerClass
                        .getConstructor(new Class<?>[] { BeanDefinitionRegistry.class, boolean.class })
                        .newInstance(new Object[] { (BeanDefinitionRegistry) beanFactory, true });
                // add filter
                Class<?> filterClass = ReflectUtils
                        .forName("org.springframework.core.type.filter.AnnotationTypeFilter");
                Object filter = filterClass.getConstructor(Class.class).newInstance(Service.class);
                Method addIncludeFilter = scannerClass.getMethod("addIncludeFilter",
                        ReflectUtils.forName("org.springframework.core.type.filter.TypeFilter"));
                addIncludeFilter.invoke(scanner, filter);
                // scan packages
                String[] packages = Constants.COMMA_SPLIT_PATTERN.split(annotationPackage);
                Method scan = scannerClass.getMethod("scan", new Class<?>[] { String[].class });
                scan.invoke(scanner, new Object[] { packages });
            } catch (Throwable e) {
                // spring 2.0
            }
        }
    }
    public void destroy() throws Exception {
        for (ServiceConfig<?> serviceConfig : serviceConfigs) {
            try {
                serviceConfig.unexport();
            } catch (Throwable e) {
                logger.error(e.getMessage(), e);
            }
        }
        for (ReferenceConfig<?> referenceConfig : referenceConfigs.values()) {
            try {
                referenceConfig.destroy();
            } catch (Throwable e) {
                logger.error(e.getMessage(), e);
            }
        }
    }
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (!isMatchPackage(bean)) {
            return bean;
        }
        Class<?> clazz = getBeanClass(bean);
        Service service = clazz.getAnnotation(Service.class);
        // >>>>>>>>>>>>>>>>>>
        if (service != null) {
            ServiceBean<Object> serviceConfig = new ServiceBean<Object>(service);
            if (void.class.equals(service.interfaceClass()) && "".equals(service.interfaceName())) {
                if (bean.getClass().getInterfaces().length > 0) {
                    serviceConfig.setInterface(bean.getClass().getInterfaces()[0]);
                } else {
                    throw new IllegalStateException("Failed to export remote service class " + bean.getClass().getName()
                            + ", cause: The @Service undefined interfaceClass or interfaceName, and the service class unimplemented any interfaces.");
                }
            }
            if (applicationContext != null) {
                serviceConfig.setApplicationContext(applicationContext);
                if (service.registry() != null && service.registry().length > 0) {
                    List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();
                    for (String registryId : service.registry()) {
                        if (registryId != null && registryId.length() > 0) {
                            registryConfigs
                                    .add((RegistryConfig) applicationContext.getBean(registryId, RegistryConfig.class));
                        }
                    }
                    serviceConfig.setRegistries(registryConfigs);
                }
                if (service.provider() != null && service.provider().length() > 0) {
                    serviceConfig.setProvider(
                            (ProviderConfig) applicationContext.getBean(service.provider(), ProviderConfig.class));
                }
                if (service.monitor() != null && service.monitor().length() > 0) {
                    serviceConfig.setMonitor(
                            (MonitorConfig) applicationContext.getBean(service.monitor(), MonitorConfig.class));
                }
                if (service.application() != null && service.application().length() > 0) {
                    serviceConfig.setApplication((ApplicationConfig) applicationContext.getBean(service.application(),
                            ApplicationConfig.class));
                }
                if (service.module() != null && service.module().length() > 0) {
                    serviceConfig
                            .setModule((ModuleConfig) applicationContext.getBean(service.module(), ModuleConfig.class));
                }
                if (service.provider() != null && service.provider().length() > 0) {
                    serviceConfig.setProvider(
                            (ProviderConfig) applicationContext.getBean(service.provider(), ProviderConfig.class));
                } else {
                }
                if (service.protocol() != null && service.protocol().length > 0) {
                    List<ProtocolConfig> protocolConfigs = new ArrayList<ProtocolConfig>();
                    for (String protocolId : service.registry()) {
                        if (protocolId != null && protocolId.length() > 0) {
                            protocolConfigs
                                    .add((ProtocolConfig) applicationContext.getBean(protocolId, ProtocolConfig.class));
                        }
                    }
                    serviceConfig.setProtocols(protocolConfigs);
                }
                try {
                    serviceConfig.afterPropertiesSet();
                } catch (RuntimeException e) {
                    throw (RuntimeException) e;
                } catch (Exception e) {
                    throw new IllegalStateException(e.getMessage(), e);
                }
            }
            serviceConfig.setRef(bean);
            serviceConfigs.add(serviceConfig);
            serviceConfig.export();
        }
        return bean;
    }
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // logger.debug("扫描bean:"+bean);
        // System.out.println("扫描bean:"+bean);
        if (!isMatchPackage(bean)) {
            return bean;
        }
        // >>>>>>>>>>>>>>>>>>>>>>>>
        Class<?> clazz = getBeanClass(bean);
        // >>>>>>>>>>>>>>>>>>>>>>>>
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            String name = method.getName();
            if (name.length() > 3 && name.startsWith("set") && method.getParameterTypes().length == 1
                    && Modifier.isPublic(method.getModifiers()) && !Modifier.isStatic(method.getModifiers())) {
                try {
                    Reference reference = method.getAnnotation(Reference.class);
                    if (reference != null) {
                        Object value = refer(reference, method.getParameterTypes()[0]);
                        if (value != null) {
                            method.invoke(bean, new Object[] {});
                        }
                    }
                } catch (Throwable e) {
                    logger.error("Failed to init remote service reference at method " + name + " in class "
                            + bean.getClass().getName() + ", cause: " + e.getMessage(), e);
                }
            }
        }
        // >>>>>>>>>>>>>>>>>>>>>>>>
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            try {
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                Reference reference = field.getAnnotation(Reference.class);
                if (reference != null) {
                    Object value = refer(reference, field.getType());
                    if (value != null) {
                        field.set(bean, value);
                    }
                }
            } catch (Throwable e) {
                logger.error("Failed to init remote service reference at filed " + field.getName() + " in class "
                        + bean.getClass().getName() + ", cause: " + e.getMessage(), e);
            }
        }
        return bean;
    }
    private Object refer(Reference reference, Class<?> referenceClass) { // method.getParameterTypes()[0]
        String interfaceName;
        if (!"".equals(reference.interfaceName())) {
            interfaceName = reference.interfaceName();
        } else if (!void.class.equals(reference.interfaceClass())) {
            interfaceName = reference.interfaceClass().getName();
        } else if (referenceClass.isInterface()) {
            interfaceName = referenceClass.getName();
        } else {
            throw new IllegalStateException(
                    "The @Reference undefined interfaceClass or interfaceName, and the property type "
                            + referenceClass.getName() + " is not a interface.");
        }
        String key = reference.group() + "/" + interfaceName + ":" + reference.version();
        ReferenceBean<?> referenceConfig = referenceConfigs.get(key);
        if (referenceConfig == null) {
            referenceConfig = new ReferenceBean<Object>(reference);
            if (void.class.equals(reference.interfaceClass()) && "".equals(reference.interfaceName())
                    && referenceClass.isInterface()) {
                referenceConfig.setInterface(referenceClass);
            }
            if (applicationContext != null) {
                referenceConfig.setApplicationContext(applicationContext);
                if (reference.registry() != null && reference.registry().length > 0) {
                    List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();
                    for (String registryId : reference.registry()) {
                        if (registryId != null && registryId.length() > 0) {
                            registryConfigs
                                    .add((RegistryConfig) applicationContext.getBean(registryId, RegistryConfig.class));
                        }
                    }
                    referenceConfig.setRegistries(registryConfigs);
                }
                if (reference.consumer() != null && reference.consumer().length() > 0) {
                    referenceConfig.setConsumer(
                            (ConsumerConfig) applicationContext.getBean(reference.consumer(), ConsumerConfig.class));
                }
                if (reference.monitor() != null && reference.monitor().length() > 0) {
                    referenceConfig.setMonitor(
                            (MonitorConfig) applicationContext.getBean(reference.monitor(), MonitorConfig.class));
                }
                if (reference.application() != null && reference.application().length() > 0) {
                    referenceConfig.setApplication((ApplicationConfig) applicationContext
                            .getBean(reference.application(), ApplicationConfig.class));
                }
                if (reference.module() != null && reference.module().length() > 0) {
                    referenceConfig.setModule(
                            (ModuleConfig) applicationContext.getBean(reference.module(), ModuleConfig.class));
                }
                if (reference.consumer() != null && reference.consumer().length() > 0) {
                    referenceConfig.setConsumer(
                            (ConsumerConfig) applicationContext.getBean(reference.consumer(), ConsumerConfig.class));
                }
                try {
                    referenceConfig.afterPropertiesSet();
                } catch (RuntimeException e) {
                    throw (RuntimeException) e;
                } catch (Exception e) {
                    throw new IllegalStateException(e.getMessage(), e);
                }
            }
            referenceConfigs.putIfAbsent(key, referenceConfig);
            referenceConfig = referenceConfigs.get(key);
        }
        return referenceConfig.get();
    }
    private boolean isMatchPackage(Object bean) {
        if (annotationPackages == null || annotationPackages.length == 0) {
            return true;
        }
        Class<?> clazz = getBeanClass(bean);
        String beanClassName = clazz.getName();
        for (String pkg : annotationPackages) {
            if (beanClassName.startsWith(pkg)) {
                return true;
            }
        }
        return false;
    }
    private Class<?> getBeanClass(Object bean) {
        Class<?> clazz = bean.getClass();
        if (AopUtils.isAopProxy(bean)) {
            clazz = AopUtils.getTargetClass(bean);
        }
        return clazz;
    }


6. 整合


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

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

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


package com.roc.dubbo.boot.configuration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.roc.dubbo.config.spring.AnnotationBean;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
 * @author binghe
 * @description 以外挂方式整合修改后的Dubbo源码
 */
@Configuration
@Import(DubboBasicConfiguration.class)
@AutoConfigureAfter(DubboBasicConfiguration.class)
public class DubboConfiguration {
    private final Logger logger = LogManager.getLogger(getClass());
    @Bean
    // @DependsOn("dubboConfiguration")
    public AnnotationBean dubboAnnotationBean() {
        AnnotationBean annotationBean = new AnnotationBean();
        // annotationBean.setApplicationContext(applicationContext);
        // annotationBean.setPackage(dubboProperties.getAnnotationPackage());
        // annotationBean.setPackage("com.roc.pay");
        logger.info("加入自定义" + annotationBean);
        return annotationBean;
    }
}
相关文章
|
17天前
|
Java Spring
在使用Spring的`@Value`注解注入属性值时,有一些特殊字符需要注意
【10月更文挑战第9天】在使用Spring的`@Value`注解注入属性值时,需注意一些特殊字符的正确处理方法,包括空格、引号、反斜杠、新行、制表符、逗号、大括号、$、百分号及其他特殊字符。通过适当包裹或转义,确保这些字符能被正确解析和注入。
|
19天前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
31 0
|
13天前
|
Java API Spring
在 Spring 配置文件中配置 Filter 的步骤
【10月更文挑战第21天】在 Spring 配置文件中配置 Filter 是实现请求过滤的重要手段。通过合理的配置,可以灵活地对请求进行处理,满足各种应用需求。还可以根据具体的项目要求和实际情况,进一步深入研究和优化 Filter 的配置,以提高应用的性能和安全性。
|
5天前
|
XML JSON Java
SpringBoot必须掌握的常用注解!
SpringBoot必须掌握的常用注解!
23 4
SpringBoot必须掌握的常用注解!
|
5天前
|
Java Spring
[Spring]aop的配置与使用
本文介绍了AOP(面向切面编程)的基本概念和核心思想。AOP是Spring框架的核心功能之一,通过动态代理在不修改原代码的情况下注入新功能。文章详细解释了连接点、切入点、通知、切面等关键概念,并列举了前置通知、后置通知、最终通知、异常通知和环绕通知五种通知类型。
14 1
|
7天前
|
存储 缓存 Java
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
41 2
|
7天前
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
25 1
|
21天前
|
Java BI 调度
Java Spring的定时任务的配置和使用
遵循上述步骤,你就可以在Spring应用中轻松地配置和使用定时任务,满足各种定时处理需求。
107 1
|
2天前
|
存储 安全 Java
springboot当中ConfigurationProperties注解作用跟数据库存入有啥区别
`@ConfigurationProperties`注解和数据库存储配置信息各有优劣,适用于不同的应用场景。`@ConfigurationProperties`提供了类型安全和模块化的配置管理方式,适合静态和简单配置。而数据库存储配置信息提供了动态更新和集中管理的能力,适合需要频繁变化和集中管理的配置需求。在实际项目中,可以根据具体需求选择合适的配置管理方式,或者结合使用这两种方式,实现灵活高效的配置管理。
6 0
|
14天前
|
存储 Java 数据管理
强大!用 @Audited 注解增强 Spring Boot 应用,打造健壮的数据审计功能
本文深入介绍了如何在Spring Boot应用中使用`@Audited`注解和`spring-data-envers`实现数据审计功能,涵盖从添加依赖、配置实体类到查询审计数据的具体步骤,助力开发人员构建更加透明、合规的应用系统。