dubbo-spring-boot-starter 源码分析

作者: 徐志毅 | 来源:发表于2018-06-21 23:04 被阅读0次

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的核心功能。

相关文章

网友评论

    本文标题:dubbo-spring-boot-starter 源码分析

    本文链接:https://www.haomeiwen.com/subject/hovneftx.html