Dubbo源码学习——SPI/IOC/DI

之前的博客大致介绍了Dubbo体系中比较重要的接口抽象概念,在宏观上了解其设计,具体的实现必然要围绕这些接口和概念展开,在核心工作流介绍之前需要先单独讲下Dubbo设计中比较重要的SPI机制,我个人认为算是Dubbo的设计精髓,有限实现了IOC/DI的功能,是Dubbo的核心。良兵猛将如果配合不好也是一群莽夫,这里Dubbo用一个叫ExtensionLoader的工具类,如同皇帝圣旨一般,协调组织三军,或调兵遣将,或协同作战。

 

ExtensionLoader光从命名上看,就是扩展点加载器,什么是扩展点,简单点说就是面向接口编程,核心的功能都是先用接口规范好行为,具体实现,希望能够在实际加载或运行期找到具体的功能实现类,去找到这些接口实现类就是“load extension”,这个扩展点加载器的代码有些多,也没啥好全部贴出来的,我就从核心的加载功能去梳理这些类的属性和重要方法: 

public T getAdaptiveExtension()

基本上这个方法出现在核心抽象接口的加载上,比如ReferenceConfig里面去加载Protocol属性这个重要实现

/**
     * The {@link Protocol} implementation with adaptive functionality,it will be different in different scenarios.
     * A particular {@link Protocol} implementation is determined by the protocol attribute in the {@link URL}.
     * For example:
     *
     * <li>when the url is registry://224.5.6.7:1234/org.apache.dubbo.registry.RegistryService?application=dubbo-sample,
     * then the protocol is <b>RegistryProtocol</b></li>
     *
     * <li>when the url is dubbo://224.5.6.7:1234/org.apache.dubbo.config.api.DemoService?application=dubbo-sample, then
     * the protocol is <b>DubboProtocol</b></li>
     * <p>
     * Actually,when the {@link ExtensionLoader} init the {@link Protocol} instants,it will automatically wraps two
     * layers, and eventually will get a <b>ProtocolFilterWrapper</b> or <b>ProtocolListenerWrapper</b>
     */
    private static final Protocol REF_PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

看到上面的英文注释没,这个扩展点是协议的实现,如果是一般人设计的,这个ReferenceConfig得绑死一个具体协议,那么后果就是我的消费方系统里面,一个ReferenceConfig只能引用一种类型的服务方法,假如是redis协议的,那我想再引用dubbo就莫得办法了,再来一个ReferenceConfig?如果就是一个方法,服务提供方自己改了协议,我也跟着改,那么多服务提供方都改了,我的应用还要不要了,就是想在运行时能自动选择具体的协议加载!Dubbo满足你,上面REF_PROTOCOL 这个属性的注释上说明,如果URL(配置)上是registry协议,那么就启用RegistryProtocol协议,如果是dubbo协议,那就启用DubboProtocol,甚至那天我自己搞一个协议【FxxKPM】协议,我的代码不用动,只要URL指明是fxxkpm://就自动加载,岂不美哉。好接着看下里面的逻辑

public T getAdaptiveExtension() {
	Object instance = cachedAdaptiveInstance.get();
	if (instance == null) {
		if (createAdaptiveInstanceError != null) {
			throw new IllegalStateException("Failed to create adaptive instance: " +
					createAdaptiveInstanceError.toString(),
					createAdaptiveInstanceError);
		}

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

	return (T) instance;
}

主函数没几行代码,为了提高效率搞了个缓存,需要指明的是,一个扩展点一个ExtensionLoader,这个AdaptiveExtension也一个扩展点只有一个,为啥只有一个,因为是Adaptive的功能是Dubbo自己写字节码生成的类实现的,除非是一个具体实现类在类级别搞了一个@Adaptive注解,很少,大部分都是在方法注解,然后Dubbo去生成相应的Adaptive类重写相应的@Adaptive方法实现。继续往下看,标准的双重检查锁设计(认真脸,敲黑板?),那么在锁的临界区域内去生成相应的实例

private T createAdaptiveExtension() {
	try {
		return injectExtension((T) getAdaptiveExtensionClass().newInstance());
	} catch (Exception e) {
		throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
	}
}

看到inject了吧,想到了什么?,上面聊过,这个Adaptive类,有可能是个实体实现类,或者得Dubbo帮你生成字节码加载进入,那么得看下这个类的加载过程

private Class<?> getAdaptiveExtensionClass() {
	getExtensionClasses();
	if (cachedAdaptiveClass != null) {
		return cachedAdaptiveClass;
	}
	return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

getExtensionClasses()方法不贴里面的代码了,逻辑比较简单,Dubbo的SPI机制就封装在里面,就是读取指定目录下接口全限定名文件里面的具体实现,不过不同于Java原生的SPI,Dubbo给每个实现还前缀了个名称,要不然上面说的我要registry的Protocol或dubbo的Protocol怎么找,还校验了下一个Extension只能有个Adaptive类,多于一个就报错,还有些缓存加速的东西没必要拿上来讲了,重点看下这个创建Adaptive的Class

private Class<?> createAdaptiveExtensionClass() {
	String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
	ClassLoader classLoader = findClassLoader();
	org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
	return compiler.compile(code, classLoader);
}

我拿的代码是Apache重构过的,里面有些的逻辑和阿里原来的逻辑有些出入,Aapache把阿里之前比较粗犷的设计进行了包装,后果就是  功能埋的更深了?,我有些后悔用Apache版本的Dubbo了。。这个AdaptiveClassCodeGenerator是Apache把之前放到ExtensionLoader的逻辑抽出一个类来,generate()方法逻辑就是判断需要被生成代理类(type参数)接口得有@Adaptive方法,进去逻辑之前得先跳过一下看下下面有个编译器Compiler也是扩展点加载的,可惜这个接口的方法可没有@Adaptive修饰的方法,按照之前的逻辑,不让Dubbo帮你用字节码生成一个类,那你得提供一个@Adaptive修饰的实现类,不然去哪拿实例,用IDE很容易找到Compiler的@Adaptive类(再说一句,Dubbo体系中扩展点实现类上注解@Adaptive的很少,Compiler接口算是一个)

@Adaptive
public class AdaptiveCompiler implements Compiler {

    private static volatile String DEFAULT_COMPILER;

    public static void setDefaultCompiler(String compiler) {
        DEFAULT_COMPILER = compiler;
    }

    @Override
    public Class<?> compile(String code, ClassLoader classLoader) {
        Compiler compiler;
        ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
        String name = DEFAULT_COMPILER; // copy reference
        if (name != null && name.length() > 0) {
            compiler = loader.getExtension(name);
        } else {
            compiler = loader.getDefaultExtension();
        }
        return compiler.compile(code, classLoader);
    }

}

这个Compiler毕竟是内部使用的,确实没啥必要生成一个字节码搞一个类出来,这个AdaptiveCompiler里面的逻辑也很清晰,允许在编译前指定下用那种编译方式(Dubbo目前提供两种:Javassist和JDK),不指定的话,Compiler上的那个@SPI注解默认指定用Javassist,再回去看那个字节码生成器类AdaptiveClassCodeGenerator,里面乱七八糟生成属性啦,方法啦的一些工具方法,我就挑我之前说的,那个@Adaptive方法得重写,不然没法实现运行时动态选取指定的扩展执点覆盖执行指定方法

// 类属性挑出和方法体有关的
private static final String CODE_EXT_NAME_ASSIGNMENT = "String extName = %s;\n";
private static final String CODE_EXTENSION_ASSIGNMENT = "%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);\n";

/**
 * generate method content
 */
private String generateMethodContent(Method method) {
	Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
	StringBuilder code = new StringBuilder(512);
	if (adaptiveAnnotation == null) {
		return generateUnsupported(method);
	} else {
        // 参数配置在URL中,从方法参数找到URL的位置
		int urlTypeIndex = getUrlTypeIndex(method);

		// found parameter in URL type
		if (urlTypeIndex != -1) {
			// Null Point check
			code.append(generateUrlNullCheck(urlTypeIndex));
		} else {
			// did not find parameter in URL type
			code.append(generateUrlAssignmentIndirectly(method));
		}

		String[] value = getMethodAdaptiveValue(adaptiveAnnotation);

		boolean hasInvocation = hasInvocationArgument(method);

		code.append(generateInvocationArgumentNullCheck(method));

		code.append(generateExtNameAssignment(value, hasInvocation));
		// check extName == null?
		code.append(generateExtNameNullCheck(value));

		code.append(generateExtensionAssignment());

		// return statement
		code.append(generateReturnAndInvocation(method));
	}

	return code.toString();
}

/**
 * 获取运行时需要动态加载扩展点的名称 extName
 */
private String generateExtNameAssignment(String[] value, boolean hasInvocation) {
	// TODO: refactor it
	String getNameCode = null;
	for (int i = value.length - 1; i >= 0; --i) {
		if (i == value.length - 1) {
			if (null != defaultExtName) {
				if (!"protocol".equals(value[i])) {
					if (hasInvocation) {
						getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
					} else {
						getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
					}
				} else {
					getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
				}
			} else {
				if (!"protocol".equals(value[i])) {
					if (hasInvocation) {
						getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
					} else {
						getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
					}
				} else {
					getNameCode = "url.getProtocol()";
				}
			}
		} else {
			if (!"protocol".equals(value[i])) {
				if (hasInvocation) {
					getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
				} else {
					getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
				}
			} else {
				getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
			}
		}
	}

	return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode);
}

private String generateExtensionAssignment() {
        return String.format(CODE_EXTENSION_ASSIGNMENT, type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
    }

上面贴的代码大致就是从运行时入参列表中找到URL(消息总线,所有的配置都放它这),去找到你想运行时动态获取的扩展点名称(extName),那么方法体内帮你去执行ExtensionLoader的根据extName获取扩展点方法得到相应的扩展点(getExtension(extName)),用加载的扩展点实例再执行被重写的方法,实现了动态切换扩展点实现类的效果。讲了挺多的SPI,貌似没有提到IOC/DI,这个还得倒回去,上面我不是提到inject开头的方法名injectExtension么,就在里面了

private T injectExtension(T instance) {

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

        try {
            for (Method method : instance.getClass().getMethods()) {
                if (!isSetter(method)) {
                    continue;
                }
                /**
                 * 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) {
                        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;
    }

原理不复杂,就是找到扩展点实现方法中的那些setter方法,然后从objectFactory里面取出同名扩展点名称(extName)的实例帮你注入进去,这个objectFactory如果不涉及Spring的话,其实就是ExtensionLoader的getExtension(extName),除此之外Dubbo还对SPI加载搞了个装饰器模式的加强(Wrapper)

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

好奇这个wrapper类是怎么来的,其实就是SPI那些类,如果这些实现类存在一个构造方法,参数是自身SPI接口类型的话,那意味着可以把和我同类型的SPI接口实现类传入进来,那这个实现类就是一个wrapper类,Extension调用getExtension创建扩展点实例会像俄罗斯套娃那样把一个一个wrapper类实现并调用构造器注入进去,最后给你一个大的俄罗斯套娃,外部调用者当然也不不知道里面套了多少层,一种软件设计模式吧(装饰器模式)。

 

 ExtensionLoader还有些其他功能,等讲后续Dubbo的其他流程中会说到,不过核心的就是这个SPI机制,就像一座大厦的基石,Dubbo宏伟的建筑构建其上。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值