乐趣区

关于dubbo:Dubbo-自定义-ReferenceAnnotationBeanPostProcessor以支持灵活配置-group

缘起

是日,公司小张在测试环境调用 Dubbo 服务遇到了点问题:

  1. 蜡笔小新服务调用了用户服务和小泥鳅服务;
  2. 用户服务的 Dubbo 分组只有一个:分组 A,小泥鳅服务的 Dubbo 分组有好几个:分组 A、分组 B、分组 C …;
  3. 小张须要与小泥鳅服务 分组 B 上的实例进行联调;

须要如何配置呢?小张犯愁了。

事态倒退

首先,要阐明的是,公司中应用的 Dubbo 版本是 com.alibaba:dubbo:2.6.3

Dubbo 原生反对哪些配置

全局配置

应用如下全局配置,用户服务和小泥鳅服务都会到 分组 B 查找 service,最终会导致用户服务的 service 查找失败。

spring.dubbo.registry.group = 分组 B 
@Reference

应用 @Reference 配置分组,这样每个 service 都要配置一下,比拟麻烦。

@Reference(group = "分组 B")
private lateinit var serviceC: ServiceC

有没有更好的形式

于是,小张信心批改 Dubbo 底层,以更好反对这一需要。

确定指标

目前的服务状况大略是这样的:

用户服务 service:com.xz.user.serviceA
com.xz.user.serviceB
小泥鳅服务 service:com.xz.loach.serviceC
com.xz.loach.serviceD

如果反对这样的配置,就很好:

# 所有 com.xz.loach 下的 service,都到 分组 B 去查找
dubbo.custom.groups = {"com.xz.loach.*": "分组 B"}
实现剖析

通过追踪 @Reference 被援用的中央,最终找到上面这段代码,是很好的切入点:

public class ReferenceAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
        implements MergedBeanDefinitionPostProcessor, PriorityOrdered, ApplicationContextAware, BeanClassLoaderAware,
        DisposableBean {

    // ......

    private ReferenceBean<?> buildReferenceBean(Reference reference, Class<?> referenceClass) throws Exception {String referenceBeanCacheKey = generateReferenceBeanCacheKey(reference, referenceClass);

        ReferenceBean<?> referenceBean = referenceBeansCache.get(referenceBeanCacheKey);

        if (referenceBean == null) {

            ReferenceBeanBuilder beanBuilder = ReferenceBeanBuilder
                    .create(reference, classLoader, applicationContext)
                    .interfaceClass(referenceClass);

            referenceBean = beanBuilder.build();

            referenceBeansCache.putIfAbsent(referenceBeanCacheKey, referenceBean);

        }

        return referenceBean;

    }

    // ......
    
}

无奈的是,buildReferenceBeanprivate 的,它既不能被继承,又不能被代理,仿佛只能祭出 ASM 了。但,小张想了想,想到了另一个损招:间接复制 ReferenceAnnotationBeanPostProcessor 的代码,把 buildReferenceBean 改成 public,而后将 ReferenceAnnotationBeanPostProcessor 整个替换掉。

最终的实现如下:

  • 复制 ReferenceAnnotationBeanPostProcessor

ReferenceAnnotationBeanPostProcessor 又引入了几个默认作用域的类,所以最终复制了以下几个类。

AbstractAnnotationConfigBeanBuilder.java
AnnotationPropertyValuesAdapter.java
ReferenceAnnotationBeanPostProcessor.java
ReferenceBeanBuilder.java
  • 定义配置类:
@Configuration
class CustomDubboProperties {

    /**
     * groups 配置样例:* dubbo.custom.groups = {"com.xz.loach.*": "分组 B"}
     */
    @Value("\${dubbo.custom.groups:}")
    lateinit var groups: String

}
  • 自定义 ReferenceAnnotationBeanPostProcessor
@Component
class CustomReferenceAnnotationBeanPostProcessor : ReferenceAnnotationBeanPostProcessor() {private val logger = LoggerFactory.getLogger(CustomReferenceAnnotationBeanPostProcessor::class.java)

    private lateinit var applicationContext: ApplicationContext

    private var groupMap: Map<String, String>? = null

    @Throws(BeansException::class)
    override fun setApplicationContext(applicationContext: ApplicationContext) {
        this.applicationContext = applicationContext
        super.setApplicationContext(applicationContext)
    }

    @Throws(Exception::class)
    override fun buildReferenceBean(reference: Reference?, referenceClass: Class<*>?): ReferenceBean<*>? {if (reference == null || referenceClass == null) {return super.buildReferenceBean(reference, referenceClass)
        }

        // 查找能匹配上的 group
        val antPathMatcher = AntPathMatcher()
        var group: String? = null
        getGroupMap().map {if (antPathMatcher.match(it.key, referenceClass.name)) {
                group = it.value
                return@map
            }
        }
        if (group.isNullOrBlank()) {return super.buildReferenceBean(reference, referenceClass)
        }

        // 替换 group
        val invocationHandler = Proxy.getInvocationHandler(reference)
        val memberValuesField = invocationHandler.javaClass.getDeclaredField("memberValues")
        memberValuesField.setAccessible(true)
        val memberValuesMap = memberValuesField[invocationHandler] as MutableMap<Any, Any>
        memberValuesMap["group"] = group!!

        logger.info("将 [{}] 的 group 替换为:{}", referenceClass.name, group)
        return super.buildReferenceBean(reference, referenceClass)
    }

    private fun getGroupMap(): Map<String, String> {if (groupMap != null) {return groupMap!!}

        val customDubboProperties = applicationContext.getBean(CustomDubboProperties::class.java)
        val groups = customDubboProperties.groups
        if (groups.isNullOrBlank()) {groupMap = mapOf()
            return groupMap!!
        }

        try {groupMap = JsonUtil.toBean(groups, Map::class.java) as Map<String, String>
        } catch (e: Exception) {logger.error("property \${dubbo.custom.groups} config error", e)
            groupMap = mapOf()}
        return groupMap!!
    }

}
  • 应用自定义类替换 ReferenceAnnotationBeanPostProcessor
@Component
class CustomDubboBeanFactoryPostProcessor : BeanFactoryPostProcessor {private val logger: Logger = LoggerFactory.getLogger(CustomDubboBeanFactoryPostProcessor::class.java)

    @Throws(BeansException::class)
    override fun postProcessBeanFactory(beanFactory: ConfigurableListableBeanFactory) {logger.info("应用自定义的 CustomReferenceAnnotationBeanPostProcessor,替换 com.alibaba:dubbo:2.6.3 的 ReferenceAnnotationBeanPostProcessor")
        val beanName = ReferenceAnnotationBeanPostProcessor.BEAN_NAME
        val beanDefinition = beanFactory.getBeanDefinition(beanName) as AbstractBeanDefinition
        beanDefinition.setBeanClass(CustomReferenceAnnotationBeanPostProcessor::class.java)
    }

}

功败垂成。最终,小张高高兴兴地通过上面的配置,灵便配置不同服务的 group。再也不必放心简单的测试环境了。

dubbo.custom.groups = {"com.xz.loach.*": "分组 B"}
退出移动版