dubbo服务导出和引入

1,184 阅读4分钟

Dubbo服务的导出和引入,这部分在Dubbo官方文档的源码导读有很详细的讲解。这边分别给出连接。

Dubbo源码导读-服务导出

Dubbo源码导读-服务引入

导出的入口

每个需要导出的服务都要配置这么一条xml元素。信息包括interfaceclass(具体实现类)

<dubbo:service interface="com.test.service.DemoService" class="com.test.service.impl.DemoServiceImpl" />

每一条dubbo:service的配置在初始化的时候,都会实例化一个ServiceBean对象。可以认为ServiceBean是多例的。利用Spring的观察者监听机制,在Spring加载过程中会调用onApplicationEvent方法。后续在官方文档中都有解说。

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
    if (isDelay() && !isExported() && !isUnexported()) {
        if (logger.isInfoEnabled()) {
            logger.info("The service ready on spring started. service: " + getInterface());
        }
        export();// 做导出动作
    }
}

引入的入口

每个需要导出的服务都要配置这么一条xml元素。信息包括interfaceclass(具体实现类)

<dubbo:reference id="demoService" interface="com.test.service.DemoService"/>

每一条dubbo:reference的配置在初始化的时候,都会实例化一个ReferenceBean对象。可以认为ReferenceBean是多例的。当系统中对该对象做getBean或者@Autowired时,会调用ReferenceBean.getObject方法获取代理对象。后续在官方文档中都有解说。

@Override
public Object getObject() throws Exception {
  return get();
}

服务调用过程

在看源码的过程中会发现各种InvokerProxyFilter等等,这些抽象都是Dubbo的核型,为了把整个过程搞的比较清晰,我画了个图,仔细来过一下。

举个例子,Provider提供了服务接口com.xxx.XxxService。并且有实现类com.xxx.XxxServiceImpl。其中服务方法名声明为 int demoService(int a, int b);

从上往下说

1.Consumer 的业务逻辑中有对com.xxx.XxxService的调用

2.首先会调用到ProxyConsumer在初始化时为com.xxx.XxxService生成的动态代理)

3.调用InvokerInvocationHandler.invokeinvoker.invoke被执行过程中首先会执行所有被包装好的filter过滤器链。(关于filter的包装不理解的可以看下文章dubbo的filter

4.过滤器链执行完后会执行真实的invoker.invokeConsumer端的invoke执行),这个方法会组织参数RpcInvocation并且编码 + 序列化之后通过RPC协议发送到Provider

5.Consumer发送完请求之后会执行同步等待方法ResponseFuture.get来等待返回的Response信息。

6.Provider端接收到请求之后,通过各种handler调用到Exporter(服务导出时用来包装Invoker的)

7.Exporter自然会拿到它所包装好的Invoker,并且调用invoker.invokeProvider端的invoke执行)

8.同样在调用真实Invoker之前会先调用事先包装好的所有filter过滤器链。

9.过滤器链执行完后会执行真实的invoker.invokeProvider端的invoke执行)这个方法会调用到服务导出时准备好的Proxy

10.这边的Proxy有两种:

​ ① 直接使用Java的反射完成com.xxx.XxxServiceImpl服务调用

​ ② 使用Javassist在服务导出时,会动态生成对com.xxx.XxxServiceImpl方法调用的代码,并且构造出Proxy(这个算是静态代理),在执行invoker.invoke时,就直接调用方法。

Dubbo默认使用Javassist在服务导出时,生成直接调用方法的静态代理。我认为这个地方这么处理最主要的目的是性能。毕竟每次都反射,不太爽哦。

11.通过上面第10条描述的,对com.xxx.XxxServiceImpl用户服务的执行。

12.com.xxx.XxxServiceImpl执行完成后,把Response原路返回。出Provider端的时候还是TCP发送信息到Consumer端。

13.Consumer端的ResponseFuture.get得到结果。

14.Consumer端执行后续业务行为。

通过上述详细流程的描述和解释,再对照官方文档关于服务导出和引入的源码导读。应该可以很好的理解这个过程了。

关于Protocol

看下Protocol的类结构

子类有2大块(其实有3块,有一块包装类我这边先给去掉了)分别是RegistryProtocolAbstractProtocol

RegistryProtocol很特殊,这边实际上用了适配器模式,RegistryProtocol就是适配器。所有服务导出都会首先创建RegistryProtocol实例,然后由RegistryProtocol来做导出过程(包括服务暴露,注册,监听)的适配。这个过程服务引入也是一样的。

看下Protocol的接口行为定义。比较简单,不做太多解释。

public interface Protocol {
  
    int getDefaultPort();

  	// 导出
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
		
  	// 引入
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

    void destroy();
}

总结

关于服务导出和引入,服务调用过程的图解是重点。其他的代码细节可以重点关注

Dubbo源码导读-服务导出

Dubbo源码导读-服务引入