Afred's Blog / 编程之旅

Dubbo Provider 本地暴露流程

2019-01-03 posted in [编程之旅]

类图

以Spring XML + Dubbo为例,在Provider端启动时,会初始化ServiceBean:

图片无法显示

本章主要关注ServiceBean的初始化,ServiceBean实现了InitializingBean和DisposableBean接口,了解Spring机制的都知道这两个类的作用,顺藤摸瓜即可理解Dubbo 服务暴露的入口了。

图片无法显示

JavassistProxyFactory生成Invoker

在分析之前流程之前,借用Dubbo官网的描述说明Invoker在整个调用流程中的重要性:

从Dubbo 官网可知,由于 Invoker 是 Dubbo 领域模型中非常重要的一个概念,很多设计思路都是向它靠拢。这就使得 Invoker 渗透在整个实现代码里,对于刚开始接触 Dubbo 的人,确实容易给搞混了。 下面我们用一个精简的图来说明最重要的两种 Invoker:服务提供 Invoker 和服务消费 Invoker:

图片无法显示

略去ServiceBean的初始化流程,分析到Provider端Invoder的生成流程。在分析Dubbo的流程时,一般都是调试Dubbo的测试代码,逐步debug,在这里也是一样,启动com.alibaba.dubbo.examples.version.VersionProvider,跟踪到生成Invoker代码如下:

    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName,
                                      Class<?>[] parameterTypes,
                                      Object[] arguments) throws Throwable {
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }

根据上述代码,会先根据代理对象生成装饰类,具体代码如下:


public static Wrapper getWrapper(Class<?> c) {
        while (ClassGenerator.isDynamicClass(c)) // can not wrapper on dynamic class.
            c = c.getSuperclass();

        if (c == Object.class)
            return OBJECT_WRAPPER;

        //  优先从缓存中取对应的Wrapper对象,如果不存在,动态生成Wrapper class,并创建Wrapper对象
        Wrapper ret = WRAPPER_MAP.get(c);
        if (ret == null) {
            ret = makeWrapper(c);
            WRAPPER_MAP.put(c, ret);
        }
        return ret;
    }

创建Wrapper类的过程,由Dubbo封装javassist完成,具体流程这里不分析。因为Dubbo自动生成的类太多,为了方便理解,所以在调试时使用ClassDump把Wrapper类打印出来如下,方便分析流程:

package com.alibaba.dubbo.common.bytecode;

import com.alibaba.dubbo.examples.version.impl.VersionServiceImpl;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;

public class Wrapper1
  extends Wrapper
  implements ClassGenerator.DC
{
  public static String[] pns;
  public static Map pts;
  public static String[] mns;
  public static String[] dmns;
  public static Class[] mts0;
  
  public String[] getPropertyNames()
  {
    return pns;
  }
  
  public String[] getMethodNames()
  {
    return mns;
  }
  
  public boolean hasProperty(String paramString)
  {
    return pts.containsKey(paramString);
  }
  
  public Class getPropertyType(String paramString)
  {
    return (Class)pts.get(paramString);
  }
  
  public String[] getDeclaredMethodNames()
  {
    return dmns;
  }
  
  public Object invokeMethod(Object paramObject, String paramString, Class[] paramArrayOfClass, Object[] paramArrayOfObject)
    throws InvocationTargetException
  {
    VersionServiceImpl localVersionServiceImpl;
    try
    {
      localVersionServiceImpl = (VersionServiceImpl)paramObject;
    }
    catch (Throwable localThrowable1)
    {
      throw new IllegalArgumentException(localThrowable1);
    }
    try
    {
      if ((!"sayHello".equals(paramString)) || (paramArrayOfClass.length == 1)) {
        return localVersionServiceImpl.sayHello((String)paramArrayOfObject[0]);
      }
    }
    catch (Throwable localThrowable2)
    {
      throw new InvocationTargetException(localThrowable2);
    }
    throw new NoSuchMethodException("Not found method \"" + paramString + "\" in class com.alibaba.dubbo.examples.version.impl.VersionServiceImpl.");
  }
  
  public Object getPropertyValue(Object paramObject, String paramString)
  {
    try
    {
      VersionServiceImpl localVersionServiceImpl = (VersionServiceImpl)paramObject;
    }
    catch (Throwable localThrowable)
    {
      throw new IllegalArgumentException(localThrowable);
    }
    throw new NoSuchPropertyException("Not found property \"" + paramString + "\" filed or setter method in class com.alibaba.dubbo.examples.version.impl.VersionServiceImpl.");
  }
  
  public void setPropertyValue(Object paramObject1, String paramString, Object paramObject2)
  {
    try
    {
      VersionServiceImpl localVersionServiceImpl = (VersionServiceImpl)paramObject1;
    }
    catch (Throwable localThrowable)
    {
      throw new IllegalArgumentException(localThrowable);
    }
    throw new NoSuchPropertyException("Not found property \"" + paramString + "\" filed or setter method in class com.alibaba.dubbo.examples.version.impl.VersionServiceImpl.");
  }
}

返回AbstractProxyInvoker的一个实现类对象,跟踪该对象的invoke方法:


	@Override
    public Result invoke(Invocation invocation) throws RpcException {
        try {
            return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()));
        } catch (InvocationTargetException e) {
            return new RpcResult(e.getTargetException());
        } catch (Throwable e) {
            throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }

最终会调用匿名类的doInvoke方法,而doInvoke则会调用Wrapper#invokeMethod方法,该方法最终调用代理对象的指定方法,这就是Invoker之后实际的调用流程。

疑问:Invoker之前的调用流程是什么样子的?

本地暴露流程

分析完Invoker对象的生成和执行流程,回到本地暴露方法exportLocal方法:

    private void exportLocal(URL url) {
    // 如果当前协议不是本地暴露,则进行本地暴露
        if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
            URL local = URL.valueOf(url.toFullString())
                    // 改写Protocol,改写前值为registry,改写后为injvm
                    .setProtocol(Constants.LOCAL_PROTOCOL)
                    .setHost(LOCALHOST)
                    .setPort(0);
            
            // 暂时没理解这段代码的意义
            ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));
            
            // 核心代码
            // proxyFactory.getInvoker(ref, (Class) interfaceClass, local) 生成Invoker对象的url protocol为injvm
            // exporter 为ListenerExporterWrapper的对象
            Exporter<?> exporter = protocol.export(
                    proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
            // 添加到列表中
            exporters.add(exporter);
            logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
        }
    }

上述核心代码有两个关键变量protocolproxyFactory,在ServiceConfig类中引用的proxyFactory代码如下:

private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

根据Dubbo SPI机制,proxyFactory实际上是StubProxyFactoryWrapper对象,默认情况下是对JavassistProxyFactory对象的包装,实现如下:

	@Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException {
    // proxyFactory 为 JavassistProxyFactory 对象
        return proxyFactory.getInvoker(proxy, type, url);
    }

同样,Protocol也默认有几个Wrapper类,分别是:ProtocolListenerWrapperProtocolFilterWrapperQosProtocolWrapper

图片无法显示

JavassistProxyFactory#getInvoker流程上一章节已经分析,不再复述,综上所述,Dubbo Provider 本地暴露的流程如下:

  1. ServiceBean#afterpropertiesSet初始化,调用export方法;
  2. export方法遍历所有的protocols,调用doExportUrlsFor1Protocol方法;
  3. doExportUrlsFor1Protocol会判断scope的值;
  4. 如果scope不是remote,并且protocol的值不为injvm,则会暴露本地服务;
  5. 本地服务的protocol为invjm,host为127.0.0.1,port为0
  6. 根据Dubbo SPI,会分别调用StubProxyFactoryWrapperJavassistProxyFactorygetInvoker方法,构建Invoker对象——一个AbstractProxyInvoker匿名实现类的对象,Invoker对象封装了对实际目标对象的调用;
  7. protocol根据invoker对象生成Exporter对象;
  8. 将生成的Exporter对象添加到exporters中,流程结束。

Dubbo Consumer端注册中心流程

2017-09-17 posted in [编程之旅]

Dubbo consumer 端启动流程

2017-08-26 posted in [编程之旅]

Kong 初体验

2017-02-26 posted in [编程之旅]

ReentrantLock源码分析

2017-02-19 posted in [编程之旅]

CountDownLatch源码分析

2017-01-15 posted in [编程之旅]

BeanPostProcessor 分析

2016-12-21 posted in [编程之旅]

PropertySourcesPlaceholderConfigurer源码分析

2016-12-20 posted in [编程之旅]

DelayQueue 源码分析

2016-09-03 posted in [编程之旅]

ConcurrentHashMap 源码分析

2016-05-21 posted in [编程之旅]

LinkedHashMap 源码分析

2016-04-24 posted in [编程之旅]

JAVA GC学习笔记

2015-10-17 posted in [编程之旅]

JAVA线程池源码分析

2015-06-20 posted in [编程之旅]

sleep和wait的区别

2015-05-24 posted in [编程之旅]

记一次上线的NoClassDefFoundError异常

2015-04-25 posted in [编程之旅]

ActiveMQ 使用搜集

2014-10-18 posted in [编程之旅]

用Jmeter 测试netty服务器接口

2014-07-26 posted in [编程之旅]

用JMeter进行压力测试

2014-02-09 posted in [编程之旅]

ibatis存储过程返回结果集

2014-01-04 posted in [编程之旅]

BufferedWriter和BufferedOutputStream性能比较

2014-01-04 posted in [编程之旅]

记消息中心改版

2013-09-19 posted in [编程之旅]