Dubbo SPI详解

544 阅读6分钟

1.什么是SPI?如何使用?

(1) SPI全称为(Service Provider Interface),是JDK内置的一种服务提供发现机制。目前不少框架用它来做服务的扩展发现,简单来说,它就是一种动态替换发现的机制。使用SPI机制的优势来实现解耦,使得第三方服务模块的装配控制逻辑与调用者的业务代码分离。

(2) JDK遵循如下约定:

  • 当服务提供者提供了接口的一种具体实现后,在META-INF/services目录下创建一个以“接口全限定名”为命令的文件,内容为实现类的全限定名;

  • 接口实现类所在的jar包放在主程序的classpath中;

  • 主程序通过java.util.ServiceLoader动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;

  • SPI的实现类必须携带一个无参构造方法;

(3) 如何使用:

ServiceLoader<HelloService> service = ServiceLoader.load(HelloService.class);
for (HelloService ser : service)
{    
    System.out.println(ser.sayHello());
}

2.Dubbo中是如何实现SP技术的?如何使用?

(1) Dubbo并未使用Java SPI,而是重新实现了一套功能更强的SPI机制。DubboSPI的相关逻辑被封装在了ExtensionLoader类中,通过ExtensionLoader。Dubbo SPI所需的配置文件需放置在META-INF/dubbo路径下,配置内容如下。

spring=org.apache.dubbo.config.spring.extension.SpringExtensionFactory
adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory

dubbo中大量的使用了SPI来作为扩展点,通过实现同一接口的前提下,可以进行定制自己的实现类。

3.Dubbo SPI 目的

  • JDK标准的SPI会一次性实例化扩展点所有实现,如果扩展实现初始化很耗时,但如果没有用上也加载,会很浪费资源。

  • 如果有扩展点加载失败,则所有扩展点无法使用

  • 提供了对扩展点包装的功能(Adaptive),并且还支持通过set的方式对其它的扩展点进行注入。

4.ExtensionLoader中重要域说明

// 缓存dubbo 全局ExtensionLoader实例,以接口class对象为key
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
// 缓存dubbo 全局通过ExtensionLoader已经加载初始化的实例,以接口class对象为key
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);

// 当前ExtensionLoader 实例对应的接口lass对象
private final Class<?> type;

// 在初始化实例时,寻找对应待set方法注入其他对象
private final ExtensionFactory objectFactory;
// 存储该接口下的扩展点Class(扩展点Class对象)->Name(扩展点名称)映射
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
// 存储该接口下的扩展点Name(扩展点名称)->Class(扩展点Class对象)映射
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();

private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
// 存储该接口下的已经实例化的扩展点的实例对象映射,按扩展点名称为key
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
// 存储该接口已经适配的实例对象
private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
// 存储该接口已经适配的Class对象
private volatile Class<?> cachedAdaptiveClass = null;

5.自适应扩展机制的理解

有些拓展并不像在框架启动节点被加载,而是希望在拓展方式被调用时,根据运行时参数进行加载。Dubbo为需要动态拓展接口生成具有代理功能的代码。然后通过javassit 或jdk编译这段代码,得到Class类。最后再通过反射创建代理类。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
String[] value() default {};
}

首先它是一个注解,可以注解到方法上或者类上。当Adaptive注解在类上时,Dubbo不会为该类生成代理类,直接将该类作为默认的适应类。如果注解在接口方法上,表示拓展的加载逻辑需由框架自动生成。具体代码逻辑如下:

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
boolean overridden) throws NoSuchMethodException {
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
if (clazz.isAnnotationPresent(Adaptive.class)) {
// 如果有Adaptive类上的注解,则将该类直接缓存到cachedAdaptiveClass域中,在后面逻辑中,
// 就会以该域是否有值作为参照,是否去调用createAdaptiveExtensionClass
cacheAdaptiveClass(clazz, overridden);
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);

6.ExtensionLoader 与 ExtensionFactory 之间关系的理解

每个ExtensionLoader都会对应一个ExtensionFactory类型的域,它的作用是在实例化对应class对象之后,需要注入其他对象时用得到。通过ExtensionFactory获得待注入对象实例。

ExtensionFactory它也是个被SPI注解标识的类。同样也是需要通ExtensionLoader进行加载。ExtensionFactory会默认通过ExtensionLoader私有构造方法进行加载,且是第一个被加载的对象。只有ExtensionFactory对象被加载之后,才能保证后面加载的对象的set注入实例才能够正确的工作。

ExtensionFactory有三个实现类

  • SpiExtensionFactory

  • AdaptiveExtensionFactory 该类级别被Adaptive注解

  • SpringExtensionFactory

由于AdaptiveExtensionFactory 被Adaptive注解,则该类默认为ExtensionFactory自适应实现,ExtensionLoader 就不会产生代理对象进行代理了。

其实AdaptiveExtensionFactory的实现是最终都会调用SpiExtensionFactory和SpringExtensionFactory的具体实现方法。具体代码如下。这样做的目的就是为了最大程度的去找到待注入的实例对象。

public <T> T getExtension(Class<T> type, String name) {
for (ExtensionFactory factory : factories) {
    // 这边在遍历上面两个ExtensionFactory
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}

7.Dubbo SPI流程图

ExtensionLoader<HelloService>  extensionLoader  = ExtensionLoader.getExtensionLoader(HelloService.class);
        HelloService helloService = extensionLoader.getExtension("name");
        helloService.sayHello();

以上代码对应流程图如下

8.阅读Dubbo SPI代码的心得体会

(1) 大量用到ConcurrentHashMap在对ConcurrentHashMap增加数据时的操作,保证线程安全,用putIfAbsent() 方式进行赋值。再通过get方式获取。这样有效的保证了线程安全。

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);
        }

(2) Holder类设计

public class Holder<T> {

    private volatile T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }

}

synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        instance = createAdaptiveExtension();
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
                        createAdaptiveInstanceError = t;
                        throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                    }
                }
            }

纯属个人理解:Holder类的作用,是为了在该对象还未初始化时,可以有加锁的对象(包裹目标对象的Holder对象),防止多线程同时在初始化该对象。Hodler类包裹目标+volatile+synchronized,可以让程序做到,延迟加载 + 线程安全。

(3) 多出运用双重检查

final 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);
                }
            }
        }

双重检查的目的也防止多次初始化目标对象。比如A线程拿到对象锁了,B线程在等待该锁。A线程运行完成,释放掉该锁,然后接下来B线程获得该锁之后,如果不进行第二次判断,线程B同样会初始化对象两次。在ExtensionLoader中多处使用双重检查的代码设计。