从源码角度一步步窥探Dubbo服务发布原理
@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()));
}
return packagesToScan;
}
看到这里,如果熟悉spring的朋友不知是否会有跟笔者有这么这么一个疑问,value 和 basePackages 两个属性本质都一样,可互相作为别名,注解源码的注释也证明这一点,那为何不用@AliasFor注解来简化这两个属性的解析操作呢?
搞定了要扫描的包路径,我们当然就要在这些包路径来做解析了,具体怎么解析,我们来看一下源码:
private void registerServiceAnnotationBeanPostProcessor(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
// 得到一个将指定类作为RootBeanDefinition,并将其作为BeanDefinitionBuilder构造参数来实例化BeanDefinitionBuilder,跟BeanDefinitionBuilder 比较相似的还有个BeanDefinitionHolder,因此可见spring的抽象设计是做得很好的,拿beanDefinition 不会直接拿,而是通过builder或者holder这些包装类来拿
BeanDefinitionBuilder builder = rootBeanDefinition(ServiceAnnotationBeanPostProcessor.class);
// 添加构造参数
builder.addConstructorArgValue(packagesToScan);
// 设置角色
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// 获得beanDefinition
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
// 将此beanDefinition 注册到IOC中
BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);
}
最重要的一步就是构建ServiceAnnotationBeanPostProcessor后置处理器这个beanDefinition。我们来看看这个后置处理器做了什么。从声明上看,它实现了BeanDefinitionRegistryPostProcessor、EnvironmentAware、ResourceLoaderAware以及BeanClassLoaderAware接口。熟悉spring都知道,这四个接口都是spring留给我们开发人员扩展spring的宝贵途径。
public class ServiceAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,
ResourceLoaderAware, BeanClassLoaderAware {
}
我们可以轻易拿到当前spring上下文的environment、resourceLoader 以及classLoader三个资源,为我们后续深入处理时,提供便捷。这里顺带解释一下这三者。Environment、ClassLoader不难理解,我们系统都会分环境,加载类时一定会用到类加载器来加载对应类。对于ResourceLoader 资源加载器又为何需要呢?在spring实现中,spring在扫描的类要将其转为beanDefinition 前,还有一步操作是将其转为一个个Resource。
// ResourceLoaderAware
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
// ResourceLoaderAware
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
// BeanClassLoaderAware
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
接下来我们最关键的一个接口实现–BeanDefinitionRegistryPostProcessor,重写postProcessBeanDefinitionRegistry方法
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 处理包路径的空格,并解析转换包路径的占位符
Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);
if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
// 注册resolvedPackagesToScan包路径下带有@service的bean到BeanDefinitionRegistry
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扫描器对象
DubboClassPathBeanDefinitionScanner scanner =
new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
// 构建beanNameGenerator.分两步
// 第一步:如果当前是registry是SingletonBeanRegistry类型,则构建spring内建的org.springframework.context.annotation.internalConfigurationBeanNameGenerator
// 第二步:第一步不成立的话,则直接new AnnotationBeanNameGenerator()来构建
BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
// 给dubbo扫描器设置beanname生成器
scanner.setBeanNameGenerator(beanNameGenerator);
// 给dubbo扫描器设置过滤链,告诉扫描器只扫apacha的@Service的
scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
// 这里是修复issue -- 打阿里的@service不生效而加的
scanner.addIncludeFilter(new AnnotationTypeFilter(com.alibaba.dubbo.config.annotation.Service.class));
// 遍历包,逐一扫描
for (String packageToScan : packagesToScan) {
scanner.scan(packageToScan);
// 查找所有@service的beanDefinitionHolders ,不管是否在spring的@ComponentScan指定扫描范围内
Set<BeanDefinitionHolder> beanDefinitionHolders =
findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {
// 第三步:遍历beanDefinitionHolders 并将其注册到registry
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
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 + "]");
}
}
}
}
这段代码里有关键三步,第一步是扫描,第二步是查找,第三步是注册。
我们先来看第一步: DubboClassPathBeanDefinitionScanner继承了ClassPathBeanDefinitionScanner,调用父类ClassPathBeanDefinitionScanner的scan扫描,真正的扫描逻辑在doScan中。作用是扫描,并将其解析为BeanDefinition添加到registry中。由于这块是spring的内容了,不再过多赘述。
第二步:查找所有@service的beanDefinitionHolders ,不管是否在spring的@ComponentScan指定扫描范围内
private Set<BeanDefinitionHolder> findServiceBeanDefinitionHolders(
ClassPathBeanDefinitionScanner scanner, String packageToScan, BeanDefinitionRegistry registry,
BeanNameGenerator beanNameGenerator) {
// 通过dubbo扫描器在对应包路径下获取组件候选者们
Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents(packageToScan);
// 创建一个BeanDefinitionHolder 的集合
Set<BeanDefinitionHolder> beanDefinitionHolders = new LinkedHashSet<>(beanDefinitions.size());
// 遍历
for (BeanDefinition beanDefinition : beanDefinitions) {
// 通过beanname生成器为当前的beanDefinition生成bean的name
String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry);
// 包装成BeanDefinitionHolder
BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder(beanDefinition, beanName);
// 添加到BeanDefinitionHolder 集合中
beanDefinitionHolders.add(beanDefinitionHolder);
}
return beanDefinitionHolders;
}
不知读者是否有跟笔者有这么一个疑问–第一步交由spring的扫描与第二步dubbo自己查找扫描,都是获取到@service的beanDefinition并将其注册到registry。这两部像是重复操作?其实不然,第二步会在第三步的配合下,其beanDefinition将具体为Dubbo内建抽象的SviceBean的beanDefinition
思考:findServiceBeanDefinitionHolders可否用scanner.doScan替代,或者这两者选一?
第三步:registerServiceBean进行注册
private void registerServiceBean(BeanDefinitionHolder beanDefinitionHolder, BeanDefinitionRegistry registry,
DubboClassPathBeanDefinitionScanner scanner) {
// 得到当前beanDefinitionHolder对应的bean的class
// 底层是org.springframework.util.ClassUtils#resolveClassName
Class<?> beanClass = resolveClass(beanDefinitionHolder);
// 获取apacha或者阿里的@service的Annotation对象
Annotation service = findServiceAnnotation(beanClass);
// 获取其注解属性
AnnotationAttributes serviceAnnotationAttributes = getAnnotationAttributes(service, false, false);
// 处理获取Dubbo service 的接口类型 InterfaceClass
// 等下着重分析
Class<?> interfaceClass = resolveServiceInterfaceClass(serviceAnnotationAttributes, beanClass);
// 获取beanname
String annotatedServiceBeanName = beanDefinitionHolder.getBeanName();
//包装成Dubbo抽象的ServiceBean的BeanDefinition
// 等下着重分析
AbstractBeanDefinition serviceBeanDefinition =
buildServiceBeanDefinition(service, serviceAnnotationAttributes, interfaceClass, annotatedServiceBeanName);
// 生成ServiceBean 的beanname
String beanName = generateServiceBeanName(serviceAnnotationAttributes, interfaceClass);
// 检查是否重复候选者
if (scanner.checkCandidate(beanName, serviceBeanDefinition)) {
// 注册
registry.registerBeanDefinition(beanName, serviceBeanDefinition);
// 省略部分代码
}
我们继续深入分析resolveServiceInterfaceClass(serviceAnnotationAttributes, beanClass)
public static Class<?> resolveServiceInterfaceClass(AnnotationAttributes attributes, Class<?> defaultInterfaceClass)
throws IllegalArgumentException {
ClassLoader classLoader = defaultInterfaceClass != null ? defaultInterfaceClass.getClassLoader() : Thread.currentThread().getContextClassLoader();
// 获取注解的interfaceClass的属性值,默认为void.class
Class<?> interfaceClass = getAttribute(attributes, "interfaceClass");
// 如果为默认值
if (void.class.equals(interfaceClass)) {
interfaceClass = null;
// 取注解interfaceName的属性值
String interfaceClassName = getAttribute(attributes, "interfaceName");
// 如果interfaceClassName 有值
if (hasText(interfaceClassName)) {
// 如果interfaceClassName存在并能被classLoader加载
if (ClassUtils.isPresent(interfaceClassName, classLoader)) {
// 解析其interfaceClassName所对应的interfaceClass
interfaceClass = resolveClassName(interfaceClassName, classLoader);
}
}
}
// 如果interfaceClass属性没有指定并且interfaceClassName也没指定或者所指定的类不存在或者不可加载
if (interfaceClass == null && defaultInterfaceClass != null) {
// 获取beanDefinitionHolder对应的bean Class的所实现的所有接口,并取第一个
// 看到这一步有没有突然发现我们的@service是打在实现类上,然后在注册中心看到的接口类,为其接口的权限定名的原因。
// 因此这里有个坑,我们的实现类实现的接口的顺序切记,业务接口先放,再放其他接口
Class<?>[] allInterfaces = getAllInterfacesForClass(defaultInterfaceClass);
if (allInterfaces.length > 0) {
interfaceClass = allInterfaces[0];
}
}
return interfaceClass;
}
从源码就可以清楚看出,interfaceClass的优先级高于interfaceName,所以在指定interface时要注意!
我们再继续分析buildServiceBeanDefinition(service, serviceAnnotationAttributes, interfaceClass, annotatedServiceBeanName)。其作用是将BeanDefinition包装成ServiceBean的BeanDefinition,包含ref、interface、parameters、provider、monitor、application、module等信息。ref属性持有是传入的beanDefinitionHolder对应的beanname所对应的bean引用。
ServiceBean实现了ApplicationListener监听,会在ServiceBean被注册到spring上下文时,触发监听,监听器里的实现就是export暴露
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 {@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();
}
我们来具体看看ServiceBean类定义
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware,
ApplicationEventPublisherAware {
其中实现了监听接口,监听一个spring上下文刷新事件。我们看看监听器实现
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
里面进行export进行服务暴露。我们来看看如何服务暴露,源码跟进去一看究竟。
两步:1、调用父类ServiceConfig的export; 2、发布一个ServiceBeanExportedEvent事件
@Override
public void export() {
super.export();
// Publish ServiceBeanExportedEvent
publishExportEvent();
}
ServiceConfig的export方法的关键就是调用checkAndUpdateSubConfigs、doExport方法。
public synchronized void export() {
// 一些列检查并启动注册中心
checkAndUpdateSubConfigs();
if (!shouldExport()) {
return;
}
// 是否延时暴露
if (shouldDelay()) {
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
// 暴露
doExport();
}
}
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
}
if (exported) {
return;
}
exported = true;
if (StringUtils.isEmpty(path)) {
path = interfaceName;
}
doExportUrls();
}
前面都是做一些校验,真正实现在doExportUrls方法上。这个方法主要做的是遍历RegistryConfig集合registries,这个属性的就是注册中心。得到List registryURLs并配合协议,执行protocoldoExportUrlsFor1Protocol(protocolConfig, registryURLs)完成暴露
由于具体暴露也是相对有点复杂,所以另起篇幅,详细分析export具体实现。我们先暂时有这么个宏观理解。
本文就到此结束了,我们下文见,谢谢
github: honey开源系列组件作者
本文地址:https://blog.csdn.net/China_eboy/article/details/111709788
上一篇: Java中的Valid和Validated的比较内容
下一篇: lamdba表达式实用小实例