dubbo系列之内核SPI拓展机制初识(七)

245 阅读7分钟

欢迎关注公众号【sharedCode】致力于主流中间件的源码分析, 可以直接与我联系

博主个人网站:www.shared-code.com

前言

dubbo为了适应不同的注册中心,底层通讯协议等功能的扩展,采用SPI的思想,即Service Provider Interface , 也就是我们定义了服务接口标砖,让服务商去实现。 jdk通过ServiceLoader类实现了SPI机制的服务查找功能,有兴趣的可以去网上搜一下jdk的SPI思想, , 接下来我们会讲解一下dubbo是如何实现SPI机制,SPI机制一般来说,会提供一个接口,然后供厂商来实现。

JDK实现SPI机制时通过ServiceLoader来 检索META-INF/services/ 文件夹下面的配置文件,那么dubbo应该也有类似的东西吧,是的,dubbo是通过ExtensionLoader 来实现文件的读取和解析,跟JDK的ServiceLoader起的是相同的作用。

Dubbo改进了JDK标准的SPI的以下问题:

  • JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK标准的ScriptEngine,通过getName();获取脚本类型的名称,但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因。
  • 增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。

扩展类定义

@SPI注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {

    /**
     * 默认的扩展名
     */
    String value() default "";

}

dubbo的扩展类全部定义在一下三个文件夹里面

META-INF/services/

META-INF/dubbo/

META-INF/dubbo/internal/ dubbo内存实现的各种扩展全部放在这个目录里面。

dubbo拓展机制SPIjar目录图.jpg

文件名的规则为:包名+接口名

例:

com.alibaba.dubbo.rpc.Protocol

下面的内容就是com.alibaba.dubbo.rpc.Protocol文件的具体内容

filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol
memcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol
rest=com.alibaba.dubbo.rpc.protocol.rest.RestProtocol
registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
qos=com.alibaba.dubbo.qos.protocol.QosProtocolWrapper

下面以Protocol为例,讲解一下dubbo的SPI机制。

@SPI("dubbo")
public interface Protocol {
  
    int getDefaultPort();

   /**
     * 暴露远程服务:<br>
     * 1. 协议在接收请求时,应记录请求来源方地址信息:RpcContext.getContext().setRemoteAddress();<br>
     * 2. export()必须是幂等的,也就是暴露同一个URL的Invoker两次,和暴露一次没有区别。<br>
     * 3. export()传入的Invoker由框架实现并传入,协议不需要关心。<br>
     * 
     * @param <T> 服务的类型
     * @param invoker 服务的执行体
     * @return exporter 暴露服务的引用,用于取消暴露
     * @throws RpcException 当暴露服务出错时抛出,比如端口已占用
     */
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    /**
     * 引用远程服务:<br>
     * 1. 当用户调用refer()所返回的Invoker对象的invoke()方法时,协议需相应执行同URL远端export()传入的Invoker对象的invoke()方法。<br>
     * 2. refer()返回的Invoker由协议实现,协议通常需要在此Invoker中发送远程请求。<br>
     * 3. 当url中有设置check=false时,连接失败不能抛出异常,需内部自动恢复。<br>
     * 
     * @param <T> 服务的类型
     * @param type 服务的类型
     * @param url 远程服务的URL地址
     * @return invoker 服务的本地代理
     * @throws RpcException 当连接服务提供方失败时抛出
     */
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

    
    void destroy();

}

Protocol类上面使用了@SPI注解,默认的扩展实现已经指定为了dubbo, 我们可以看一下他具体使用的地方。

在上一文中我们了解到的ServiceBean的父类ServiceConfig中有下面这一行代码,这是静态常量,在类加载的时候就会进行初始化。

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

ExtensionLoader

每个扩展类都会生成一个ExtensionLoader , 然后通过生成的ExtensionLoader去获取到一个扩展类实现,下面我们可以看一下getExtensionLoader方法,看下dubbo是如何为每个扩展类生成ExtensionLoader对象的。

@SuppressWarnings("unchecked")
    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
      	// 传入的扩展类不能为空
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
      	// 传入的扩展类不是接口的话,则报异常了
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        // 类上是否包含了@SPI注解,如果没有,则报异常了
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }
		// 从缓存的MAP中获取ExtensionLoader实现、
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
          	// 获取不到则需要 创建一个并放入缓存MAP
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
          	// 从MAP中取
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

上面的代码总结一下就是分为了两步走

1.从缓存中根据扩展类获取ExtensionLoader

2.获取不到则创建 一个新的。

通俗一点讲,就是这两步,下面看一下ExtensionLoader是如何被创建的。

private ExtensionLoader(Class<?> type) {
     this.type = type;
     this.objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).
                         getAdaptiveExtension());
}

两行代码,表面看上去特别简单,其实这里需要注意的是objectFactory 这个变量,这是dubbo的SPI机制实现**依赖注入 ** 的核心所在。我们解读一下这段代码

// 当前的type不是ExtensionFactory类型的,则调用 ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()
// 创建一个ExtensionFactory扩展类实现,如果是,则无需创建,为null即可。
this.objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).
                         getAdaptiveExtension());

ExtensionFactory

所以,回到我们最初的代码上去,第一次进来,type = Protocol.class , 所以在主动调用ExtensionLoader.getExtensionLoader(ExtensionFactory.class).

getAdaptiveExtension()) 去创建一个ExtensionFactory扩展类实现 , 默认的扩展类实现为AdaptiveExtensionFactory

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
      	// 获取ExtensionFactory的ExtensionLoader 
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
      	// 获取ExtensionFactory支持的其他扩展类实现,
        for (String name : loader.getSupportedExtensions()) {
          	// 通过类名name ,生成其他扩展类实现的ExtensionLoader
            list.add(loader.getExtension(name));
        }
        // 赋予到factories
        factories = Collections.unmodifiableList(list);
    }
	// 该方法用于依赖注入使用,通过类型和bean的名称获取。
    @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;
    }

}

上面讲解了在实例化Protocol的扩展类加载器的时候,实例化扩展工厂类。 我们首先看一下ExtensionFactory的代码

@SPI
public interface ExtensionFactory {

    <T> T getExtension(Class<T> type, String name);

}

这个类是一个加了@SPI注解的接口类,根据dubbo的SPI机制,我们在jar包的资源路径下查看一下com.alibaba.dubbo.common.extension.ExtensionFactory

看一下这个类dubbo提供了哪些实现

adaptive=com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory
spring=com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory

dubbo 一共提供了三种实现 ,那为啥默认的扩展工厂类是AdaptiveExtensionFactory 呢? 比较在ExtensionFactory的注解中并没有直接指定默认实现。我们来看一下扩展类的实现。

AdaptiveExtensionFactory

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
    // 代码省略
}

SpiExtensionFactory

public class SpiExtensionFactory implements ExtensionFactory {

// 代码省略

}

SpringExtensionFactory

public class SpringExtensionFactory implements ExtensionFactory {
	// 代码省略
}

在这三个实现类当中,只有AdaptiveExtensionFactory的类上有@Adaptive 注解,其他两个都没有,拥有这个注解的类,在解析过程中会放入

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Error when load extension class(interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + "is not subtype of interface.");
        }
  		// 类上包含Adaptive注解
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            if (cachedAdaptiveClass == null) {
              	// 将cachedAdaptiveClass设置为当前的类。
                cachedAdaptiveClass = clazz;
            } else if (!cachedAdaptiveClass.equals(clazz)) {
                throw new IllegalStateException("More than 1 adaptive class found: "
                        + cachedAdaptiveClass.getClass().getName()
                        + ", " + clazz.getClass().getName());
            }
        } else if (isWrapperClass(clazz)) {
            // 代码省略
        } else {
           // 代码省略
        }
    }

如果扩展实现类上面,拥有@Adaptive注解的话,则会将cachedAdaptiveClass设置为当前类,所以在获取扩展实现类的时候,就默认直接取到了AdaptiveExtensionFactory

private Class<?> getAdaptiveExtensionClass() {
  		// 解析资源
        getExtensionClasses();
  		// 扩展实现类中有类里面添加了@Adaptive注解。
        if (cachedAdaptiveClass != null) {	
          	// 直接返回cachedAdaptiveClass
            return cachedAdaptiveClass;
        }
         // 通过字节码技术,创建代理对象。
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

@Adaptive注解的定义如下:

这个注解和@SPI注解配置使用,用于它可以标注在SPI接口扩展类上,也可以标注在SPI接口的方法上。如果这个注解标注在SPI接口实现的扩展类上时,获取的SPI实例对象就是标注了@Adaptive注册的类。

ExtensionFactory是作为SPI的依赖注入的功能实现的,AdaptiveExtensionFactory本身不提供依赖注入,但是他里面有一个属性factories ,这个属性中包含了SpiExtensionFactory , SpringExtensionFactory 这两个实现。

SpiExtensionFactory : 负责解析带有@SPI注解的其他扩展类,用来依赖注入

SpringExtensionFactory:用来获取spring容器中的bean , 用来依赖注入,该类在初始化ServiceBean的时候被赋予了applicationContext,所以是可以获取到的。

至此,ExtensionFactory解析完成,这个解析完了之后,Protocol的ExtensionLoader生成了

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

下一篇文章中会讲解Protocol的ExtensionLoader是如何获取到扩展实现类的。

欢迎关注公众号【sharedCode】致力于主流中间件的源码分析, 可以直接与我联系

博主个人网站:www.shared-code.com