Dubbo的SPI机制(2)

67 阅读4分钟

dubbo的spi机制和jdk机制区别

原理类似,都是从指定文件中加载实现类,但是JDK的加载逻辑比较简单

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源
  • 如果扩展点加载失败,JDK SPI 没给出详细信息,不方便定位问题,Dubbo SPI 在失败时记录真正的失败原因,并打印出来
  • 增加IOC和AOP的能力
  • 增加排序能力
  • 增加条件激活能力提供了一系列更灵活的 API,如获取所有 SPI 扩展实现、根据名称查询某个扩展实现、根据类型查询扩展实现、查询匹配条件的扩展实现等

从源码了解Dubbo的SPI机制

Dubbo 中,SPI 加载固定扩展类的入口是 ExtensionLoader 的 getExtension 方法

public T getExtension(String name, boolean wrap) {
    checkDestroyed();
    if (StringUtils.isEmpty(name)) {
        throw new IllegalArgumentException("Extension name == null");
    }
    // 获取默认扩展类
    // 在注解@SPI可以设置value值,为对应实现类的名字,并且在getExtension中设置true值,则获取默认设置的值
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    String cacheKey = name;
    if (!wrap) {
        cacheKey += "_origin";
    }
    final Holder<Object> holder = getOrCreateHolder(cacheKey);
    Object instance = holder.get();
    //如果有两个线程同时来获取同一个name的扩展点对象,那只会有一个线程会进行创建
    // 双重锁,双重检查
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                // 创建扩展点实例对象
                instance = createExtension(name, wrap);//创建扩展点对象
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

创建扩展点实例对象,dubbo在创建扩展点对象的时候,实现了简单的IOC和AOP逻辑,

private T createExtension(String name, boolean wrap) {
    // 获取扩展类{name: Class}  接口的所有实现类
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null || unacceptableExceptions.contains(name)) {
        throw findException(name);
    }
    try {
        // ioc 实例缓存
        T instance = (T) extensionInstances.get(clazz);
        if (instance == null) {
            // 创建实例,放到缓存
            extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
            instance = (T) extensionInstances.get(clazz);
            instance = postProcessBeforeInitialization(instance, name);
            // 依赖注入
            injectExtension(instance);
            instance = postProcessAfterInitialization(instance, name);
        }

       // aop
        if (wrap) {
            List<Class<?>> wrapperClassesList = new ArrayList<>();
            // 进行排序
            if (cachedWrapperClasses != null) {
                wrapperClassesList.addAll(cachedWrapperClasses);
                wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                Collections.reverse(wrapperClassesList);
            }

            if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                // 遍历包装类
                for (Class<?> wrapperClass : wrapperClassesList) {
                    Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                    boolean match = (wrapper == null) ||
                        ((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) &&
                            !ArrayUtils.contains(wrapper.mismatches(), name));
                    if (match) {
                        // 获取对饮过构造方法是参数是这个类型的wrapper,然后属性注入injectExtension
                        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                        instance = postProcessAfterInitialization(instance, name);
                    }
                }
            }
        }

        // Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.
        initExtension(instance);
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
            type + ") couldn't be instantiated: " + t.getMessage(), t);
    }
}

ioc逻辑中,先从缓存获取,如果获取不到,尝试实例化对象,并通过setter方法初始化对象的成员变量。目前dubbo仅支持setter方法注入

首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。

private T injectExtension(T instance) {

    if (objectFactory == null) {
        return instance;
    }

    try {
        // 遍历目标类的所有方法
        for (Method method : instance.getClass().getMethods()) {
            // 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public
            if (!isSetter(method)) {
                continue;
            }
            /**
             * 检测是否有 DisableInject 注解修饰.
             */
            if (method.getAnnotation(DisableInject.class) != null) {
                continue;
            }

            /**
             * 检测是否实现了ScopeModelAware、ExtensionAccessorAware类,如果实现则不注入
             */
            if (method.getDeclaringClass() == ScopeModelAware.class) {
                    continue;
            }
            if (instance instanceof ScopeModelAware || instance instanceof ExtensionAccessorAware) {
                if (ignoredInjectMethodsDesc.contains(ReflectUtils.getDesc(method))) {
                    continue;
                }
            }

            // 基本类型不注入
            Class<?> pt = method.getParameterTypes()[0];
            if (ReflectUtils.isPrimitives(pt)) {
                continue;
            }

            try {
                // 获取属性名,比如 setName 方法对应属性名 name
                String property = getSetterProperty(method);
                // 从 ObjectFactory 中获取依赖对象
                Object object = objectFactory.getExtension(pt, property);
                if (object != null) {
                    // 注入
                    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;
}

在上面代码中,objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展

aop机制

Dubbo AOP 机制采用 wrapper 设计模式实现,要成为一个 AOP wrapper 类,必须同时满足以下几个条件:

  1. wrapper 类必须实现 SPI 接口,如 class QosProtocolWrapper implements Protocol
  2. 构造器 constructor 必须包含一个相同的 SPI 参数,如 QosProtocolWrapper(Protocol protocol)
  3. wrapper 类必须和普通的 SPI 实现一样写入配置文件,如 resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol

wrapper示例如下:

public class QosProtocolWrapper implements Protocol, ScopeModelAware {
    private final Protocol protocol;
    public QosProtocolWrapper(Protocol protocol) {
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }
}

写入配置文件 resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol

qos=org.apache.dubbo.qos.protocol.QosProtocolWrapper

在通过 getExtension(name) 尝试获取并加载 SPI 扩展实例时,Dubbo 框架会判断所有满足以上 3 个条件的 wrapper 类实现,并将 wrapper 类按顺序包在实例外面,从而达到 AOP 拦截的效果。

以下是 wrapper 判断与加载的实现逻辑,你还可以使用 @Wrapper 注解来控制 wrapper 类的激活条件

private T createExtension(String name, boolean wrap) {
	Class<?> clazz = getExtensionClasses().get(name);
	T instance = (T) extensionInstances.get(clazz);
	// ...
	if (wrap) { // 如果调用方告知需要 AOP,即 wrap=true
		List<Class<?>> wrapperClassesList = new ArrayList<>();
		if (cachedWrapperClasses != null) {
			wrapperClassesList.addAll(cachedWrapperClasses);
			wrapperClassesList.sort(WrapperComparator.COMPARATOR);
			Collections.reverse(wrapperClassesList);
		}

		if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
			for (Class<?> wrapperClass : wrapperClassesList) {
			    // 通过 @Wrapper 注解判断当前 wrapper 类是否要生效
				Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
				boolean match = (wrapper == null)
						|| ((ArrayUtils.isEmpty(wrapper.matches())
										|| ArrayUtils.contains(wrapper.matches(), name))
								&& !ArrayUtils.contains(wrapper.mismatches(), name));
				if (match) {
					instance = injectExtension(
							(T) wrapperClass.getConstructor(type).newInstance(instance));
					instance = postProcessAfterInitialization(instance, name);
				}
			}
		}
	}
}