Spring IOC(十四)Dubbo 与Spring 集成 - @Reference和@Service 原理

上一篇文章围绕 @EnableDubbo 进行了深入的分析,本篇文章将重点看@Service@Reference 原理。
与上面两个注解相关联两个Bean类分别为:

  1. ServiceAnnotationBeanPostProcessor
  2. ReferenceAnnotationBeanPostProcessor

ServiceAnnotationBeanPostProcessor

上一篇文章中,ServiceAnnotationBeanPostProcessor 是在 DubboComponentScan 中,在 DubboComponentScanRegistrar 中,被注册到Spring 容器中。

public class ServiceAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,
        ResourceLoaderAware, BeanClassLoaderAware {
        ...
        }

ServiceAnnotationBeanPostProcessor 最核心的角色是 一个 BeanDefinitionRegistryPostProcessor,这样能够在refresh中对BeanFactoryPostProcessor处理时,对 Spring 容器中bean定义进行删改。

里面主要方法为 postProcessBeanDefinitionRegistry:

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

        // 注册 DubboBootstrapApplicationListener
        registerBeans(registry, DubboBootstrapApplicationListener.class);
		// 获取扫描路径
        Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);

        if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
        // 具体扫描
            registerServiceBeans(resolvedPackagesToScan, registry);
        } else {
            if (logger.isWarnEnabled()) {
                logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
            }
        }

    }

上面方法则中,普通获取扫描包路径,最终调用 registerServiceBeans 进行扫描:

    private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
		// 构建一个 DubboClassPathBeanDefinitionScanner,父类为 Spring 的 ClassPathBeanDefinitionScanner
        DubboClassPathBeanDefinitionScanner scanner =
                new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
		// 获取一个beanGenerator
        BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
        scanner.setBeanNameGenerator(beanNameGenerator);
        // 添加过滤类,即只扫描@Service注解的类。
        scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
		// 添加对旧版本支持
        scanner.addIncludeFilter(new AnnotationTypeFilter(com.alibaba.dubbo.config.annotation.Service.class));

        for (String packageToScan : packagesToScan) {

            // 扫描,并且会将@Service 注解的类都注册进Spring 
            scanner.scan(packageToScan);

            // 获取所有的 @Service 的 BeanDefinitionHolders 
            Set<BeanDefinitionHolder> beanDefinitionHolders =
                    findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);

            if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {

                for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
                	// 将上一步中获取到的所有 @Service 注解bean,都注册到ServiceBean类型到 Spirng中
                    registerServiceBean(beanDefinitionHolder, registry, scanner);
                }
                if (logger.isInfoEnabled()) {
                    logger.info(beanDefinitionHolders.size() + " annotated Dubbo's @Service Components { " +
                            beanDefinitionHolders +
                            " } were scanned under package[" + packageToScan + "]");
                }
            } else {
                if (logger.isWarnEnabled()) {
                    logger.warn("No Spring Bean annotating Dubbo's @Service was found under package["
                            + packageToScan + "]");
                }
            }
        }
    }

上面方法中,主要有以下几步操作:

  1. 构建一个 DubboClassPathBeanDefinitionScanner,父类为 SpringClassPathBeanDefinitionScanner,这是Spring提供出来一个通用扫描器,其内置的 @Conponent 扫描,以及 Mybatis 中扫描,也是以这个类为主。
  2. 将 所有扫描出 的 @Service Bean类,通过registerServiceBean 注册进成为Spring的Bean。

registerServiceBean :

    private void registerServiceBean(BeanDefinitionHolder beanDefinitionHolder, BeanDefinitionRegistry registry,
                                     DubboClassPathBeanDefinitionScanner scanner) {
		// 获取到BeanClass
        Class<?> beanClass = resolveClass(beanDefinitionHolder);
        Annotation service = findServiceAnnotation(beanClass);
		// 获取 @Service 注解的参数
        AnnotationAttributes serviceAnnotationAttributes = getAnnotationAttributes(service, false, false);
		// 获取接口名
        Class<?> interfaceClass = resolveServiceInterfaceClass(serviceAnnotationAttributes, beanClass);
		// 获取ServiceBean名字
        String annotatedServiceBeanName = beanDefinitionHolder.getBeanName();
		// 构造一个 serviceBean
        AbstractBeanDefinition serviceBeanDefinition =
                buildServiceBeanDefinition(service, serviceAnnotationAttributes, interfaceClass, annotatedServiceBeanName);

        // ServiceBean Bean name
        String beanName = generateServiceBeanName(serviceAnnotationAttributes, interfaceClass);

        if (scanner.checkCandidate(beanName, serviceBeanDefinition)) { 
        	// 如果没有注册过,那么久进行注册
            registry.registerBeanDefinition(beanName, serviceBeanDefinition);
            if (logger.isInfoEnabled()) {
                logger.info("The BeanDefinition[" + serviceBeanDefinition +
                        "] of ServiceBean has been registered with name : " + beanName);
            }
        } else {
            if (logger.isWarnEnabled()) {
                logger.warn("The Duplicated BeanDefinition[" + serviceBeanDefinition +
                        "] of ServiceBean[ bean name : " + beanName +
                        "] was be found , Did @DubboComponentScan scan to same package in many times?");
            }
        }
    }

上面注册方法中,包括以下三个逻辑:

  1. 获取@Service 参数
  2. 获取ServiceBean名字
  3. 构造一个 ServiceBean 类型 BeanDefinition
  4. ServiceBean注册进Spring 容器

整个过程最终要的就是 ServiceBean 构造过程,即 buildServiceBeanDefinition 方法:

    private AbstractBeanDefinition buildServiceBeanDefinition(Annotation serviceAnnotation,
                                                              AnnotationAttributes serviceAnnotationAttributes,
                                                              Class<?> interfaceClass,
                                                              String annotatedServiceBeanName) {
        BeanDefinitionBuilder builder = rootBeanDefinition(ServiceBean.class);
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
        String[] ignoreAttributeNames = of("provider", "monitor", "application", "module", "registry", "protocol",
                "interface", "interfaceName", "parameters");
        propertyValues.addPropertyValues(new AnnotationPropertyValuesAdapter(serviceAnnotation, environment, ignoreAttributeNames));
        // References "ref" property to annotated-@Service Bean
        addPropertyReference(builder, "ref", annotatedServiceBeanName);
        // Set interface
        builder.addPropertyValue("interface", interfaceClass.getName());
        // Convert parameters into map
        builder.addPropertyValue("parameters", convertParameters(serviceAnnotationAttributes.getStringArray("parameters")));
        // Add methods parameters
        List<MethodConfig> methodConfigs = convertMethodConfigs(serviceAnnotationAttributes.get("methods"));
        if (!methodConfigs.isEmpty()) {
            builder.addPropertyValue("methods", methodConfigs);
        }
        /**
         * Add {@link org.apache.dubbo.config.ProviderConfig} Bean reference
         */
        String providerConfigBeanName = serviceAnnotationAttributes.getString("provider");
        if (StringUtils.hasText(providerConfigBeanName)) {
            addPropertyReference(builder, "provider", providerConfigBeanName);
        }
        /**
         * Add {@link org.apache.dubbo.config.MonitorConfig} Bean reference
         */
        String monitorConfigBeanName = serviceAnnotationAttributes.getString("monitor");
        if (StringUtils.hasText(monitorConfigBeanName)) {
            addPropertyReference(builder, "monitor", monitorConfigBeanName);
        }
        /**
         * Add {@link org.apache.dubbo.config.ApplicationConfig} Bean reference
         */
        String applicationConfigBeanName = serviceAnnotationAttributes.getString("application");
        if (StringUtils.hasText(applicationConfigBeanName)) {
            addPropertyReference(builder, "application", applicationConfigBeanName);
        }
        /**
         * Add {@link org.apache.dubbo.config.ModuleConfig} Bean reference
         */
        String moduleConfigBeanName = serviceAnnotationAttributes.getString("module");
        if (StringUtils.hasText(moduleConfigBeanName)) {
            addPropertyReference(builder, "module", moduleConfigBeanName);
        }
        /**
         * Add {@link org.apache.dubbo.config.RegistryConfig} Bean reference
         */
        String[] registryConfigBeanNames = serviceAnnotationAttributes.getStringArray("registry");
        List<RuntimeBeanReference> registryRuntimeBeanReferences = toRuntimeBeanReferences(registryConfigBeanNames);
        if (!registryRuntimeBeanReferences.isEmpty()) {
            builder.addPropertyValue("registries", registryRuntimeBeanReferences);
        }
        /**
         * Add {@link org.apache.dubbo.config.ProtocolConfig} Bean reference
         */
        String[] protocolConfigBeanNames = serviceAnnotationAttributes.getStringArray("protocol");
        List<RuntimeBeanReference> protocolRuntimeBeanReferences = toRuntimeBeanReferences(protocolConfigBeanNames);
        if (!protocolRuntimeBeanReferences.isEmpty()) {
            builder.addPropertyValue("protocols", protocolRuntimeBeanReferences);
        }
        return builder.getBeanDefinition();
    }

围绕两个问题说明:

  1. @Service 注解 的bean,最终注入到Spring中,是以 ServiceBean 方式存在。
  2. 注册进Spring时候,需要将对应属性填充到 BeanDefinition 中,大概有以下:
  • 包括基本的组件 "provider", "monitor", "application", "module", "registry", "protocol","interface", "interfaceName", "parameters"
  • ref 属性,默认为实现类名字
  • interface属性,暴露接口对应的接口类
  • parameters 属性
  • methods 属性
  • provider 属性,即 ProviderConfig,即确定使用哪一份ProviderConfig
  • monitor 属性,同上,可以指定哪一份配置
  • application 属性
  • module 属性
  • registry(registries) 属性
  • protocol(protocols) 属性

最终,这就是一个 @Service 在Spring 中初始化过程,但是似乎还少了些什么,读过博主以前博客同学应该知道,Dubbo的接口暴露过程远不止这些,最重要的还要执行export方法呀。

其实这个问题的答案,在上一篇文章最后已经给出,在Spring容器完全初始化完之后,由 DubboBootstrapexportServices 将所有ServiceBean 执行其 export 方法。

还有一个问题,如果使用 @Service 暴露后,在本地,可以使用 @Autowired 之类的注入注解 引用到吗?
答案是可以,因为Dubbo会往Spring中注入两种类型bean:

  1. 对应接口 实现类的bean(扫描时注入)
  2. ServiceBean类型bean

ReferenceAnnotationBeanPostProcessor

ReferenceAnnotationBeanPostProcessor 是为了处理 @Reference 注解,运行原理和 AutowiredAnnotationBeanPostProcessor (处理@Autowired)很相似。

public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBeanPostProcessor implements
        ApplicationContextAware, ApplicationListener {
	...
	}

对应的父类为:

public abstract class AbstractAnnotationBeanPostProcessor extends
        InstantiationAwareBeanPostProcessorAdapter implements MergedBeanDefinitionPostProcessor, PriorityOrdered,
        BeanFactoryAware, BeanClassLoaderAware, EnvironmentAware, DisposableBean {
	}

ReferenceAnnotationBeanPostProcessor 关键的接口超类为: InstantiationAwareBeanPostProcessorAdapterMergedBeanDefinitionPostProcessor
从调用关系来看,MergedBeanDefinitionPostProcessor 在实例化前被调用(具体原因看博主前面文章哦),postProcessMergedBeanDefinition 优先调用,先看其方法:

    @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
        if (beanType != null) {
        	// 获取所有的@Reference注解字段并缓存
            InjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null);
            // 校验
            metadata.checkConfigMembers(beanDefinition);
        }
    }

上面方法中,重要的是 findInjectionMetadata 方法,主要目的:

  1. 扫描所有非static 的字段和方法,并进行缓存。
    这一步和 @AutowiredAutowiredAnnotationBeanPostProcessor 处理逻辑一样,可以参考博主以前文章:
    https://blog.csdn.net/anLA_/article/details/104507941

当在每个Bean实例化前,找完其所有的@Reference 注解过的字段和方法后,就进入到 InstantiationAwareBeanPostProcessorAdapter 的作用范围了。
InstantiationAwareBeanPostProcessorAdapterpostProcessPropertyValues 会在实例化时被调用:

    public PropertyValues postProcessPropertyValues(
            PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
        InjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
        try {
            metadata.inject(bean, beanName, pvs);
        } catch (BeanCreationException ex) {
            throw ex;
        } catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Injection of @" + getAnnotationType().getSimpleName()
                    + " dependencies is failed", ex);
        }
        return pvs;
    }

上面就是实例化之后,具体的初始化动作中执行,还是那个方法 findInjectionMetadata 获取到 InjectionMetadata, 而后进行注入。
对 获取到的 InjectMetadata 进行注入:

	public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
		Collection<InjectedElement> checkedElements = this.checkedElements;
		Collection<InjectedElement> elementsToIterate =
				(checkedElements != null ? checkedElements : this.injectedElements);
		if (!elementsToIterate.isEmpty()) {
			for (InjectedElement element : elementsToIterate) {
				if (logger.isTraceEnabled()) {
					logger.trace("Processing injected element of bean '" + beanName + "': " + element);
				}
				element.inject(target, beanName, pvs);
			}
		}
	}

此处的 InjectedElement 包括 两种类型 AnnotatedMethodElementAnnotatedFieldElement。这两种都是在 findInjectionMetadata 已经获取到的数据。
AnnotatedFieldElement 为例:

    public class AnnotatedFieldElement extends InjectionMetadata.InjectedElement {
        private final Field field;
        private final AnnotationAttributes attributes;
        private volatile Object bean;
        protected AnnotatedFieldElement(Field field, AnnotationAttributes attributes) {
            super(field, null);
            this.field = field;
            this.attributes = attributes;
        }
        @Override
        protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
            Class<?> injectedType = field.getType();
            Object injectedObject = getInjectedObject(attributes, bean, beanName, injectedType, this);
            ReflectionUtils.makeAccessible(field);
            field.set(bean, injectedObject);
        }
    }

对应 inject 方法逻辑,首先是获取对应类型,而后调用具体子类的 getInjectedObject 获取bean实例,最后反射调用set方法注入值。
AbstactAnnotationBeanPostProcessorgetInjectedObject 中,则是定义了一层缓存,如果缓存中没有,则再从子类中获取:

    protected Object getInjectedObject(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
                                       InjectionMetadata.InjectedElement injectedElement) throws Exception {
        String cacheKey = buildInjectedObjectCacheKey(attributes, bean, beanName, injectedType, injectedElement);
        // 从缓存中获取
        Object injectedObject = injectedObjectsCache.get(cacheKey);
        if (injectedObject == null) {
        // 调用子类创建
            injectedObject = doGetInjectedBean(attributes, bean, beanName, injectedType, injectedElement);
            // Customized inject-object if necessary
            injectedObjectsCache.putIfAbsent(cacheKey, injectedObject);
        }
        return injectedObject;
    }

最终还是回到了 ReferenceAnnotationBeanPostProcessor,看它的 doGetInjectedBean 方法:

    protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
                                       InjectionMetadata.InjectedElement injectedElement) throws Exception {
         // 创建一个ServiceBean对应名字,例如    ServiceBean:com.anla.rpc.configcenter.provider.service.HelloService:1.0.0
        String referencedBeanName = buildReferencedBeanName(attributes, injectedType);
        // 生成一个@Reference bean对应名字,如果有id属性,则直接用id属性替代。 @Reference(check=false,version=1.0.0) com.anla.rpc.configcenter.provider.service.HelloService
        String referenceBeanName = getReferenceBeanName(attributes, injectedType);
        // 以 injectedElement 为蓝本,创建一个 @ReferenceBean实例
        ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType);
        // 注册到Spring中
        registerReferenceBean(referencedBeanName, referenceBean, attributes, injectedType);
        // 缓存一份bean实例
        cacheInjectedReferenceBean(referenceBean, injectedElement);
        // 返回
        return getOrCreateProxy(referencedBeanName, referenceBeanName, referenceBean, injectedType);
    }

上面方法有以下几点关键逻辑:

  1. buildReferenceBeanIfAbsent 方法里面其实也做了挺多复杂逻辑,包括检查依赖,检查Spring 各层级组件等:
    ReferenceBeanBuilder 的 build方法
    public final C build() throws Exception {
        checkDependencies();
        C configBean = doBuild();
        configureBean(configBean);
        if (logger.isInfoEnabled()) {
            logger.info("The configBean[type:" + configBean.getClass().getSimpleName() + "] has been built.");
        }
        return configBean;
    }
  1. 构建出对应的@Service 方法的bean名字,用于判断是否 @Reference 的对象就在本地,如果就在本地,则注册一个 @Reference 对象的别名。这一点在 registerReferenceBean 有体现:
    private void registerReferenceBean(String referencedBeanName, ReferenceBean referenceBean,
                                       AnnotationAttributes attributes,
                                       Class<?> interfaceClass) {

        ConfigurableListableBeanFactory beanFactory = getBeanFactory();

        String beanName = getReferenceBeanName(attributes, interfaceClass);

        if (existsServiceBean(referencedBeanName)) { // If @Service bean is local one
            /**
             * Get  the @Service's BeanDefinition from {@link BeanFactory}
             * Refer to {@link ServiceAnnotationBeanPostProcessor#buildServiceBeanDefinition}
             */
            AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) beanFactory.getBeanDefinition(referencedBeanName);
            RuntimeBeanReference runtimeBeanReference = (RuntimeBeanReference) beanDefinition.getPropertyValues().get("ref");
            // The name of bean annotated @Service
            String serviceBeanName = runtimeBeanReference.getBeanName();
            // register Alias rather than a new bean name, in order to reduce duplicated beans
            beanFactory.registerAlias(serviceBeanName, beanName);
        } else { // Remote @Service Bean
            if (!beanFactory.containsBean(beanName)) {
                beanFactory.registerSingleton(beanName, referenceBean);
            }
        }
  1. 如果不是在本地,则直接注入单例到Spring容器中。beanFactory.registerSingleton(beanName, referenceBean);
  2. 缓存注入的bean,以及inject对象。
  3. 在最后一步的 getOrCreateProxy 中,仍然会以 是否就是 @Reference 本地对象为基础:
    private Object getOrCreateProxy(String referencedBeanName, String referenceBeanName, ReferenceBean referenceBean, Class<?> serviceInterfaceType) {
        if (existsServiceBean(referencedBeanName)) { // If the local @Service Bean exists, build a proxy of ReferenceBean
        // 如果存在@Service对象,则返回一个代理对象
            return newProxyInstance(getClassLoader(), new Class[]{serviceInterfaceType},
                    wrapInvocationHandler(referenceBeanName, referenceBean));
        } else {
        // 默认是立刻获取
            return referenceBean.get();
        }

referenceBean.get(); 就是dubbo接口的refer过程,这个看博主dubbo系列文章即可清楚。

ReferenceBean 本身是一个 FactoryBean 类型,其bean实例可以动态制定,主要在 getObject方法中体现:

    public Object getObject() {
        return get();
    }

整个 @Service 暴露和 @Reference 注入原理过程即已讲清楚,
但是感觉目前最新版本(2.7.7-SNAPSHOT) 在 @Reference 处理上,还有点小缺陷,已经有了处理,打算提交Issue和PR反馈给社区,等博主后面文章具体分析。

觉得博主写的有用,不妨关注博主公众号: 六点A君。
哈哈哈,一起研究Spring:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值