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:
     *
     * 
  • when the url is registry://224.5.6.7:1234/org.apache.dubbo.registry.RegistryService?application=dubbo-sample, * then the protocol is RegistryProtocol
  • * *
  • when the url is dubbo://224.5.6.7:1234/org.apache.dubbo.config.api.DemoService?application=dubbo-sample, then * the protocol is DubboProtocol
  • *

    * Actually,when the {@link ExtensionLoader} init the {@link Protocol} instants,it will automatically wraps two * layers, and eventually will get a ProtocolFilterWrapper or ProtocolListenerWrapper */ 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 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 = (%= 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> 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宏伟的建筑构建其上。

    你可能感兴趣的:(与后端技术相关的白话文,dubbo,分布式,SPI)