我是靠谱客的博主 贪玩墨镜,最近开发中收集的这篇文章主要介绍Dubbo系列(一) 从SPI开始看ExtensionLoader,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

ExtensionLoader的原理

SPI

java中的SPI

SPI(Service Provider Interface)是java中一种服务发现机制,通过在classpath下的META-INF/services下指定一接口全路径名的文件中指定具体实现类(可配置多个),通过ServiceLoader进行动态可替换的服务方式调用。

测试类

package com.example.spi;

import java.util.ServiceLoader;

public class SpiMain {
    public static void main(String[] args) {
        ServiceLoader<Ilanguage> serviceLoader = ServiceLoader.load(Ilanguage.class);
        for(Ilanguage service:serviceLoader){
            service.call();
        }
    }
}

接口类及实现类

package com.example.spi;

public interface Ilanguage {
    void call();
}

package com.example.spi;

//实现类之一,中文
public class IChineseLanguage implements Ilanguage {
    @Override
    public void call() {
        System.out.println("Chinese call impl");
    }
}
//实现类之一,英文
package com.example.spi;
public class IEnglishLanguage implements Ilanguage {

    @Override
    public void call() {
        System.out.println("English call impl");
    }
}

提供扩展配置文件倍SPI加载
接口名文件中指定实现类全路径名,比如com.example.spi.IChineseLanguage(如有多个,换行添加不同实现类全路径名)
在这里插入图片描述
此时通过serviceLoader会加载到所有配置的服务实现类并调用

Spring中的SPI

spring作为java中流行的应用框架之一,也有对于SPI的变体支持。这个常见于spring boot的自动装配中,应用可以提供自己的自动配置类在resourses/META-INF/spring.factories中,可以被@SpringBootApplication中->@EnableAutoConfiguration->AutoConfigurationImportSelector进行扫描加载
在这里插入图片描述
其中SpringFactoriesLoader中会去当前classpath的指定路径下(META-INF/spring.factories)扫描出所有适合的注解类(EnableAutoConfiguration 指定的类)
在这里插入图片描述
在默认的autoconfigure包下,可以看到spring已经默认提供了很多自己内置的自动装配类,包括Aop配置类等,因此在springboot中不再需要像framework里加上@EnableAspectJAutoProxy注解,手动开启aop切面
在这里插入图片描述

dubbo中的SPI

dubbo中很多核心功能点都是通过SPI的机制进行动态配置和选择的,包括常见的Protocol,Proxy等,允许开发者在自己的应用中实现并选用自己的扩展类。
ExtensionLoader是一个扩展加载类,相当于Java中的ServiceLoader,用于加载发现接口配置的实现类。每一种SPI接口都有一个对应的ExtensionLoader类,其中EXTENSION_LOADERS,EXTENSION_INSTANCES是类静态变量会将所有已经加载过的接口及对应的实例保存起来,避免二次加载。
在这里插入图片描述
其中每个SPI接口的获得,通过调用getExtensionLoader(Class type)入口开始;
1.校验: type必须是SPI注解的接口才满足条件
2.缓存拿或者实例化:从EXTENSION_LOADERS中拿,或者实例化new ExtensionLoader(type)存入
3.获取指定extension的实现类:T getExtension(String name)

具体流程参照下面xmind图解

在这里插入图片描述

如Protocol自适应扩展类,动态根据url中的Protocol参数值选择具体Protocol实现并生成代理java代码如下

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.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);
}
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("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 java.util.List getServers()  {
throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
}

injectExtension(T instance) VS Wrapper

injectExtension要求SPI必须至少有一个对应的Adaptive方法,否则会报错。
因为AdaptiveExtensionFactory构造时通过dubbo加载目录下的ExtensionFactory接口通过type+name获取所有的扩展实现
在这里插入图片描述
在支持的扫描目录下发现dubbo-commonsrcmainresourcesMETA-INFdubbointernalorg.apache.dubbo.common.extension.ExtensionFactory实际上只有一个真正的实现接口即SpiExtensionFactory,因此调用getAdaptiveExtension总是拿到动态适应扩展接口
在这里插入图片描述
所以要求必须是一个带自适应方法的SPI接口,如Protocol。
在这里插入图片描述
如果接口实现类中有相应的setter方法,会尝试去根据方法名解析出来的key作为extension name,通过ExtensionFactory getExtension(Class type, String name)找到自适应代理类,调用方法反射注入。例如,下面定义的MockProtocolWrapper中setMockprotocol方法会解析出property: mockprotocol去ExtensionLoader找该实现类

 String property = getSetterProperty(method);
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
 method.invoke(instance, object);
 }

以Protocol为例,自己加一个扩展类MockProtocolWrapper并放在SPI加载的目录中
在这里插入图片描述
该实现类是一个包装类,也是一个可以injectExtension(调用set方法注入的类)

package org.apache.dubbo.config.mock;

//@Wrapper(matches = "mockprotocol")
public class MockProtocolWrapper implements Protocol {

    private Protocol mockprotocol;
    private Protocol ctrMockprotocol;

    public MockProtocolWrapper(Protocol protocol){
        System.out.println("construct "+ctrMockprotocol+" to "+protocol);
        this.ctrMockprotocol = protocol;
    }
    public void setMockprotocol(Protocol protocol) {
        System.out.println("invoke setMockprotocol method with "+protocol);
        this.mockprotocol = protocol;
    }

执行完后的结果如下,可以知道通过构造方法传入的Protocol是一个可无限包装扩展的包装类,而setter方法找到的是一个运行时动态生成的Adaptive代理对象

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

在这里插入图片描述
包装类本身并不作为一种扩展实现类存在ExtensionLoader中,而是用于包装其他符合条件的同类型接口的实现类。例如上述代码中,你通过包装类配置的name即mockProtocolWrapper无法找到一种实现类

java.lang.IllegalStateException: No such extension org.apache.dubbo.rpc.Protocol by name mockProtocolWrapper, no related exception was found, please check whether related SPI module is missing.

而通过包装类可修饰的其他实现类的配置名如mockprotocol找出来的实际是一个包装对象MockProtocolWrapper(如果没有自定义包装类,一般为ProtocolFilterWrapper),并不是原实现类本身。

最后

以上就是贪玩墨镜为你收集整理的Dubbo系列(一) 从SPI开始看ExtensionLoader的全部内容,希望文章能够帮你解决Dubbo系列(一) 从SPI开始看ExtensionLoader所遇到的程序开发问题。

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

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

评论列表共有 0 条评论

立即
投稿
返回
顶部