Dubbo中的SPI机制

目录

Dubbo SPI的Demo

Dubbo扩展点原理的实现

getExtensionLoader(Protocol.class)

getExtension("myprotocol")

createExtension(String name)

getExtensionClasses()

injectExtension

Adaptive自适应扩展点

注解在类上

注解在方法上


Dubbo SPI的Demo

Dubbo 的 SPI 扩展机制,有两个规则:

1、需要在 resource 目录下配置 META-INF/dubbo 或者 META-INF/dubbo/internal 或者 META-INF/services,并基于 SPI 接口去创建一个文件

2、文件名称和接口名称保持一致,文件内容和 SPI 有差异,内容是 KEY 对应 Value

Dubbo中的接口org.apache.dubbo.rpc.Protocol,有多种通讯协议实现,如下图,可以看到org.apache.dubbo.rpc.Protocol文件下的key以及对应的实现类的全路径。

那我们也可以自己写一个实现Protocol接口的实现类,做一个协议扩展。

public class MyProcol implements Protocol {
    @Override
    public int getDefaultPort() {
        return 8888;
    }

    //暴露服务(Dubbo-> ;)
    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        return null;
    }

    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        return null;
    }

    @Override
    public void destroy() {

    }
}

在resources下添加META-INF.dubbo 文件。类名和 Dubbo 提供的协议扩展点接口保持一致

测试

public class MainDemo {

    public static void main(String[] args) {
        Protocol protocol=ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myprotocol");
        System.out.println(protocol.getDefaultPort());
    }
}

可以看到自定义的协议扩展生效了。

Dubbo扩展点原理的实现

从上面可以看出,这个扩展点的实现关键在于这句代码

Protocol protocol=ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myprotocol");

getExtensionLoader(Protocol.class)

ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

先尝试在EXTENSION_LOADERS中获取ExtensionLoader,若获取不到则初始化一个ExtensionLoader,如果当前的 type=ExtensionFactory,type,那么 objectFactory=null, 否则会创建一个自适应扩展点给到 objectFacotry。

getExtension("myprotocol")

这个方法就是根据一个名字来获得一个对应类的实例,根据之前配置的自定义协议(myprotocol=com.dubbo.practice.practiceprovider.MyProcol),name 实际上就是 myprotocol,而返回的实现类应该就是 MyProtocol。

public T getExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {// 如果 name=true ,表示返回一个默认的扩展点
            return getDefaultExtension();
        }
        Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();// 缓存一下 , 如果实例已经加载过了,直接从缓存读取
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name);// 根据名称创建实例
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

这个默认的扩展点其实就是dubbo

createExtension(String name)

根据名称获取实例

private T createExtension(String name) {
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);//EXTENSION_INSTANCES用于缓存实例
            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);
        }
    }

getExtensionClasses()

会查找指定目录/META-INF/dubbo || /META-INF/services 下对应的 type->也就是 Protocol 的 properties 文件,然后扫描这个文件下的所有配置信然后保存到一个 HashMap 中(classes),key=name(对应 protocol 文件中配置的 myprotocol), value=对应配置的类的实例

private Map<String, Class<?>> getExtensionClasses() {
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = loadExtensionClasses();// 这里就是配置文件加载的过程
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }
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;
    }

injectExtension

回到 createExtension(String name)下的injectExtension(T instance)

这个方法是用来实现依赖注入的,如果被加载的实例中,有成员属性本身也是一个扩展点,则会通过 set 方法进行注入。

private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    if (isSetter(method)) {
                        /**
                         * Check {@link DisableInject} to see if we need auto injection for this property
                         */
                        // 可以选择禁用依赖注入
                        if (method.getAnnotation(DisableInject.class) != null) {
                            continue;
                        }
                        // 获得方法的参数,这个参数必须是一个对象类型并且是一个扩展点
                        Class<?> pt = method.getParameterTypes()[0];
                        if (ReflectUtils.isPrimitives(pt)) {
                            continue;
                        }
                        try {
                            String property = getSetterProperty(method);
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                //调用 set 方法进行赋值
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("Failed to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

所谓的扩展点,套路都一样,不管是 springfactorieyLoader,还是 Dubbo 的 spi。实际上,Dubbo 的功能会更加强大,比如自适应扩展点,比如依赖注入

Adaptive自适应扩展点

参考博客:https://www.liangzl.com/get-article-detail-5461.html

注解在类上

Compiler compiler=ExtensionLoader.getExtensionLoader(Compiler.class).getAdaptiveExtension();
        System.out.println(compiler.getClass());

打印出的是class org.apache.dubbo.common.compiler.support.AdaptiveCompiler

设计目的是为了实现Dubbo SPI 时用来固定已知的类和扩展未知类。

注解在接口的实现类上:代表人工实现,实现一个装饰类,它主要用于固定已知类,目前整个系统只有两个,AdaptiveCompiler、AdaptiveExtensionFactory。

注解在方法上

注解在方法上,代表自动生成和编译一个动态的Adaptive类,每个方法都可以根据方法参数动态获取各自的扩展点,主要由于SPI 获取类为不固定的位置的扩展类,所以设计了动态的$Adaptive类。

例如 Protocol的spi类有injvm、dubbo、registry、filter、listener等很多未知扩展类,它设计了Protocol$Adaptive的类,再通过ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(spi类);来提取对象。

Protocol所有扩展实现类上都没有@Adaptive注解,且扩展接口含有两个 @Adaptive 注解的方法:exporter() refer(),所以dubbo会生成一个动态类Protocol$Adaptive,且它实现Protocol接口来扩展这两个Adaptive方法。

String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );从arg0中解析出扩展点名称extName,extName的默认值为@SPI的value。这是adaptive的精髓:每一个方法都可以根据方法参数动态获取各自需要的扩展点。

Protocol extension =(Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).
getExtension(extName);根据extName重新获取指定的Protocol.class扩展点。

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值