@DubboComponentScan注解
我们在使用Dubbo时,在没有使用阿里的starter时,都会在启动类打上@DubboComponentScan这个注解来将我们被Dubbo的@service注解标注的类,注册到Spring IOC容器中。这个注解很关键,Dubbo服务的发布,以及引用都从此注解开始。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
// 省略注解属性
}
注解里Import了DubboComponentScanRegistrar这个类,从字面上就能大致理解是做什么用的了–扫描Dubbo的组件并将其注册。我们看看这个类具体长什么样并干些什么。
public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
}
DubboComponentScanRegistrar 实现了spring 提供的ImportBeanDefinitionRegistrar接口。这个接口对于扩展spring非常重要,其作用是注册BeanDefinition。熟悉spring的应该会知道,spring在注册bean前,会将扫描的bean组装成BeanDefinition。由于这里不是讲解spring IOC原理,就不过多赘述,后续再另开篇幅写IOC原理。ImportBeanDefinitionRegistrar接口定义如下:
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
// 忽略BeanNameGenerator 转发调用其重载的另一个registerBeanDefinitions方法
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
我们再看看DubboComponentScanRegistrar 是如何实现重写ImportBeanDefinitionRegistrar的接口:
public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 解析@service注解,获取将要扫描的包路径集合
Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
// 后置处理器--将带有@service注解的类解析并注册到Ioc容器中,并export进行服务暴露
registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
// 后置处理器--将带有@Reference注解的类解析并注册到IOC容器中,并初始化ReferenceBeanInvocationHandler,代理执行invoke进行服务调用
registerReferenceAnnotationBeanPostProcessor(registry);
}
// 省略其他代码....
}
由于篇幅限制,本文着重分析Dubbo的发布原理,调用原理将另起篇幅分析。
我们先分析getPackagesToScan方法,看看Dubbo是如何处理获取包路径的。在分析之前,我们先看看这个DubboComponentScan注解有哪些属性
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
String[] value() default {
};
String[] basePackages() default {
};
Class<?>[] basePackageClasses() default {
};
}
接着具体看看getPackagesToScan的操作
private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
// 获取DubboComponentScan注解的注解属性,这里的AnnotationAttributes类在spring源码中也是能寻觅到其踪迹的。
// AnnotationAttributes是spring抽出来的注解属性对象,与其经常配对出现的就是spring封装的工具类AnnotationUtils和AnnotatedElementUtils
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(DubboComponentScan.class.getName()));
// 获取注解的basePackages属性值
String[] basePackages = attributes.getStringArray("basePackages");
// 获取注解的basePackageClasses属性值
Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
// 获取注解的value属性值
String[] value = attributes.getStringArray("value");
// 用value 初始化一个packagesToScan 集合
Set<String> packagesToScan = new LinkedHashSet<String>(Arrays.asList(value));
// 添加basePackages的值
packagesToScan.addAll(Arrays.asList(basePackages));
// 遍历basePackageClasses,将制定的类所处的包路径也添加到packagesToScan集合中
for (Class<?> basePackageClass : basePackageClasses) {
packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
}
// 如果从三处basePackages、basePackageClasses、value获取的包路径都为空
// 返回该注解所标注的当前类所处的包路径作为packagesToScan
// 从此处我们就可以知道为什么@DubboComponentScan为何通常标注在启动类上的原因了
if (packagesToScan.isEmpty()) {
return Collections.singleton(ClassUtils.getPackageName(metadata.getClassName