Dubbo源码分析

1. 源码构建

dubbo源码仓库:https://github.com/apache/dubbo/
Dubbo 使用 maven 作为构建工具。下载源码后,使用以下命令进行构建:

  1. mvn clean install -Dmaven.test.skip

通过以下命令以构建 Dubbo 的源代码 jar 包

  1. mvn clean source:jar install -Dmaven.test.skip

并且修改样例项目中的 dubbo 依赖为本地仓库的 SANPSHOT 版本,然后使用远程 debug 来调试 dubbo。

注:依赖源码项目时,需要修改maven配置中的<optional>属性,防止阻断依赖的传递,导致demo工程直接依赖此源码项目时无法依赖这些包

02-Dubbo源码分析 - 图1

2. Dubbo 启动过程源码分析

2.1. Spring 框架知识回顾 - BeanDefinition

  • 在Java中,一切皆对象。在JDK中使用java.lang.Class来描述类这个对象。
  • 而在Spring中,bean对象是操作核心。那么Spring也需要一个东西来描述bean这个对象,它就是BeanDefinition

BeanDefinition继承类图

  1. // 1. 定义Bean信息
  2. RootBeanDefinition beanDef = new RootBeanDefinition();
  3. // 2. 设置BeanDefinition相关属性
  4. beanDef.setBeanClass(DemoServiceImpl.class);
  5. beanDef.setBeanClassName(DemoServiceImpl.class.getName());
  6. beanDef.setScope(BeanDefinition.SCOPE_SINGLETON); // 单例
  7. // 3. 设置bean的属性
  8. MutablePropertyValues propertyValues = beanDef.getPropertyValues();
  9. propertyValues.addPropertyValue("type", "自定义");
  10. // 4. 注册bean到spring容器
  11. applicationContext.registerBeanDefinition("demoService", beanDef);
  12. // 5. 测试获取刚注册的bean功能
  13. DemoService demoService = (DemoService) applicationContext.getBean("demoService");
  14. demoService.sayHello("自定义bean注册");

从示例可以看出,最后的spring动作applicationContext.registerBeanDefinition会在IOC容器内创建描述的bean对象。后续Dubbo的所有对象创建,皆以此形式委托给Spring来创建

2.2. Dubbo的配置解析过程

dubbo的配置解析,不论是xml方式的配置,还是注解方式的配置,目标都是把配置的属性值提取出来,变成dubbo的组件bean(先由BeanDefinition描述,然后委托spring生成组件bean)
下表以xml的标签为例,每个标签配置所对应要解析成为的目标组件bean
dubbo自定义标签与处理类关系图

2.2.1. xml配置的解析过程

  1. dubbo自定义了spring标签描述约束文件dubbo.xsd。在dubbo-config-spring模块下的 src/main/resouce/META-INF目录下
  2. 在spring.handlers、spring.schemas中指定标签解析类,将标签引入spring中管理
  3. DubboBeanDefinitionParser继承了spring的BeanDefinitionParser接口,spring会调用parse方法来读取每个标签配置,将属性值装入对应的BeanDefinition定义中,后续spring会根据此BeanDefinition定义生成dubbo的组件bean

xml配置文件解析初始化流程

2.2.2. 注解的解析过程

Dubbo注解解析的目标,与xml一致,都是将配置信息变为BeanDefinition定义,交由spring生成组件bean
spring项目启动的时候,最开始的切入点是@EnableDubbo注解,此注解用于启用dubbo配置。而此注解又引入@EnableDubboConfig@DubboComponentScan两个注解

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Inherited
  4. @Documented
  5. @EnableDubboConfig
  6. @DubboComponentScan
  7. public @interface EnableDubbo {
  8. @AliasFor(annotation = DubboComponentScan.class, attribute = "basePackages")
  9. String[] scanBasePackages() default {};
  10. @AliasFor(annotation = DubboComponentScan.class, attribute = "basePackageClasses")
  11. Class<?>[] scanBasePackageClasses() default {};
  12. @AliasFor(annotation = EnableDubboConfig.class, attribute = "multiple")
  13. boolean multipleConfig() default false;
  14. }
  • @EnableDubboConfig:主要用于处理dubbo中全局性的组件配置,一般在.properties文件中的配置项,如Application/Registry/Protocol/Provider/Consumer
  • @DubboComponentScan:负责扫描项目源代码,处理业务类上的@Reference@Service注解
    2.2.2.1. EnableDubboConfig 注解解析过程
    @EnableDubboConfig注解主要用来解析属性文件中的配置,一般在springboot项目中比较常用,解析过程如下:
  1. 通过DubboConfigConfigurationSelector类,连接到DubboConfigConfiguration类,此处配置了它支持解析的所有注解组件 ``` public class DubboConfigConfiguration { /**
    • Single Dubbo {@link AbstractConfig Config} Bean Binding */ @EnableDubboConfigBindings({
      1. // 这里定义了properties文件对应的属性前缀的值,设置到相应的类中
      2. @EnableDubboConfigBinding(prefix = "dubbo.application", type = ApplicationConfig.class),
      3. @EnableDubboConfigBinding(prefix = "dubbo.module", type = ModuleConfig.class),
      4. @EnableDubboConfigBinding(prefix = "dubbo.registry", type = RegistryConfig.class),
      5. @EnableDubboConfigBinding(prefix = "dubbo.protocol", type = ProtocolConfig.class),
      6. @EnableDubboConfigBinding(prefix = "dubbo.monitor", type = MonitorConfig.class),
      7. @EnableDubboConfigBinding(prefix = "dubbo.provider", type = ProviderConfig.class),
      8. @EnableDubboConfigBinding(prefix = "dubbo.consumer", type = ConsumerConfig.class)
      }) public static class Single { } …… }
  1. 2. 此处为每个dubbo组件绑定了属性文件的前缀值。具体的处理过程在`@EnableDubboConfigBinding`注解中,最终引入到`DubboConfigBindingRegistrar`类来完成组件bean注册
  2. ##### 2.2.2.2. [DubboComponentScan ](/DubboComponentScan ) 注解解析过程
  3. `@DubboComponentScan`注解是用于解析标注在业务Service实现上的注解,主要是暴露业务服务的`@Service`和引入服务的`@Reference`。总入口在`DubboComponentScanRegistrar`类上。可以看到,两个注解是分开处理的
  4. ###### 2.2.2.2.1. @Service注解
  5. - `@Service`注解的处理,最终由`ServiceAnnotationBeanPostProcessor`来处理。dubbo会先调用spring扫描包处理

// ServiceAnnotationBeanPostProcessor类 private void registerServiceBeans(Set packagesToScan, BeanDefinitionRegistry registry) { …… // 标注@Service的类也在Spring容器中实例化 scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));

  1. for (String packageToScan : packagesToScan) {
  2. // Registers @Service Bean first
  3. scanner.scan(packageToScan);
  4. ......

}

  1. ```
  2. // ServiceAnnotationBeanPostProcessor类
  3. private AbstractBeanDefinition buildServiceBeanDefinition(Service service, Class<?> interfaceClass,
  4. String annotatedServiceBeanName) {
  5. BeanDefinitionBuilder builder = rootBeanDefinition(ServiceBean.class);
  6. // 创建封装serviceBean的信息的BeanDefinition对象
  7. AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
  8. MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
  9. // 父类组件信息,已在前一步组装完成。这里将以下属性忽略
  10. String[] ignoreAttributeNames = of("provider", "monitor", "application", "module", "registry", "protocol", "interface");
  11. propertyValues.addPropertyValues(new AnnotationPropertyValuesAdapter(service, environment, ignoreAttributeNames));
  12. // References "ref" property to annotated-@Service Bean
  13. // 将References标签对应的ref值与Service的BeanName关系绑定起来
  14. addPropertyReference(builder, "ref", annotatedServiceBeanName);
  15. // Set interface
  16. builder.addPropertyValue("interface", interfaceClass.getName());
  17. ......
  18. }
  • servicebean有很多父级组件信息引入,这些组件已经在属性文件处理部分完成。
  • servicebean中将ref指向业务bean

    2.2.2.2.2. @Reference注解
  • @Reference注解的处理,最终由ReferenceAnnotationBeanPostProcessor来处理。

  • ReferenceAnnotationBeanPostProcessor类继承于AnnotationInjectedBeanPostProcessor类,实现了MergedBeanDefinitionPostProcessor接口,方法postProcessMergedBeanDefinition()在创建bean实例前会被调用(用来找出bean中含有@Reference注解的Field和Method)

然后在metadata.inject(bean, beanName, pvs)方法中,对字段和方案进行反射绑定。
当Spring完成bean的创建后会调用AbstractAutowireCapableBeanFactory#populateBean方法完成属性的填充

2.3. ServiceBean与ReferenceBean关系图

Bean关系图

2.4. Dubbo的服务暴露过程

源码导读参考官网:http://dubbo.apache.org/zh-cn/docs/source_code_guide/export-service.html

在dubbo的组件中,ServiceBeanReferenceBean是比较特殊的。这两个组件,将完成dubbo服务的远程RPC过程
ServiceBean作为服务端,会在bean创建成功后,发起服务暴露流程。其过程如下:

  • 实现的InitializingBean接口中,spring调用afterPropertiesSet方法,发起服务的暴露动作。 ``` @Override @SuppressWarnings({“unchecked”, “deprecation”}) public void afterPropertiesSet() throws Exception { …… if (!isDelay()) {
    1. // 将dubbo服务暴露到rpc网络
    2. export();
    } }

@Override public void export() { super.export(); // Publish ServiceBeanExportedEvent publishExportEvent(); }

  1. - 父类`ServiceConfig<T>`最终执行此暴露的动作

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) { …… // 包装成服务实例。此ref是ServiceBean的属性,是@Service注解对应的类,目标业务类 Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

  1. // 暴露服务
  2. Exporter<?> exporter = protocol.export(wrapperInvoker);
  3. exporters.add(exporter);
  4. ......

}

  1. 先将本地服务ref包装成Invoker,然后由protocol网络协议将invoker连通到网络上。其核心,即是一旦有protocol网络传输过来的请求,则拉起invoker动作,并将动作传递到ref服务上进行响应。在这个整个过程中,dubbo不希望每一个具体的协议ptotocol去关心目标服务是谁(耦合性太强),于是中间插入了一个invoker概念实体来解耦双方的绑定关系(重点)
  2. #### 2.4.1. Dubbo 框架的 Invoker 机理
  3. - 先明确Dubbo引入Invoker的目标,Invoker的本义是,invoke方法会转嫁到对象到目标ref上,接口定义如下

public interface Invoker extends Node {

  1. /**
  2. * get service interface.
  3. *
  4. * @return service interface.
  5. */
  6. Class<T> getInterface();
  7. /**
  8. * invoke.
  9. *
  10. * @param invocation
  11. * @return result
  12. * @throws RpcException
  13. */
  14. /* 实现此接口方法,最终会将这个执行逻辑传递到目标对象ref上 */
  15. Result invoke(Invocation invocation) throws RpcException;

}

  1. - 协议使用方,希望如此调用Invoker:希望dubbo中的所有协议都按此模式,将网络请求转给invoker对象即可,剩下的protocol协议不要去关心

/ 自定义协议示例 / public class RmiProtocol implements Protocol {

  1. private static final int DEFAULT_PORT = 1099;
  2. @Override
  3. public int getDefaultPort() {
  4. return DEFAULT_PORT;
  5. }
  6. /* 以rmi协议为示例,暴露服务 */
  7. @Override
  8. public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
  9. // 创建spring rmi服务
  10. final RmiServiceExporter rmiServiceExporter = new RmiServiceExporter();
  11. // 设置
  12. rmiServiceExporter.setRegistryPort(invoker.getUrl().getPort());
  13. rmiServiceExporter.setServiceName(invoker.getUrl().getPath());
  14. rmiServiceExporter.setServiceInterface(invoker.getInterface());
  15. /*
  16. * 此时目标服务没有,需要通过invoker调通,使用动态代理
  17. * 这里希望通过invoker对象,制作一个target服务代理来使用
  18. */
  19. T service = (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
  20. new Class[]{invoker.getInterface()},
  21. new InvokerInvocationHandler(invoker));
  22. /*
  23. * 如果能获取目标Service接口,直接设置即可
  24. * protocol协议的目标,都是要将调用转到目标target服务上,但是当前环境并没有这个目标target
  25. */
  26. rmiServiceExporter.setService(service);
  27. try {
  28. rmiServiceExporter.afterPropertiesSet();
  29. } catch (RemoteException e) {
  30. throw new RpcException(e.getMessage(), e);
  31. }
  32. return null;
  33. }
  34. ......

}

  1. - Invoker如何转请求到target目标服务呢?就是交给Invoker的实现类去具体实现,如下例,invoker中本身持有目标target服务

URL url = URL.valueOf(“rmi://127.0.0.1:9001/“ + DemoService.class.getName());

/**

  • Protocol连接服务端invoker
  • 将目标服务调用信息,包装成为invoker实体,暴露到网络上
  • 当网络信息到达,将触发invoker的invoke方法,最终将调用转到目标service上 */ @Test public void invoker2protocol() throws IOException { DemoService service = new DemoServiceImpl();

    // 这里将目标对象(即DemoService实例)引入到Invoker中 Invoker invoker = new SimpleInvoker(service, DemoService.class, url); Protocol protocol = new RmiProtocol(); // 暴露对象。协议对象只需要调用即可 protocol.export(invoker); System.out.println(“Dubbo server 启动”); // 保证服务一直开着 System.in.read(); }

  1. - 后续的调用是通过java反射将方法转给target服务

public class SimpleInvoker implements Invoker { private T target; private Class type; private URL url;

  1. public SimpleInvoker(T service, Class<T> type, URL url) {
  2. this.target = service;
  3. this.type = type;
  4. this.url = url;
  5. }
  6. @Override
  7. public Class<T> getInterface() {
  8. return type;
  9. }
  10. @Override
  11. public Result invoke(Invocation invocation) throws RpcException {
  12. Method method = null;
  13. try {
  14. method = DemoService.class.getMethod(invocation.getMethodName(), invocation.getParameterTypes());
  15. // 通过反射调用目标target
  16. return new RpcResult(method.invoke(target, invocation.getArguments()));
  17. } catch (NoSuchMethodException e) {
  18. e.printStackTrace();
  19. } catch (InvocationTargetException e) {
  20. e.printStackTrace();
  21. } catch (IllegalAccessException e) {
  22. e.printStackTrace();
  23. }
  24. return null;
  25. }
  26. ......

}

  1. #### 2.4.2. 总结服务暴露调用过程
  2. 1. protocol组件收到网络请求到来时,它需要将请求发向target目标服务(如果当前环境中有此服务就好了)。
  3. 1. 因为当前环境中没有target对象,于是它创建了一个target的代理对象proxy,将请求转给了此代理proxy对象,而此proxy对象只会干一件事,将调用转给了invoker对象的invoke方法
  4. 1. invoker对象发现自己内部有target对象,于是它使用java反射,将请求发向了target服务
  5. > **注:如果让protocol中持有target服务,直接转向请求到target要简单得多,但这样一来,每一个ptotocol服务要对接千千万万的业务service接口,耦合性太强。于是,dubbo专门设计了invoker实体来解开两者间的直接耦合(工作中可否借鉴?)**
  6. ### 2.5. Dubbo的服务引入过程
  7. > 源码导读参考官网:[http://dubbo.apache.org/zh-cn/docs/source_code_guide/refer-service.html](ef3a3b7a14d474ef9ea3f76bd967e2ab)
  8. - Dubbo服务的引入过程,是在Referencebean的实例化过程中实现的。当dubbo启动过程中,遇到`@Reference`,即会创建一个Referencebean的实例
  9. - 此实例一样实现了`InitializingBean`接口,在其调用的afterPropertiesSet方法中,会为服务调用方创建一个远程代理对象
  10. - ref是通过interfaceurl信息生成的代理

/ ReferenceConfig/ / 用于获取服务提供的代理对象 / public synchronized T get() { if (destroyed) { throw new IllegalStateException(“Already destroyed!”); } if (ref == null) { init(); } return ref; }

  1. - protocol协议制作了一个invoker对象,你可以通过invoker对象,向protocol协议发送信息(网络传输)。

/ ReferenceConfig#get() —> init() —> T createProxy(Map map) / // 通过网络协议获取一个Invoker对象 invoker = refprotocol.refer(interfaceClass, urls.get(0));

  1. - 使用springrmi协议示例来说明一个这个过程

private T doRefer(Class type, URL url) throws RpcException { final RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean(); if (url.getParameter(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion()).equals(Version.getProtocolVersion())) { rmiProxyFactoryBean.setRemoteInvocationFactory(new RemoteInvocationFactory() { @Override public RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) { return new RmiRemoteInvocation(methodInvocation); } }); } rmiProxyFactoryBean.setServiceUrl(url.toIdentityString()); rmiProxyFactoryBean.setServiceInterface(type); rmiProxyFactoryBean.setCacheStub(true); rmiProxyFactoryBean.setLookupStubOnStartup(true); rmiProxyFactoryBean.setRefreshStubOnConnectFailure(true); rmiProxyFactoryBean.afterPropertiesSet(); return (T) rmiProxyFactoryBean.getObject(); }

  1. - 一个protocol协议建立后,会得到一个object输出对象,输出到网络信息的动作,都由此对象进行。本来,此对象类型已经是业务接口类型,可以直接使用此对象进行通信了。但是,考虑到protocol本身不应该跟具体的业务接口耦合,于是,dubbo再次插入了invoker实体来解耦双方
  2. 1. protocol生成的输出对象object,包装成invoker对象
  3. 1. 在业务操作端,为了方便操作,再做一个代理对象,来转请求到invoker

/**

  • Protocol连接消费端invoker
  • 将要调用的信息,包装成invoker实体,向网络发送
  • 本地调用接口代理时,最终方法被转到invoker的invoke方法上,向网络发送 */ @Test public void protocol2Invoker() { // 创建协议 Protocol protocol = new RmiProtocol();

    // 创建消费端invoker,负责发送协议调用信息 Invoker invoker = protocol.refer(DemoService.class, url);

    // 做一个动态代理,将调用目标指向invoker即可 DemoService service = (DemoService) Proxy

    1. .newProxyInstance(Thread.currentThread().getContextClassLoader(),
    2. new Class[]{invoker.getInterface()},
    3. new InvokerInvocationHandler(invoker)); // 反射逻辑

    String result = service.sayHello(“moon”); System.out.println(result); }

```

注:Dubbo中的invoker概念,作用不仅仅于此,它统一了dubbo中各组件间相互交流的规范,统一都用invoker进行粘合(书同文、车同轴)