Dubbo 无侵入式本地自测

450 阅读4分钟

痛点分析

场景分析

日常工作中我们的开发环境会氛围d(研发),t(测试),q(准生产,或者预发环境)和生产环境,同样我们的dubbo注册中心也随着我们的环境的不同而不同.例如会在yml文件中进行如下配置

如果该zk只有你一个人注册,那么在你的应用端掉服务端时肯定只有你一个服务,可是日常开发中都是多个人用同一个zk,其他人的服务也注册到了该zk上,要是你们代码一致,那万事大吉,该怎么测怎么测,可是如果你的代码和别人代码有差异,蛋疼的事情来了,你会发现自己在掉服务的时候时好时坏.于是有了以下的思考

  • 让自己的队友暂时不要连zk,自己进行测试
  • 自己在本地debug自己的服务,自测验证完成后在提到d环境

貌似第二个方案比较合理,那么怎么才能在本地debug自己的服务呢?

解决方案

再稍微唠叨一下官网的配置吧,具体配置如下

<!--服务直连-->
<dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20890" />
<!--服务分组-->
<dubbo:service group="feedback" interface="com.xxx.IndexService" />
<dubbo:service group="member" interface="com.xxx.IndexService" />

<dubbo:reference id="feedbackIndexService" group="feedback" interface="com.xxx.IndexService" />
<dubbo:reference id="memberIndexService" group="member" interface="com.xxx.IndexService" />

分组还要配置那么多,相信大多数人都选服务直连,这时候又有人问了,小帅我们dubbo用的是注解啊,该怎么配呢? 先来看一下Reference注解里的内容

@Deprecated
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
public @interface Reference {
    //...省略若干代码,手动滑稽
    String group() default "";

    String url() default "";
    //...省略若干代码,手动滑稽
    }
    

可以看到Reference注解和xml配置基本一致,只需要配置group或者url属性即可,如下

@Reference(url="dubbo://localhost:20880")

好了,有了这两种法宝,兴致冲冲的本地自测完,提交代码,关机下班😁

回到家中后冥冥之中觉得有点不对劲,猛然一机灵发现自己的reference配置url="dubbo://localhost:20890"竟然也被提交了上去,完了完了,愚蠢的小帅教我这么写,也没让我在提交代码的时候去掉这些配置,等我过去喷一下他,这不是教我每天一个离职小技巧吗?还好发现的比较早,要是代码上线了那可不得了.

所以官网推荐的方法虽然简单,但是还是有风险的,如果你觉得自己比较谨慎,在提交代码代码前一定会检查并去掉这些无用配置的,那么恭喜你,下面的废话可以不用看了,可是你有没有思考过有没有其他方法来规避这种情况呢,不去修改配置文件或者@Reference属性,而且能直连本地,是不是想想都有点小兴奋

  • 利用spring框架中的扩展点来丰富bean,并且实现上述功能

刚好小帅研究过一阵spring源码,看完之后总想利用spring的扩展点来搞点事情,可是一直无门,总觉得spring源码白看了,那么我们的dubbo无侵入式本地自测能不能利用spring的扩展点来实现呢,talk is cheap, show me the code

@Component
@Slf4j
public class DubboConfigBeanProcessor implements BeanFactoryPostProcessor, EnvironmentAware {
    private Environment environment;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    //如果env值为dev,则给referenceBean添加属性dubbo://localhost:20880
        if("dev".equalsIgnoreCase(environment.getProperty("env"))){
            Map<String, ReferenceBean> beansOfType = configurableListableBeanFactory.getBeansOfType(ReferenceBean.class);
            beansOfType.forEach( (key, value) ->{
                value.setUrl("dubbo://localhost:20880");
            });
        }
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
}

好了,核心代码就这么点,那么有人会问了你为什么会知道要这么写,这个就要说说spring源码,小帅后续会出点spring源码的文章,在这先简单说点bean加载过程

synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}

关键就在invokeBeanFactoryPostProcessors(beanFactory);这一步中,BeanFactoryPostProcessors是spring提供的一个扩展点,如果有兴趣大家可以在小帅后续的文章中与小帅讨论相关东西.

我是有丶小帅,一个混迹在互联网公司的复制粘贴工程师,如果大家喜欢小帅的原创文章就多多支持啦,我们下一篇见!