我是靠谱客的博主 火星上手机,最近开发中收集的这篇文章主要介绍dubbo源码学习笔记之spi前言dubbo扩展点相关的注解,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

前言

要学习dubbo源码 必须要搞清楚dubbo的spi。它比Java的spi更加灵活。

dubbo扩展点相关的注解

@SPI

@SPI主要标示在接口上,标示这个接口是个扩展点,具体使用那个实现是通过配置去找到具体实现类。这个注解可以接受一个value,这个值和文件(spi获取具体现实类全路径名的文件)里面的key 对应。dubbo的这个文件格式是key:实现类。例如

filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=org.apache.dubbo.rpc.support.MockProtocol

dubbo 会从3个路径是读取这个文件,它的代码如下

 private static final String SERVICES_DIRECTORY = "META-INF/services/";

    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";

    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

 

private Map<String, Class<?>> loadExtensionClasses() {
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
    }

总结就是/META-INF/services  /META-INF/dubbo  /META-INF/dubbo/internal 3个路径

@Adaptive

当这个注解标注在方法上的时候,这个方法的入参一定是带有URL这个在dubbo定义的一个对象的。在扫描到这个扩展点的这个方法的注解的时候,会生成一个代理类,在这个方法里,会获取URL,他是和个接口名字一样的一个属性,(例如protocol接口的export方法有@adaptive注解它生成的代理类就会从url里面获取一个protocol属性的值)这个属性的值是来获取对应的扩展实现类的。如果异常就用获取默认的扩展实现类,它生成这个类的方式就是通过string字符串去拼接这个代理类然后在用编译器编译为class。它生成的类如下:例子是 Protocol的扩展Adaptive 

package org.apache.dubbo.rpc;

import org.apache.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {

    public void destroy() {
        throw new UnsupportedOperationException(
                "The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    public int getDefaultPort() {
        throw new UnsupportedOperationException(
                "The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    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.Invokerargument == 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(
                    "Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
        }
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader
                .getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }

    public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
        if (arg1 == null) {
            throw new IllegalArgumentException("url == null");
        }
        org.apache.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null) {
            throw new IllegalStateException(
                    "Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
        }
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader
                .getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
}

Protocol的源码是 上下对照就能清楚知道生成的代理类是怎么来的了

@SPI("dubbo")
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();

}

@Adaptive标示在类上的时候比较特殊,且只能有一个实现类被它标示在类上。标示在类上的时候,这个实现类像是其他实现类的一个代理一样,它一般会遍历其他所有的实现类来完成功能,比如AdaptiveExtensionFactory

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}

这个类的作用是为扩展点实例化的时候注入对象属性的,它遍历的主要是SpiExtensionFactory和SpringExtensionFactory 来获取它保存的一个实例。SpringExtensionFactory就是拿到Spring管理的那些bean。

@Activate

主要是用在有多个扩展点的实现,需要跟据不同条件被激活的场景,主要用在filter上例如暴露服务ProtocolFilterWrapper的代码

    private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        Invoker<T> last = invoker;
//获取active扩展点
        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>() {

                    @Override
                    public Class<T> getInterface() {
                        return invoker.getInterface();
                    }

                    @Override
                    public URL getUrl() {
                        return invoker.getUrl();
                    }

                    @Override
                    public boolean isAvailable() {
                        return invoker.isAvailable();
                    }

                    @Override
                    public Result invoke(Invocation invocation) throws RpcException {
                        Result asyncResult;
                        try {
                            asyncResult = filter.invoke(next, invocation);
                        } catch (Exception e) {
                            // onError callback
                            if (filter instanceof ListenableFilter) {
                                Filter.Listener listener = ((ListenableFilter) filter).listener();
                                if (listener != null) {
                                    listener.onError(e, invoker, invocation);
                                }
                            }
                            throw e;
                        }
                        return asyncResult;
                    }

                    @Override
                    public void destroy() {
                        invoker.destroy();
                    }

                    @Override
                    public String toString() {
                        return invoker.toString();
                    }
                };
            }
        }

        return new CallbackRegistrationInvoker<>(last, filters);
    }

它把filter一层一层的联在一起

包装类

还有一类扩展点它是包装类。在扫到的时候会根据构造函数里是否是该接口类型来判断它是否是一个包装类,如果它是包装类,实例化的时候会一层一层的包装,以真正有暴露协议功能的dubboProtocol实例化的时候为列。

它会实例化ProtocolFilterWrapper,然后把dubboProtocol当作构造的参数传入,然后ProtocolListenerWrapper又会把ProtocolFilterWrapper当作构造的参数传入,最后拿到的一个实例是ProtocolListenerWrapper的实例了。

调用这个实例的export的方法的时候是会经过这些个包装类的处理在调用正真能export的dubboProtocol的。

最后的结构就是ProtocolListenerWrapper持有ProtocolFilterWrapper的对象,ProtocolFilterWrapper又持有dubboProtocol 的对象,ProtocolListenerWrapper的export会调用ProtocolFilterWrapper的export,ProtocolFilterWrapper的export又会调用dubboProtocol 的export。完成了一些过滤等功能

它对应的代码在

 private T createExtension(String name) {
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
//循环注入包装类
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

扩展实现在暴露一个服务中的用处

真正通过dubbo暴露一个服务的时候,是serviceConfig 通过

ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

这个方法去获取一个protocol的 ,一般url是registry开头的,于是会得到一个registryProtocol,这个实现类会去把服务的信息放到注册中心,放完了在用它自己持有的一个protocol对象去export,这个protocol 对象就是 因为registryProtocol 在实例化的时候他有setProtocol方法所以会通过extensionFactory 来获取实例来进行象注入(参见上面多AptiveExtensionFactory说明)。这个对象又会通过spiExtionFactory获取,得到的就是一个包装对象如上面介绍包装类例子所说的,经过包装类的一些过滤最后是用dubboProtocol打开一个netty端口就可以接受请求了。

上面的registryProtocol 具体选择注册中心又是通过配置拿的注册中心扩展点。

最后

以上就是火星上手机为你收集整理的dubbo源码学习笔记之spi前言dubbo扩展点相关的注解的全部内容,希望文章能够帮你解决dubbo源码学习笔记之spi前言dubbo扩展点相关的注解所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(60)

评论列表共有 0 条评论

立即
投稿
返回
顶部