dubbo-spring-boot-starter 源码分析

Spring boot 让我们的项目配置越来越简单,很多第三方也对spring boot进行来很好的继承。今天将要分析的是dubbo对spring boot的集成——dubbo-spring-boot-starter。
在spring boot项目中我们经常会见到各种各样的starter,那一个starter究竟做了什么,如何注入到我们的项目中去的呢?

项目地址:https://github.com/alibaba/dubbo-spring-boot-starter

关于dubbo starter的使用,可参见项目文档:https://github.com/alibaba/dubbo-spring-boot-starter/blob/master/README.md

dubbo starter的代码比较少,文件结构如下图所示:

.
|-- LICENSE
|-- README.md
|-- README_zh.md
|-- googlestyle-java.xml
|-- pom.xml
`-- src
    |-- main
    |   |-- java
    |   |   `-- com
    |   |       `-- alibaba
    |   |           `-- dubbo
    |   |               `-- spring
    |   |                   `-- boot
    |   |                       |-- DubboAutoConfiguration.java
    |   |                       |-- DubboCommonAutoConfiguration.java
    |   |                       |-- DubboConsumerAutoConfiguration.java
    |   |                       |-- DubboProperties.java
    |   |                       |-- DubboProviderAutoConfiguration.java
    |   |                       |-- annotation
    |   |                       |   `-- EnableDubboConfiguration.java
    |   |                       |-- bean
    |   |                       |   |-- ClassIdBean.java
    |   |                       |   `-- DubboSpringBootStarterConstants.java
    |   |                       |-- context
    |   |                       |   `-- event
    |   |                       |       `-- DubboBannerApplicationListener.java
    |   |                       |-- health
    |   |                       |   `-- DubboHealthIndicator.java
    |   |                       |-- listener
    |   |                       |   `-- ConsumerSubscribeListener.java
    |   |                       `-- server
    |   |                           `-- DubboServer.java
    |   `-- resources
    |       `-- META-INF
    |           |-- dubbo
    |           |   `-- com.alibaba.dubbo.rpc.InvokerListener
    |           |-- spring.factories
    |           `-- spring.provides
    `-- test
        `-- java
            `-- com
                `-- alibaba
                    `-- dubbo
                        `-- spring
                            `-- boot
                                |-- Mock.java
                                `-- testcase
                                    `-- ClassIdBeanTest.java

首先我们分析一下dubbo starter做了什么事情?

其实每一个spring boot项目的starter的本质都是一样:在你的spring容器中注入特定的bean来达到特定效果。

dubbo的核心是暴漏服务和代理消费。通过ServiceConfig来暴漏服务,通过ReferenceConfig来代理消费。dubbo starter正是围绕这两个核心来实现的。

dubbo starter入口

确切的说不是dubbo starter的入口,spring boot所有的starter的入口都一样,从spring boot提供的扩展方式spring.factories来切入。
dubbo starter的配置如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.dubbo.spring.boot.DubboAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboProviderAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboConsumerAutoConfiguration

org.springframework.context.ApplicationListener=\
com.alibaba.dubbo.spring.boot.context.event.DubboBannerApplicationListener

上述配置了三个Configration和一个ApplicationListener,DubboBannerApplicationListener我们暂时忽略,从名字就可以看出,只是注入dubbo的banner。我们重点关注三个Configration:

1、DubboAutoConfiguration
@Configuration
@EnableConfigurationProperties(DubboProperties.class)
public class DubboAutoConfiguration {

    /**
     * Start a non-daemon thread
     *
     * @return DubboServer
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "spring.dubbo", name = "server", havingValue = "true")
    public DubboServer dubboServer() {
        final DubboServer dubboServer = new DubboServer();
        final CountDownLatch latch = new CountDownLatch(1);
        Thread awaitThread = new Thread("dubboServer") {

            @Override
            public void run() {
                latch.countDown();
                dubboServer.await();
            }
        };
        awaitThread.setContextClassLoader(this.getClass().getClassLoader());
        awaitThread.setDaemon(false);
        awaitThread.start();
        try {
            latch.await();
        } catch (InterruptedException e) {
            throw new IllegalStateException(e);
        }

        return dubboServer;
    }

    @Bean
    public DubboHealthIndicator dubboHealthIndicator() {
        return new DubboHealthIndicator();
    }
}

启动了一个非守护线程,用来hold住dubbo服务。类似我常做的System.in.read。

2、DubboProviderAutoConfiguration

@Configuration
@ConditionalOnClass(Service.class)
@ConditionalOnBean(annotation = EnableDubboConfiguration.class)
@AutoConfigureAfter(DubboAutoConfiguration.class)
@EnableConfigurationProperties(DubboProperties.class)
public class DubboProviderAutoConfiguration extends DubboCommonAutoConfiguration {

    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private DubboProperties properties;

    @PostConstruct
    public void init() throws Exception {
        Map<String, Object> beanMap = this.applicationContext.getBeansWithAnnotation(Service.class);
        if (beanMap != null && beanMap.size() > 0) {
            this.initIdConfigMap(this.properties);
            for (Map.Entry<String, Object> entry : beanMap.entrySet()) {
                this.initProviderBean(entry.getKey(), entry.getValue());
            }
        }
    }

    private void initProviderBean(String beanName, Object bean) throws Exception {
        Service service = this.applicationContext.findAnnotationOnBean(beanName, Service.class);
        ServiceBean<Object> serviceConfig = new ServiceBean<Object>(service);
        if ((service.interfaceClass() == null || service.interfaceClass() == void.class)
            && (service.interfaceName() == null || "".equals(service.interfaceName()))) {
            Class<?>[] interfaces = bean.getClass().getInterfaces();
            if (interfaces.length > 0) {
                serviceConfig.setInterface(interfaces[0]);
            }
        }

        Environment environment = this.applicationContext.getEnvironment();
        String application = service.application();
        serviceConfig.setApplication(this.parseApplication(application, this.properties, environment,
                                                           beanName, "application", application));
        String module = service.module();
        serviceConfig.setModule(
            this.parseModule(module, this.properties, environment, beanName, "module", module));
        String[] registries = service.registry();
        serviceConfig.setRegistries(
            this.parseRegistries(registries, this.properties, environment, beanName, "registry"));
        String[] protocols = service.protocol();
        serviceConfig.setProtocols(
            this.parseProtocols(protocols, this.properties, environment, beanName, "registry"));
        String monitor = service.monitor();
        serviceConfig.setMonitor(
            this.parseMonitor(monitor, this.properties, environment, beanName, "monitor", monitor));
        String provider = service.provider();
        serviceConfig.setProvider(
            this.parseProvider(provider, this.properties, environment, beanName, "provider", provider));

        serviceConfig.setApplicationContext(this.applicationContext);
        serviceConfig.afterPropertiesSet();
        serviceConfig.setRef(bean);
        //关键方法 暴漏服务
        serviceConfig.export();
    }

    @Override
    protected String buildErrorMsg(String... errors) {
        if (errors == null || errors.length != 3) {
            return super.buildErrorMsg(errors);
        }
        return new StringBuilder().append("beanName=").append(errors[0]).append(", ").append(errors[1])
            .append("=").append(errors[2]).append(" not found in multi configs").toString();
    }
}

DubboProviderAutoConfiguration这个类用来暴漏dubbo服务,它继承了DubboCommonAutoConfiguration,获得一些解析能力。此类最核心的就是initProviderBean的最后一行代码 serviceConfig.export()。它将从spring容器中获取所有实现Service接口的bean,经过解析,最后通过serviceConfig来暴漏服务。

3、DubboConsumerAutoConfiguration

@Configuration
@ConditionalOnClass(Service.class)
@ConditionalOnBean(annotation = EnableDubboConfiguration.class)
@AutoConfigureAfter(DubboAutoConfiguration.class)
@EnableConfigurationProperties(DubboProperties.class)
public class DubboConsumerAutoConfiguration extends DubboCommonAutoConfiguration {

    private static final Map<ClassIdBean, Object> DUBBO_REFERENCES_MAP =
        new ConcurrentHashMap<ClassIdBean, Object>();

    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private DubboProperties properties;

    public static Object getDubboReference(ClassIdBean classIdBean) {
        return DUBBO_REFERENCES_MAP.get(classIdBean);
    }

    @Bean
    public BeanPostProcessor beanPostProcessor() {
        return new BeanPostProcessor() {

            @Override
            public Object postProcessBeforeInitialization(Object bean, String beanName)
                throws BeansException {
                Class<?> objClz;
                if (AopUtils.isAopProxy(bean)) {
                    objClz = AopUtils.getTargetClass(bean);
                } else {
                    objClz = bean.getClass();
                }

                try {
                    for (Field field : objClz.getDeclaredFields()) {
                        Reference reference = field.getAnnotation(Reference.class);
                        if (reference != null) {
                            DubboConsumerAutoConfiguration.this
                                .initIdConfigMap(DubboConsumerAutoConfiguration.this.properties);
                            ReferenceBean<?> referenceBean =
                                DubboConsumerAutoConfiguration.this.getConsumerBean(beanName, field, reference);
                            Class<?> interfaceClass = referenceBean.getInterfaceClass();
                            String group = referenceBean.getGroup();
                            String version = referenceBean.getVersion();
                            ClassIdBean classIdBean = new ClassIdBean(interfaceClass, group, version);
                            Object dubboReference =
                                DubboConsumerAutoConfiguration.DUBBO_REFERENCES_MAP.get(classIdBean);
                            if (dubboReference == null) {
                                synchronized (this) {
                                    // double check
                                    dubboReference =
                                        DubboConsumerAutoConfiguration.DUBBO_REFERENCES_MAP.get(classIdBean);
                                    if (dubboReference == null) {
                                        referenceBean.afterPropertiesSet();
                                        // dubboReference should not be null, otherwise it will cause
                                        // NullPointerException
                                        dubboReference = referenceBean.getObject();
                                        DubboConsumerAutoConfiguration.DUBBO_REFERENCES_MAP.put(classIdBean,
                                                                                                dubboReference);
                                    }
                                }
                            }
                            field.setAccessible(true);
                            field.set(bean, dubboReference);
                        }
                    }
                } catch (Exception e) {
                    throw new BeanCreationException(beanName, e);
                }
                return bean;
            }

            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName)
                throws BeansException {
                return bean;
            }
        };
    }

    /**
     * init consumer bean
     *
     * @param reference      reference
     * @return ReferenceBean<T>
     * @throws BeansException BeansException
     */
    private <T> ReferenceBean<T> getConsumerBean(String beanName, Field field, Reference reference)
        throws BeansException {
        ReferenceBean<T> referenceBean = new ReferenceBean<T>(reference);
        if ((reference.interfaceClass() == null || reference.interfaceClass() == void.class)
            && (reference.interfaceName() == null || "".equals(reference.interfaceName()))) {
            referenceBean.setInterface(field.getType());
        }

        Environment environment = this.applicationContext.getEnvironment();
        String application = reference.application();
        referenceBean.setApplication(this.parseApplication(application, this.properties, environment,
                                                           beanName, field.getName(), "application", application));
        String module = reference.module();
        referenceBean.setModule(this.parseModule(module, this.properties, environment, beanName,
                                                 field.getName(), "module", module));
        String[] registries = reference.registry();
        referenceBean.setRegistries(this.parseRegistries(registries, this.properties, environment,
                                                         beanName, field.getName(), "registry"));
        String monitor = reference.monitor();
        referenceBean.setMonitor(this.parseMonitor(monitor, this.properties, environment, beanName,
                                                   field.getName(), "monitor", monitor));
        String consumer = reference.consumer();
        referenceBean.setConsumer(this.parseConsumer(consumer, this.properties, environment, beanName,
                                                     field.getName(), "consumer", consumer));

        referenceBean.setApplicationContext(DubboConsumerAutoConfiguration.this.applicationContext);
        return referenceBean;
    }

    @Override
    protected String buildErrorMsg(String... errors) {
        if (errors == null || errors.length != 4) {
            return super.buildErrorMsg(errors);
        }
        return new StringBuilder().append("beanName=").append(errors[0]).append(", field=")
            .append(errors[1]).append(", ").append(errors[2]).append("=").append(errors[3])
            .append(" not found in multi configs").toString();
    }
}

DubboConsumerAutoConfiguration是消费端的关键类,核心方法是beanPostProcessor(),该方法会创建一个Spring 的 BeanPostProcessor,并实现postProcessBeforeInitialization,在bean初始化完成后实现,判断bean是否是Reference,如果是Reference,则为其生成代理类。

通过上面三个类就完成了dubbo-starter的核心功能。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容