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