天天看点

Dubbo-服务暴露

前言

在开发中,我们定义了接口、实现类,然后在配置文件(XML)中用 <dubbo:service >标签就可以把这个接口暴露出去,本文就介绍一下 Dubbo 的服务暴露过程。

服务暴露流程

先看下官方文档中给出的实体转化图,一个服务暴露必须经过的是从提供服务的实际类 ref -> Invoker -> exporter。大致流程如下:

Dubbo-服务暴露

解析配置文件,发起服务暴露

解析配置文件的自定义标签<dubbo:service > ,在 ServiceBean 初始化的时候会触发 Bean 初始化事件,开始进行服务暴露;

ProxyFactory 动态代理实现类,返回代理对象 invoker

// ServiceConfig ProxyFactory创建代理类,获取invoker
Invoker<?> invoker = proxyFactory.getInvoker() 
  
// JavassistProxyFactory.getInvoker() 动态代理创建代理类
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
  final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
  return new AbstractProxyInvoker<T>(proxy, type, url) {
    protected Object doInvoke(T proxy, String methodName,
                              Class<?>[] parameterTypes,
                              Object[] arguments) throws Throwable {
      return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
    }
  };
}
           

可以看到 JavassistProxyFactory 先动态编译创建了一个代理类 wrapper,wrapper 其实是代理了接口实现;最后返回的 AbstractProxyInvoker 又是对 wrapper 的包装。我们看下 wrapper 是怎么样的。

public class Wrapper0 extends Wrapper implements ClassGenerator.DC {
    ...
    public Object invokeMethod(Object object, String string, Class[] arrclass, Object[] arrobject) throws InvocationTargetException {
        DemoService demoService;
        try {
            demoService = (DemoService)object;
        } catch (Throwable throwable) {
            throw new IllegalArgumentException(throwable);
        }
        try {
            if ("sayHello".equals(string) && arrclass.length == 1) {
                return demoService.sayHello((String)arrobject[0]);// 执行实现类的方法
            }
        } catch (Throwable throwable) {
            throw new InvocationTargetException(throwable);
        }
        throw new NoSuchMethodException(new StringBuffer().append("Not found method \"").append(string).append("\" in class com.alibaba.dubbo.demo.DemoService.").toString());
    }
    ...
}
           

根据协议暴露服务,生成 exporter

// ServiceConfig 根据协议暴露服务
Exporter<?> exporter = protocol.export(wrapperInvoker);
           

对于通常的有注册中心的 Dubbo 使用方式,这里暴露服务的 URL 是:

registry://host:prot/com.alibaba.dubbo.registry.RegistryService?protocol=zookeeper&export=dubbo://ip:port/XXX?...

。所以 protocol 的实现是 RegistryProtocol。

在 RegistryProtocol 中会暴露服务并完成注册中心的注册和订阅,所谓的暴露服务其实就是启动 Netty 服务。

暴露服务

  1. 服务暴露首先会根据 URL 中的 exprot 值,选择具体的协议。就以 Dubbo 协议为例;
  2. 在 DubboProtocol 中会创建一个 DubboExporter,exproter 持有之前的代理对象 invoker,接着按照 端口、接口名、版本、分组 作为key,DubboExporter 作为 value,放入一个 Map 中,这是为了服务调用的时候查找对应的 exporter 进而获得服务实现。
  3. 在 DubboProtocol 中会去创建 NettyServer 然后监听请求,并创建一个 exprot 实例返回,这就完成了服务暴露。

注册与订阅

  1. 首先根据 URL 中的 protocol 值,选择具体的注册中心,就以 zookeeper 为例;
  2. 会在 zookeeper/接口名/providers 节点下注册,订阅 configurators 节点。

我们看下最后完成服务暴露之后的实例关系,如下图。

Dubbo-服务暴露

标签解析

这一节对照上面的流程看看这个 Dubbo 服务暴露的入口。通常我们使用 Dubbo 暴露服务会定义一个接口以及接口的实现类,然后在配置文件中配置即可,例如:

<!-- 应用名 -->
<dubbo:application name="demo-provider"/>
<!-- 注册中心 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" check="false"/>
<!-- 暴露协议 -->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- spring bean -->
<bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>
<!-- 声明暴露的服务 -->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>
           

这里有两种类型的标签:Dubbo 的自定义标签和 Spring 自带的 < bean> 标签,spring 的就不说了,我们看看 Dubbo 是怎么解析 < dubbo:service> < dubbo:registry> 等这些标签的。这一步很关键,是 Dubbo 服务暴露最开始的地方。

Spring自定义标签解析

Dubbo 服务暴露的实现依赖 spring ,使用了 spring 自定义标签解析的方式,所以很有必要了解 spring 自定义标签解析的使用方式。我们就以 Dubbo 标签为例。下图中框出的类和文件就是 Dubbo 使用自定义标签的解析类和配置类,此外还有各种标签对应的实体类,如下表。

Dubbo-服务暴露
标签 实体类
application ApplicationConfig
module ModuleConfig
registry RegistryConfig
monitor MonitorConfig
provider ProviderConfig
consumer ConsumerConfig
protocol ProtocolConfig
service ServiceBean,继承 ServiceConfig
reference ReferenceBean
  • 实体类。定义标签具有的属性和一些处理的方法;
  • dubbo.xsd。限定了spring.xml配置文件中可以使用的自定义标签的名称和属性;
  • spring.chemas。定义了名称空间和对应的xsd文件的对应关系,在解析到这个名称空间的时候就会去找对应的xsd文件进行校验标签使用是否正确;
  • spring.handlers。定义了名称空间和对应的名称空间处理类的关系,dubbo 中对应的就是 DubboNamespaceHandler;
  • DubboNamespaceHandler。这个名称空间处理类会注册标签和对应的标签处理类以及标签实体类;
  • DubboBeanDefinitionParser。dubbo 中真正的标签处理类,根据传入的 Class 对象的不同,进行不同的处理。
    Dubbo-服务暴露

生成实现的代理类

无论是远程暴露还是本地暴露,都需要对接口实现创建代理,也就是 invoker 对象,而创建的方式都一样:

proxyFactory.getInvoker(ref, (Class) interfaceClass, url)

ProxyFactory

ProxyFactory 代理工厂顾名思义就是用来创建 invoker 的代理类,Dubbo 有两种代理方式 JDKProxyFactory 和 JavassistProxyFactory,这两种代理工厂都是通过动态代理的方式来创建 invoker 的代理类。

代理模式

Dubbo 服务调用本质调用的是代理类,然后通过代理类再调用到实现类,所以我们有必要对代理模式有深入的了解。当调用者调用一个方法时,会在方法实现之外做一层代理,调用者调用到的是这个代理层,然后代理层再去调用具体方法实现,这就是代理模式。

代理模式分为两种:静态代理和动态代理,无论是静态代理还是动态代理本质都是代理模式,都是对具体方法实现的增强,唯一的差别是静态代理在编译期已经确定,而动态代理在程序运行时会生成代理类。

静态代理

静态代理是在编译期就已经有了代理类,代理类和实现类实现共同的接口,然后在代理类中注入实现类,并在代理的方法中调用实现类的方法,增强功能也是在代理方法中的。

静态代理的实现很简单,但需要通过接口的方式,并且每个不同的增强(代理类)都需要重写接口,当需要增强的功能点很多的时候(例如需要增强 日志、时间统计、访问次数等),就会有实现类很多的问题。

静态代理适合代理类少的情况。

动态代理

动态代理是在程序运行期间生成代理类,动态代理更加灵活。动态代理主要有三种实现方式:

  • JDK。基于反射实现,必须定义接口;
  • CGLIB。自己生成代理类的字节码然后类加载,不定义接口,继承实现类;
  • Javassist。自己生成代理类的字节码然后类加载,不定义接口,继承实现类。

JavassistProxyFactory

JavassistProxyFactory 的代理方式是:new 一个 AbstractProxyInvoker (invoker代理类),并重写了 doInvoke 方法来代理真正实现类 ref 的方法。而这里代理的方式是调用创建的 wrapper 类的 invokeMethod 方法。而 wrapper 本来是没有的,是通过拼接字符串拼成一个类,然后用 javassist 编译这个类之后再创建这个类的实例。这部分之前分析过了就不展开了。

JDKProxyFactory

JDKProxyFactory 的代理方式也是:new 一个 AbstractProxyInvoker (invoker代理类),并重写了 doInvoke 方法来代理真正实现类 ref 的方法。但这里的代码方式更简单,通过反射调用实现类 ref 的方法。

public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
  return new AbstractProxyInvoker<T>(proxy, type, url) {
    @Override
    protected Object doInvoke(T proxy, String methodName, 
                              Class<?>[] parameterTypes, 
                              Object[] arguments) throws Throwable {
      Method method = proxy.getClass().getMethod(methodName, parameterTypes);
      return method.invoke(proxy, arguments);
    }
  };
}