【Dubbo源码浏览系列】效劳袒露之当地袒露_玖富


玖富娱乐是一家为代理招商,直属主管信息发布为主的资讯网站,同时也兼顾玖富娱乐代理注册登录地址。

在上一篇文章中我们引见 Dubbo 自界说标签剖析相干内容,个中我们自界说的 XML 标签 <dubbo:service /> 会被剖析为 ServiceBean 工具(传送门:Dubbo XML 设置装备摆设加载)。本日我们报告的内容和 ServiceBean 密切相干!
仔细的读者在阅读 ServiceBean 类时会发明 onApplicationEvent() 要领和 afterPropertiesSet() 要领挪用了一个配合的要领 export()。直觉通知我们这个要领应当和效劳的袒露有关,我们接下来就
从 export() 要领入手剖析。

export()要领挪用机遇

为了解答 export() 挪用机遇题目,我们须要存眷 ServiceBean 类中的三个要领

  1. setApplicationContext(ApplicationContext applicationContext)
    ServiceBean 完成了 ApplicationContextAware 接口,在 ServiceBean 初始化后,会挪用 setApplicationContext 注入 Spring 上下文;
  2. afterPropertiesSet()
    注入 ApplicationConfig、registries、protocols 等属性;
  3. onApplicationEvent(ContextRefreshedEvent event)
    这里接收的 event 事宜范例为 ContextRefreshedEvent。当 applicationContext 被初始化或许革新时,会挪用该要领。
    这三个要领在 Spring 生命周期中被挪用的递次大抵以下图所示
    setApplicationContext()——> afterPropertiesSet() ——> onApplicationEvent()
    我们连系代码继承看
public void setApplicationContext(ApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
    SpringExtensionFactory.addApplicationContext(applicationContext);
    supportedApplicationListener = addApplicationListener(applicationContext, this);
}

public void onApplicationEvent(ContextRefreshedEvent event) {
    if (!isExported() && !isUnexported()) {
        if (logger.isInfoEnabled()) {
            logger.info("The service ready on spring started. service: "   getInterface());
        }
        export();
    }
}

public void afterPropertiesSet() throws Exception {
    // 省略...
    if (!supportedApplicationListener) {
        export();
    }
}

代码实行逻辑大抵以下:

  1. 起首实行 setApplicationContext() 要领,注入上下文。这里的 supportedApplicationListener 用于推断 Spring 是不是支撑 Spring 监听机制。
  2. 实行 afterPropertiesSet() 要领。若是 supportedApplicationListener 值为 false,挪用 export() 要领。
  3. 实行 onApplicationEvent() 要领。若是没有实行过 export() 和 unexport() 要领,挪用 export() 要领。
    经由过程上面简朴的剖析我们能够看到 export() 要领只会在 onApplicationEvent() 和 export() 要领中挪用一次。

export() 要领剖析

public synchronized void export() {
    if (provider != null) {
        if (export == null) {
            export = provider.getExport();
        }
        if (delay == null) {
            delay = provider.getDelay();
        }
    }
    if (export != null && !export) {
        return;
    }

    if (delay != null && delay > 0) {
        delayExportExecutor.schedule(new Runnable() {
            @Override
            public void run() {
                doExport();
            }
        }, delay, TimeUnit.MILLISECONDS);
    } else {
        doExport();
    }
}

export()要领比较简朴。注重这里有个 delay 变量,我们能够运用该变量耽误实行 export() 要领。
继承看 doExport() 要领

protected synchronized void doExport() {
    // 省略...
    doExportUrls();
    ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), ref, interfaceClass);
    ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}

private void doExportUrls() {
    List<URL> registryURLs = loadRegistries(true);
    for (ProtocolConfig protocolConfig : protocols) {
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}

doExport()要领省略了许多 ServiceBean 设置装备摆设校验和初始化代码。人人有兴致能够自行阅览。这里直接划重点!!!剖析 doExportUrls() 要领!!!
先看 loadRegistries() 要领:

loadRegistries()

protected List<URL> loadRegistries(boolean provider) {
    checkRegistry();
    List<URL> registryList = new ArrayList<URL>();
    // registries 在 afterPropertiesSet() 要领中初始化
    if (registries != null && !registries.isEmpty()) {
        for (RegistryConfig config : registries) {
            String address = config.getAddress();
            if (address == null || address.length() == 0) {
                address = Constants.ANYHOST_VALUE;
            }
            String sysaddress = System.getProperty("dubbo.registry.address");
            if (sysaddress != null && sysaddress.length() > 0) {
                address = sysaddress;
            }
            if (address.length() > 0 && !RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
                Map<String, String> map = new HashMap<String, String>();
                // 将 application/config 局部属性整合到 map 中,细致见:
                appendParameters(map, application);
                appendParameters(map, config);
                map.put("path", RegistryService.class.getName());
                map.put("dubbo", Version.getProtocolVersion());
                map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
                if (ConfigUtils.getPid() > 0) {
                    map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
                }
                if (!map.containsKey("protocol")) {
                    if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) {
                        map.put("protocol", "remote");
                    } else {
                        map.put("protocol", "dubbo");
                    }
                }
                // 构建 url ,返回效果相似 zookeeper://192.168.0.100:2181/org.apache.dubbo.registry.RegistryService?
                // application=demo-provider&dubbo=2.0.2&pid=22705&qos.port=22222&timestamp=1549005672530
                List<URL> urls = UrlUtils.parseURLs(address, map);
                for (URL url : urls) {
                    // 将此时 url 的 protocol 生存到 registry 参数中
                    url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
                    // 设置 url protcol 属性为 registry
                    url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
                    if ((provider && url.getParameter(Constants.REGISTER_KEY, true))
                            || (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {
                        registryList.add(url);
                    }
                }
            }
        }
    }
    return registryList;
}

loadRegistries() 用于加载注册中央。归纳综合来讲就是用于剖析我们在设置装备摆设文件中界说的 <dubbo:registry /> 标签。
checkRegistry() 要领用于校验注册中央设置装备摆设校验,内里有一些版本兼容的代码。appendParameters() 要领详见 appendParameters() 小节。

当地袒露

引见完 loadRegistries() 要领,我们接着看 doExportUrlsFor1Protocol()。doExportUrlsFor1Protocol() 要领比较长,这里我们挑出和当地袒露相干的内容举行剖析。

if (!Constants.SCOPE_NONE.equalsIgnoreCase(scope)) {
    // export to local if the config is not remote (export to remote only when config is remote)
    if (!Constants.SCOPE_REMOTE.equalsIgnoreCase(scope)) {
        exportLocal(url);
    }
    if (!Constants.SCOPE_LOCAL.equalsIgnoreCase(scope)) {
        // 长途袒露相干内容,省略...
    }
}
private void exportLocal(URL url) {
    if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
        URL local = URL.valueOf(url.toFullString())
                .setProtocol(Constants.LOCAL_PROTOCOL)
                .setHost(LOCALHOST)
                .setPort(0);
        Exporter<?> exporter = protocol.export(
                proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
        exporters.add(exporter);
        logger.info("Export dubbo service "   interfaceClass.getName()   " to local registry");
    }
}

看到 exportLocal() 要领,意味着我们已将近直达当地效劳袒露的中心了!更使人按捺不住的是!这里又用到了 Dubbo 中的 SPI 机制(详见系列第一篇Dubbo SPI)。让我们看看这里究竟做了甚么?

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

熟习的配方熟习的料,在这里我们猎取了 Protocol 和 ProxyFactory 对应的自适应扩大类。依据要领挪用的嵌套逻辑,先来看 ProxyFactory 自适应扩大类 ProxyFactory$Adaptive 的 getInvoker() 要领。

中心要领 proxyFactory.getInvoker()

public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory {
    public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException {
        if (arg2 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg2;
        String extName = url.getParameter("proxy", "javassist");
        if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url("   url.toString()   ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = null;
        try {
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        }catch(Exception e){
            if (count.incrementAndGet() == 1) {
                logger.warn("Failed to find extension named "   extName   " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.", e);
            }
            extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension("javassist");
        }
        return extension.getInvoker(arg0, arg1, arg2);
    }
}

这里我们现实会去挪用 StubProxyFactoryWrapper 包装类的 getInvoker() 要领,若是不明白能够先看下 【Dubbo源码阅读系列】之 Dubbo SPI 机制。

-玖富娱乐是一家为代理招商,直属主管信息发布为主的资讯网站,同时也兼顾玖富娱乐代理注册登录地址。-
public class StubProxyFactoryWrapper implements ProxyFactory {
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException {
        return proxyFactory.getInvoker(proxy, type, url);
    }
}
public class JavassistProxyFactory extends AbstractProxyFactory {
    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);
            }
        };
    }
}

连系上面的代码我们发明,发明末了挪用的是 JavassistProxyFactory 类的 getInvoker() 要领。个中 wrapper 是动态天生的署理工具。末了返回一个 AbstractProxyInvoker 工具,doInvoke() 要领会挪用 wrapper 署理类的 invokeMethod() 要领,个中 invokeMethod() 要领也许以下所示:

public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws java.lang.reflect.InvocationTargetException {
    org.apache.dubbo.demo.provider.DemoServiceImpl w;
    try {
        w = ((org.apache.dubbo.demo.provider.DemoServiceImpl) $1);
    } catch (Throwable e) {
        throw new IllegalArgumentException(e);
    }
    try {
        if ("sayHello".equals($2) && $3.length == 1) {
            return ($w) w.sayHello((java.lang.String) $4[0]);
        }
    } catch (Throwable e) {
        throw new java.lang.reflect.InvocationTargetException(e);
    }
    throw new org.apache.dubbo.common.bytecode.NoSuchMethodException("Not found method ""   $2   "" in class org.apache.dubbo.demo.provider.DemoServiceImpl.");
}

轻微有一点绕,最少我们已看完了 proxyFactory.getInvoker() 要领了,我们猎取到了一个包装了动态署理类的 AbstractProxyInvoker 工具。接下来继承看 protocol.export() 要领。

中心要领 protocol.export()

public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
    if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
    if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");org.apache.dubbo.common.URL url = arg0.getUrl();
    String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
    if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.Protocol) name from url("   url.toString()   ") use keys([protocol])");
    org.apache.dubbo.rpc.Protocol extension = null;
    try {
        extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
    }catch(Exception e){
        if (count.incrementAndGet() == 1) {
            logger.warn("Failed to find extension named "   extName   " for type org.apache.dubbo.rpc.Protocol, will use default extension dubbo instead.", e);
        }
        extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension("dubbo");
    }
    return extension.export(arg0);
}

因为此时的 url 中 protocol 值为 injvm(url 经由 setProtocol(LOCAL_PROTOCOL) 操纵后 protocol 已更新为 injvm),因而我们这里取得的扩大类现实为包装了 InjvmProtocol 的包装类工具,对 wrapper 类有疑问的能够看下【Dubbo源码阅读系列】之 Dubbo SPI 机制。
这里会涉及到一个要领 buildInvokerChain() 方,道它用于构建一个挪用链。
团体挪用时序简图以下所示:


末了 exportLocal() 要领中猎取到的是一个 InjvmExporter 工具,并将其添加到 ServiceConfig 类的 exporters 鸠合中。

buildInvokerChain()

ProtocolFilterWrapper.java
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
    Invoker<T> last = invoker;
    List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
    if (!filters.isEmpty()) {
        for (int i = filters.size() - 1; i >= 0; i--) {
            final Filter filter = filters.get(i);
            final Invoker<T> next = last;
            last = new Invoker<T>() {
                // 省略 Invoker 构建代码...
                @Override
                public Result invoke(Invocation invocation) throws RpcException {
                    return filter.invoke(next, invocation);
                }
                // 省略 Invoker 构建代码...
            };
        }
    }
    return last;
}

buildInvokerChain() 要领用于构建挪用链,开端阅读下来发明挪用链应当是由 Filter 扩大类组成。那末这些 Filter 扩大类又从何而来呢?这行代码很症结!!!

List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);

关于这段代码我们应当有很强的亲切感,但仔细看又稍稍有所不同。现实上被 @Activate 注解符号的扩大类会被加载到 ExtensionLoader 类的 cachedActivates 鸠合中。
我们在挪用 ExtensionLoader 类的 getActivateExtension() 时,会依据我们传入的 key 和 group 值从 cachedActivates 鸠合中猎取知足以后前提的 filter 工具。
拿到 filters 鸠合后,会用链表的情势拼接 filter 挪用链,举个例子:
假定以后猎取到的 filters 鸠合中生存的 filter 工具为 filter0、filter1、filter2。我们对 filters 鸠合举行倒序遍历。末了取得的 last 其实为新建的 ivk2 工具。若是我们挪用 last 的 invoke 要领,挪用链以下图所示:

End

本文引见了 Export() 要领被挪用的机遇和基础流程。而且花了肯定篇幅对 Dubbo 效劳当地袒露举行了剖析。个中搀杂了很多代码的剖析,能够没有四平八稳吧。照样发起人人本身本身 Debug 一下,许多器械霎时秒懂,有助于源码明白。下一篇文章我们引见 Dubbo 效劳长途袒露。

appendProperties()

protected static void appendProperties(AbstractConfig config) {
    if (config == null) {
        return;
    }
    // getTagName:猎取去除 Bean/Config 末端的小写类名(ApplicationConfig->application)
    String prefix = "dubbo."   getTagName(config.getClass())   ".";
    Method[] methods = config.getClass().getMethods();
    for (Method method : methods) {
        try {
            String name = method.getName();
            // 1、要领长度大于3;2、要领以 set 开首;3、要领修饰符范例为 public;4、形参个数为 1;5、形参范例为基础范例
            if (name.length() > 3 && name.startsWith("set") && Modifier.isPublic(method.getModifiers())
                    && method.getParameterTypes().length == 1 && isPrimitive(method.getParameterTypes()[0])) {
                // camelToSplitName: 举个例子 ApplicationConfig——>application.config
                String property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase()   name.substring(4), ".");

                String value = null;
                if (config.getId() != null && config.getId().length() > 0) {
                    // 拼接属性称号,并实验猎取对应属性
                    String pn = prefix   config.getId()   "."   property;
                    value = System.getProperty(pn);
                    if (!StringUtils.isBlank(value)) {
                        logger.info("Use System Property "   pn   " to config dubbo");
                    }
                }
                if (value == null || value.length() == 0) {
                    // 好比以后 config 为 ApplicationConfig,pn = dubbo.application.xxx
                    String pn = prefix   property;
                    value = System.getProperty(pn);
                    if (!StringUtils.isBlank(value)) {
                        logger.info("Use System Property "   pn   " to config dubbo");
                    }
                }
                if (value == null || value.length() == 0) {
                    Method getter;
                    try {
                        getter = config.getClass().getMethod("get"   name.substring(3));
                    } catch (NoSuchMethodException e) {
                        try {
                            getter = config.getClass().getMethod("is"   name.substring(3));
                        } catch (NoSuchMethodException e2) {
                            getter = null;
                        }
                    }
                    if (getter != null) {
                        if (getter.invoke(config) == null) {
                            // 实验运用 ConfigUtils.getProperty() 要领猎取属性值
                            // 实验从 dubbo.properties.file 文件或 dubbo.properties 文件中读取属性
                            if (config.getId() != null && config.getId().length() > 0) {
                                value = ConfigUtils.getProperty(prefix   config.getId()   "."   property);
                            }
                            if (value == null || value.length() == 0) {
                                value = ConfigUtils.getProperty(prefix   property);
                            }
                            if (value == null || value.length() == 0) {
                                String legacyKey = legacyProperties.get(prefix   property);
                                if (legacyKey != null && legacyKey.length() > 0) {
                                    value = convertLegacyValue(legacyKey, ConfigUtils.getProperty(legacyKey));
                                }
                            }

                        }
                    }
                }
                if (value != null && value.length() > 0) {
                    method.invoke(config, convertPrimitive(method.getParameterTypes()[0], value));
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }
}

appendParameters()

protected static void appendParameters(Map<String, String> parameters, Object config) {
    appendParameters(parameters, config, null);
}
protected static void appendParameters(Map<String, String> parameters, Object config, String prefix) {
    if (config == null) {
        return;
    }
    Method[] methods = config.getClass().getMethods();
    // 遍历 config 类要领鸠合
    for (Method method : methods) {
        try {
            String name = method.getName();
            // 找到知足以下的要领:以set/is 开首,非 getClass;要领修饰符为 public;要领参数个数为 0;返回范例为基础范例
            if ((name.startsWith("get") || name.startsWith("is"))
                    && !"getClass".equals(name)
                    && Modifier.isPublic(method.getModifiers())
                    && method.getParameterTypes().length == 0
                    && isPrimitive(method.getReturnType())) {
                // 猎取 parameter 注解
                Parameter parameter = method.getAnnotation(Parameter.class);
                // @Parameter(excluded = true),直接跳过
                if (method.getReturnType() == Object.class || parameter != null && parameter.excluded()) {
                    continue;
                }
                int i = name.startsWith("get") ? 3 : 2;
                String prop = StringUtils.camelToSplitName(name.substring(i, i   1).toLowerCase()   name.substring(i   1), ".");
                String key;
                if (parameter != null && parameter.key().length() > 0) {
                    key = parameter.key();
                } else {
                    key = prop;
                }
                // 应用反射挪用 config 类中的 get/is 要领
                Object value = method.invoke(config);
                String str = String.valueOf(value).trim();
                if (value != null && str.length() > 0) {
                    // 是不是须要转义,UTF-8
                    if (parameter != null && parameter.escaped()) {
                        str = URL.encode(str);
                    }
                    if (parameter != null && parameter.append()) {
                        String pre = parameters.get(Constants.DEFAULT_KEY   "."   key);
                        if (pre != null && pre.length() > 0) {
                            str = pre   ","   str;
                        }
                        pre = parameters.get(key);
                        if (pre != null && pre.length() > 0) {
                            str = pre   ","   str;
                        }
                    }
                    if (prefix != null && prefix.length() > 0) {
                        key = prefix   "."   key;
                    }
                    // key/value 添加到 parameters 鸠合
                    parameters.put(key, str);
                } else if (parameter != null && parameter.required()) {
                    throw new IllegalStateException(config.getClass().getSimpleName()   "."   key   " == null");
                }
                // 要领名为 getParameters();要领修饰符为 public;要领形参个数为0;返回范例为 Map
            } else if ("getParameters".equals(name)
                    && Modifier.isPublic(method.getModifiers())
                    && method.getParameterTypes().length == 0
                    && method.getReturnType() == Map.class) {
                Map<String, String> map = (Map<String, String>) method.invoke(config, new Object[0]);
                if (map != null && map.size() > 0) {
                    String pre = (prefix != null && prefix.length() > 0 ? prefix   "." : "");
                    for (Map.Entry<String, String> entry : map.entrySet()) {
                        parameters.put(pre   entry.getKey().replace('-', '.'), entry.getValue());
                    }
                }
            }
        } catch (Exception e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }
}

该要领会挪用以后类工具的 isXXX/getXXX 要领(非 getClass 要领;要领修饰符为 public;形参个数为 0;返回范例为基础范例),猎取其返回值组织键值对添加到指定 map 鸠合中;同时也会剖析 getParameters() 返回的效果,组织键值对注入到 map 鸠合中。

本BLOG上原创文章未经自己允许,不得用于商业用途及传统媒体。网络媒体转载请说明出处,不然属于侵权行为。https://juejin.im/post/5c2b7ab46fb9a049d236273b

-玖富娱乐是一家为代理招商,直属主管信息发布为主的资讯网站,同时也兼顾玖富娱乐代理注册登录地址。