Dubbo: 在springboot中的启动过程
版本
- dubbo: 2.7.8
- springboot 2.3.2.RELEASE
SpringBoot中dubbo启动过程
1. @DubboService注解的bean是如何被注册到容器中的
SpringBoot在启动时,通过ConfigurationClassPostProcessor.postProcessBeanFactory完成对依赖jar包中XxAutopConfiguration类的注册,自然DubboAutoConfiguration也会被注册到容器内部。
DubboAutoConfiguration中,定义了一个ServiceClassPostProcessor ,同样会被注册到容器内。
@Configuration
@AutoConfigureAfter({DubboRelaxedBindingAutoConfiguration.class})
@EnableConfigurationProperties({DubboConfigurationProperties.class})
@EnableDubboConfig
public class DubboAutoConfiguration implements ApplicationContextAware, BeanDefinitionRegistryPostProcessor {
public DubboAutoConfiguration() {
}
@ConditionalOnProperty(
prefix = "dubbo.scan.",
name = {"base-packages"}
)
@ConditionalOnBean(
name = {"dubbo-service-class-base-packages"}
)
@Bean
public ServiceClassPostProcessor serviceClassPostProcessor(@Qualifier("dubbo-service-class-base-packages") Set<String> packagesToScan) {
return new ServiceClassPostProcessor(packagesToScan);
}
ServiceClassPostProcessor 实现了BeanDefinitionRegistryPostProcessor接口,同样他也是一个BeanFactoryPostProcessor。
public class ServiceClassPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware, ResourceLoaderAware, BeanClassLoaderAware {
在SpringBoot刷新容器,调用所有BeanFactoryPostProcessors时,对BeanDefinitionRegistryPostProcessor,会去调用其postProcessBeanDefinitionRegistry方法。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 注册监听器,容器启动各个阶段向监听器发送消息
BeanRegistrar.registerInfrastructureBean(registry, "dubboBootstrapApplicationListener", DubboBootstrapApplicationListener.class);
Set<String> resolvedPackagesToScan = this.resolvePackagesToScan(this.packagesToScan);
if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
// 从待扫描的包中获取ServiceBean,然后注册到容器
this.registerServiceBeans(resolvedPackagesToScan, registry);
} else if (this.logger.isWarnEnabled()) {
this.logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
}
}
private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
DubboClassPathBeanDefinitionScanner scanner = new DubboClassPathBeanDefinitionScanner(registry, this.environment, this.resourceLoader);
BeanNameGenerator beanNameGenerator = this.resolveBeanNameGenerator(registry);
scanner.setBeanNameGenerator(beanNameGenerator);
serviceAnnotationTypes.forEach((annotationType) -> {
scanner.addIncludeFilter(new AnnotationTypeFilter(annotationType));
});
Iterator var5 = packagesToScan.iterator();
// 获取迭代器,遍历待扫描的packages,获取ServiceBean信息,封装beanDefinition并注册到IOC容器
while(true) {
while(var5.hasNext()) {
String packageToScan = (String)var5.next();
scanner.scan(new String[]{packageToScan});
Set<BeanDefinitionHolder> beanDefinitionHolders = this.findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {
Iterator var8 = beanDefinitionHolders.iterator();
while(var8.hasNext()) {
BeanDefinitionHolder beanDefinitionHolder = (BeanDefinitionHolder)var8.next();
this.registerServiceBean(beanDefinitionHolder, registry, scanner);
}
if (this.logger.isInfoEnabled()) {
this.logger.info(beanDefinitionHolders.size() + " annotated Dubbo's @Service Components { " + beanDefinitionHolders + " } were scanned under package[" + packageToScan + "]");
}
} else if (this.logger.isWarnEnabled()) {
this.logger.warn("No Spring Bean annotating Dubbo's @Service was found under package[" + packageToScan + "]");
}
}
return;
}
至此,便将ServiceBean注册进了Spring IOC容器。至于对象和代理对象的创建,那是后话了。
2. DubboService是如何对外暴露其url的
我们知道DubboService会被注册到注册中心,最终的结果是:将服务名、服务对外暴露的url等信息通过网络请求发送到注册中心,那么对外暴露的时机是什么时候?它又是如何做到这件事情的?
关于时机,它应该在容器刷新完成之后将所有DubboService对外暴露,那么如何感知到容器刷新呢?SpringBoot中可以注册listener,容器开始启动、启动完成等事件会通知注册进来的listener。
ServiceClassPostProcessor 除了向容器注册ServiceBean之外,还注册了一个监听器:DubboBootstrapApplicationListener,当感知到容器刷新完成和关闭事件时,做出相应处理,在这里关注刷新完成该如何处理。
public void onApplicationContextEvent(ApplicationContextEvent event) {
if (event instanceof ContextRefreshedEvent) {
this.onContextRefreshedEvent((ContextRefreshedEvent)event);
} else if (event instanceof ContextClosedEvent) {
this.onContextClosedEvent((ContextClosedEvent)event);
}
}
private void onContextRefreshedEvent(ContextRefreshedEvent event) {
this.dubboBootstrap.start();
}
启动DubboBootStrap
public DubboBootstrap start() {
// ... 省略诸多初始化代码
// 服务暴露
this.exportServices();
if (!this.isOnlyRegisterProvider() || this.hasExportedServices()) {
this.exportMetadataService();
this.registerServiceInstance();
}
// 服务引用
this.referServices();
if (this.asyncExportingFutures.size() > 0) {
(new Thread(() -> {
try {
this.awaitFinish();
} catch (Exception var2) {
this.logger.warn(NAME + " exportAsync occurred an exception.");
}
this.ready.set(true);
if (this.logger.isInfoEnabled()) {
this.logger.info(NAME + " is ready.");
}
})).start();
} else {
this.ready.set(true);
if (this.logger.isInfoEnabled()) {
this.logger.info(NAME + " is ready.");
}
}
// log ...
}
return this;
}
private void exportServices() {
this.configManager.getServices().forEach((sc) -> {
ServiceConfig serviceConfig = (ServiceConfig)sc;
serviceConfig.setBootstrap(this);
if (this.exportAsync) {
ExecutorService executor = this.executorRepository.getServiceExporterExecutor();
Future<?> future = executor.submit(() -> {
sc.export();
this.exportedServices.add(sc);
});
this.asyncExportingFutures.add(future);
} else {
sc.export();
this.exportedServices.add(sc);
}
});
}
至于Service如何暴露:如下图
export过程参考:https://mergades.blog.csdn.net/article/details/109338146
上面注册的ServiceBean实际上就继承了ServiceConfig。
配合下图右半部分:
DubboBootStrap在exportServices中获取ServiceConfigs。迭代每个ServiceConfig在export的时候创建URL, 通过代理工厂创建invoker,最后由Protocol完成导出。在导出时,分别要向本地和注册中心分别暴露。本地导出时,通过proxyFactory获取invoker,然后由InJVMProtocol完成导出工作,做这件事情的目的是:有时,一个服务既是provider,又是consumer。如果进程内调用该服务,那么它不应该像其他进程一样通过网络请求来进行调用,而是在进程内通信,减少IO带来的开销。远程暴露就比较容易理解,根据invoker的url做encode,然后想注册中心发起请求。
image.png
最后服务暴露到底对外暴露了个啥呢?其实就是invoker的url,当consumer发起服务调用的时候,发起请求,当provider接收到请求之后,将请求携带的信息(接口、方法名、参数数组...)封装为Invocation对象,分派到相应的invoker,通过invoker.invoke(invocation)完成调用。
另外敖丙的文章也还不错,Dubbo系列之服务暴露过程
3. 服务引入及远程调用
从springboot解析@DubboReference开始讲起。
DubboAutoConfiguration被@EnableDubboConfig注解,通过该注解引入了DubboConfigConfigurationRegistrar类。
@EnableDubboConfig
...
@Import({DubboConfigConfigurationRegistrar.class})
DubboConfigConfigurationRegistrar::registerBeanDefinitions(args)
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//...省略一些代码
DubboBeanUtils.registerCommonBeans(registry);
}
public interface DubboBeanUtils {
static void registerCommonBeans(BeanDefinitionRegistry registry) {
BeanRegistrar.registerInfrastructureBean(registry, "referenceAnnotationBeanPostProcessor", ReferenceAnnotationBeanPostProcessor.class);
// ... 省略
}
}
ReferenceAnnotationBeanPostProcessor是一个InstantiationAwareBeanPostProcessorAdapter,在对象实例化后,填充属性中,会调用其postProcessPropertyValues
public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBeanPostProcessor implements ApplicationContextAware {
// ... 省略
public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
InjectionMetadata metadata = this.findInjectionMetadata(beanName, bean.getClass(), pvs);
try {
// 这里的metadata就是AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement对象
metadata.inject(bean, beanName, pvs);
return pvs;
} catch (BeanCreationException var7) {
throw var7;
} catch (Throwable var8) {
throw new BeanCreationException(beanName, "Injection of @" + this.getAnnotationType().getSimpleName() + " dependencies is failed", var8);
}
}
}
public class AnnotatedFieldElement extends InjectedElement {
// ...
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
Class<?> injectedType = this.resolveInjectedType(bean, this.field);
Object injectedObject = AbstractAnnotationBeanPostProcessor.this.getInjectedObject(this.attributes, bean, beanName, injectedType, this);
ReflectionUtils.makeAccessible(this.field);
this.field.set(bean, injectedObject);
}
}
最终到达referenceBean.get()方法,返回代理对象,并注入到目标对象的field。
protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType, InjectedElement injectedElement) throws Exception {
String referencedBeanName = this.buildReferencedBeanName(attributes, injectedType);
String referenceBeanName = this.getReferenceBeanName(attributes, injectedType);
ReferenceBean referenceBean = this.buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType);
boolean localServiceBean = this.isLocalServiceBean(referencedBeanName, referenceBean, attributes);
this.prepareReferenceBean(referencedBeanName, referenceBean, localServiceBean);
this.registerReferenceBean(referencedBeanName, referenceBean, attributes, localServiceBean, injectedType);
this.cacheInjectedReferenceBean(referenceBean, injectedElement);
return referenceBean.get();
}
如果抛开SpringBoot如何注入被@DubboReference注解的Bean,可以说referenceBean.get()就是服务引用的入口。
// ... 省略诸多代码
private transient volatile T ref;
// 线程安全单例
public synchronized T get() {
if (this.destroyed) {
throw new IllegalStateException("The invoker of ReferenceConfig(" + this.url + ") has already destroyed!");
} else {
if (this.ref == null) {
this.init();
}
return this.ref;
}
}
// ...
}
后面的事情就是Dubbo内部要完成的了,大概过程如这样:
ref:https://zhuanlan.zhihu.com/p/224938929