Dubbo Spi机制

Dubbo三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

1.SPI机制

JDK SPI 机制就存在以下一些问题:

  1. 实现类会被全部遍历并且实例化,假如我们只需要使用其中的一个实现,这在实现类很多的情况下无疑是对机器资源巨大的浪费。
  2. 无法按需获取实现类,不够灵活,我们需要遍历一遍所有实现类才能找到指定实现。

Dubbo SPI 以 JDK SPI 为参考做出了改进设计,进行了性能优化【kv缓存】以及功能增强,Dubbo SPI 机制的出现解决了上述问题。除此之外,Dubbo 的 SPI 还支持自适应扩展以及 IOC 和 AOP 等高级特性。

//扩展接口类型
private final Class<?> type;
//存储了扩展实现类和扩展名的关系
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
//存储了扩展名和扩展实现类之间的关系
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
//存储了扩展实现类类名与实例对象之间的关系
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
//扩展接口对@SPI注解配置的value
private String cachedDefaultName;

Dubbo SPI 的配置做出了改进,在 Dubbo 中有三种不同的目录可以存放 SPI 配置,用途也不同,优先级由低到高如下所示:

  1. META-INF/services/ 目录:此目录配置文件用于兼容 JDK SPI 【ServicesLoadingStrategy】。
  2. META-INF/dubbo/ 目录:此目录用于存放用户自定义 SPI 配置文件【DubboLoadingStrategy】。
  3. META-INF/dubbo/internal/ 目录:此目录用于存放 Dubbo 内部使用的 SPI 配置文件【DubboInternalLoadingStrategy】。

其中,只有ServicesLoadingStrategy在其对应目录文件中是不支持kv形式的。

public class ExtensionLoader<T> {
	// 利用JDK Spi机制提前初始化 LoadingStrategy的三个实现类
	private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
}

@SPI注解可以指定生效候选类的扩展名,否则扩展名选值为类名。候选类可以通过注解@Extension指定当前候选类的扩展名。

策略LoadingStrategy最终利用类加载器加载候选类资源的URL,得到候选类的二进制流,逐行读取,…,解析…返回。

2.核心注解分析

目前Dubbo支持两种注册方式:应用级别以及接口级别的。

初学Dubbo在Web应用中的使用时经常用的注解为:@EnableDubbo@DubboReference@DubboService三个,下面简单分析一下注解是如何发挥作用的。

@EnableDubbo注解主要Import两个 ImportBeanDefinitionRegistrar 类型的子类:DubboComponentScanRegistrar、DubboConfigConfigurationRegistrar。但是DubboConfigConfigurationRegistrar可能是早期的版本,其功能完全可以被DubboComponentScanRegistrar取代。

DubboComponentScanRegistrar内部主要是在Spring IOC中增加类DubboSpringInitContext、接口级别的ModuleModel、应用级别的ApplicationModel。并且注册一些Dubbo框架需要使用到的核心bean,例如DubboDeployApplicationListener、DubboInfraBeanRegisterPostProcessor、DubboConfigApplicationListener等。

@DubboService注解标识的目标类主要是被后置处理器ServiceAnnotationPostProcessor加载到Spring IOC对应的注册表中。

ServiceAnnotationPostProcessor是BeanDefinitionRegistryPostProcessor类型的后置处理器,不仅实现接口BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法,同时实现了接口BeanFactoryPostProcessor的方法postProcessBeanFactory。
当然在DubboAutoConfiguration类中也初始化ServiceAnnotationPostProcessor,但是此处优先级明显低于BeanDefinitionRegistryPostProcessor。

public class ServiceAnnotationPostProcessor implements BeanDefinitionRegistryPostProcessor{
	List<Class<? extends Annotation>> serviceAnnotationTypes = 	
			// 如果普通类被以下三种注解标识都会被dubbo识别为目标类	
			asList(DubboService.class,Service.class,com.alibaba.dubbo.config.annotation.Service.class
    );
	
	private void scanServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {

	    DubboClassPathBeanDefinitionScanner scanner =
	            new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
	
	    BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
	    scanner.setBeanNameGenerator(beanNameGenerator);
	    for (Class<? extends Annotation> annotationType : serviceAnnotationTypes) {
	        scanner.addIncludeFilter(new AnnotationTypeFilter(annotationType));
	    }
	
	    ScanExcludeFilter scanExcludeFilter = new ScanExcludeFilter();
	    scanner.addExcludeFilter(scanExcludeFilter);
	
	    for (String packageToScan : packagesToScan) {
	        ...
	        // 扫描指定路径下的bean
	        scanner.scan(packageToScan);
	        // 得到集合serviceAnnotationTypes元素注解存在的bean
	        Set<BeanDefinitionHolder> beanDefinitionHolders =
	                findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
	        if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {
	            for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
	            	// 解析@DubboService注解的相关属性,控制影响候选类实例化&初始化的流程
	            	//其中候选类的BeanDefinition中class属性为ServiceBean
	                processScannedBeanDefinition(beanDefinitionHolder, registry, scanner);
	                servicePackagesHolder.addScannedClass(beanDefinitionHolder.getBeanDefinition().getBeanClassName());
	            }
	        } 
	        servicePackagesHolder.addScannedPackage(packageToScan);
	    }
	}
}

DubboClassPathBeanDefinitionScanner扫描指定路径下dubbo涉及的bean。如果@EnableDubbo没有显式指定路径,则默认为启动类所在的包路径,此时意味着Dubbo需要遍历应用中所有类是否为候选类,所以显示指定路径可以提升应用启动速率。

Dubbo中目标接口的多个实现类必须通过version版本号区别。针对每个候选类在Spring IOC中存在两种形式,一种是beanName + Class属性(候选类真实Class类型),另一种是 beanName:version + Class属性为ServiceBean。最后一种形式也是后置处理器ServiceAnnotationPostProcessor类涉及的核心功能。

@DubboReference注解是被后置处理器ReferenceAnnotationBeanPostProcessor解析的。当前后置处理器比较关注的注解如下所示分别为Reference、DubboReference。

public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBeanPostProcessor{
	public ReferenceAnnotationBeanPostProcessor() {
        super(DubboReference.class, Reference.class, com.alibaba.dubbo.config.annotation.Reference.class);
    }
}

简单概括为:遍历Spring IOC注册表中全部的类,如果类的字段或者方法上存在Reference|DubboReference等相关注解则生成该接口对应的BeanDefinition。但是BeanDefinition中Class属性为ReferenceBean类信息。

ReferenceBean是FactoryBean类型的类,核心就是为了创建候选类的代理类,无论是JDK动态代理还是Cglib动态代理。但是在Dubbo中代理过程是参考Spring AOP 代理流程来完成的。

3.初始化Dubbo应用上下文DubboSpringInitContext

DubboConfigConfigurationRegistrar类型的后置处理器触发DubboSpringInitContext初始化,涉及核心流程:

  1. 提供扩展点DubboSpringInitCustomizer。
  2. 创建实例FrameworkModel、实例ApplicationModel、实例ModuleModel。
  3. 将实例DubboSpringInitContext、实例ApplicationModel 以及 实例ModuleModel添加到IOC容器中,由Spring集中管理bean。
public class DubboConfigConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    	DubboSpringInitializer.initialize(registry);
    }
}

上述流程中Dubbo提供了一个扩展点:DubboSpringInitCustomizer。使用者可以提前修改DubboSpringInitContext内部相关属性。

ZookeeperRegistry

你可能感兴趣的:(dubbo)