Dubbo——SPI及自适应扩展原理

2020-09-07 11:02:10 浏览数 (1)

文章目录

  • 引言
  • 正文
    • 一、什么是SPI?
        1. Java SPI的实现
        1. Dubbo SPI实现原理
        • 由配置文件得到的猜想
        • SPI源码
    • 二、自适应扩展机制
    • 三、Dubbo IOC
  • 总结

引言

Dubbo虽然已交由apache管理,并且社区活跃度也不如SpringCloud,但也是国内应用比较广泛的RPC框架,其背后的设计思想非常值得我们学习借鉴。鉴于Dubbo官方文档对于基础的使用配置已经讲解的非常清楚了,这里就不再赘述。本系列文章将基于Dubbo2.5.3版本的源码做分析。而Dubbo中最核心的一点就是SPI和自适应扩展,Dubbo的高扩展性以及其它功能都是在这个基础上实现的,理解掌握其原理才能看懂后面的一系列功能的实现原理,对我们平时实现高扩展性也非常有帮助。(PS:Dubbo源码不像Zookeeper那样有明确的入口,可以根据官网的源码分析指导找到。)

正文

一、什么是SPI?

SPI(Service Provider Interface)是一种服务发现机制,它的作用是解耦接口和其具体实现,让各厂商可以自定义自己的扩展实现,并使得程序可以自动使用引入的组件。

什么意思呢?举个例子就清楚了,Java原生就提供SPI机制,比如数据库连接驱动的实现就是SPI很好的一个应用,在Java sql下提供了Driver接口,而具体的驱动程序是由各个数据库厂商实现的,平时我们要连接哪个数据库只需要引入对应的驱动jar包就可以了,非常方便,即使数据库变更也一样,我们不需要改变代码。而Dubbo的SPI机制则是在此基础上提供了更强大的功能,因此,学习了解Java SPI更益于深入了解Dubbo,下面就先来看看Java SPI的使用吧。

1. Java SPI的实现

  • 首先定义一个服务接口和两个自定义实现类(一般实现类会由第三方提供):
代码语言:javascript复制
public interface SPI {

    void sayHello(String s);

}

public class SPIImpl1 implements SPI {

    @Override
    public void sayHello(String s) {
        System.out.println("Hello, "   s   "! I'm one");
    }
}

public class SPIImpl2 implements SPI {

    @Override
    public void sayHello(String s) {
        System.out.println("Hello, "   s   "! I'm two");
    }
}
  • 然后在resources/META-INF/services创建一个以接口全类名为名称的文件cn.dark.SPI,并在文件中填入自定义实现类的全类名
代码语言:javascript复制
cn.dark.SPIImpl1
cn.dark.SPIImpl2
  • 最后通过ServiceLoader类发现并调用服务:
代码语言:javascript复制
ServiceLoader<SPI> load = ServiceLoader.load(SPI.class);
for (SPI spi : load) {
    spi.sayHello("SPI");
}

输出:

代码语言:javascript复制
Hello, SPI! I'm one
Hello, SPI! I'm two

如果需要扩展新的实现,只需要将实现类配置到资源文件中,并引入对应的Jar即可。Java SPI机制就这么简单,其实现原理也很简单,读者们可以自行阅读源码,这里就不再详细分析了,那Dubbo的SPI有何异同呢?

2. Dubbo SPI实现原理

由配置文件得到的猜想

Dubbo SPI是基于Java原生SPI思想重新实现的一套更加强大的SPI机制,类似的你可以在Dubbo的META-INF.dubbo.internal(不止这一个路径,后面在源码中会看到)路径下看到很多以接口全类名命名的配置文件,但是文件内容和JAVA SPI有点不一样,以Protocol扩展为例:

代码语言:javascript复制
registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol
injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
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=memcom.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol

看到这么多扩展类(每一个配置文件中都有很多),我们首先应该思考一个问题:Dubbo一启动,就加载所有的扩展类么?作为一个优秀的RPC框架,肯定不会耗时耗力做这样的无用功,所以肯定会通过一种方式拿到指定的扩展才对。我们可以看到大多是以键值对方式(表示为extName-value)配置的扩展,那么不难猜测,这里的extName就是用来实现上面所说的功能的。

那到底是不是呢?以上纯属猜测,下面就到源码中去验证。

SPI源码

Dubbo中实现SPI的核心类是ExtensionLoader,该类并未提供公共的构造方法来初始化,而是通过getExtensionLoader方法获取一个loader对象:

代码语言:javascript复制
// loader缓存
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
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!");
    }
    if(!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type("   type   
                ") is not extension, because WITHOUT @"   SPI.class.getSimpleName()   " Annotation!");
    }
    
    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);
    }
    return loader;
}

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

这里的class参数就是扩展点的接口类型,每一个loader都需要绑定一个扩展点类型。然后首先从缓存中获取loader,未获取到就初始化一个loader并放入缓存。而在私有构造器初始化的时候我们需要注意objectFactory这个变量,先大概有个映像,后面会用到。

拿到loader之后,就可以调用getExtension方法去获取指定的扩展点了,该方法传入了一个name参数,不难猜测这个就是配置文件中的键,可以debugger验证一下:

代码语言:javascript复制
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
public T getExtension(String name) {
	if (name == null || name.length() == 0)
	    throw new IllegalArgumentException("Extension name == null");
	if ("true".equals(name)) {
	    return getDefaultExtension();
	}
	// 从缓存中获取Holder对象,该对象的值就是扩展对象
	Holder<Object> holder = cachedInstances.get(name);
	if (holder == null) {
	    cachedInstances.putIfAbsent(name, new Holder<Object>());
	    holder = cachedInstances.get(name);
	}
	// 缓存中没有该扩展,说明还没有加载扩展类,就去配置文件中加载并创建对应的扩展对象
	// 这里通过双重校验锁的方式保证线程安全,Dubbo中大量运用了该技巧
	Object instance = holder.get();
	if (instance == null) {
	    synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                instance = createExtension(name);
                holder.set(instance);
            }
        }
	}
	return (T) instance;
}

同样的也是先从缓存拿,缓存没有就创建并添加到缓存,因此主要看createExtension方法:

代码语言:javascript复制
// 扩展类实例缓存对象
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
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, (T) clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        // 依赖注入,这里及后面都和当前流程无关,可以先略过,有个印象就好
        injectExtension(instance);
        // 获取包装类并实例化,最后注入依赖对象
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && wrapperClasses.size() > 0) {
            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   ")  could not be instantiated: "   t.getMessage(), t);
    }
}

关键点代码就在getExtensionClasses方法中,怎么从配置文件中加载扩展类的。而该方法主要是调用了loadExtensionClasses方法:

代码语言:javascript复制
private Map<String, Class<?>> loadExtensionClasses() {
	// 判断接口上是否标注有 @SPI注解,该注解的值就是默认使用的扩展类,
	// 赋值给cachedDefaultName变量缓存起来
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if(defaultAnnotation != null) {
        String value = defaultAnnotation.value();
        if(value != null && (value = value.trim()).length() > 0) {
            String[] names = NAME_SEPARATOR.split(value);
            if(names.length > 1) {
                throw new IllegalStateException("more than 1 default extension name on extension "   type.getName()
                          ": "   Arrays.toString(names));
            }
            if(names.length == 1) cachedDefaultName = names[0];
        }
    }
    
    // 真正读取配置文件的方法
    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    loadFile(extensionClasses, DUBBO_DIRECTORY);
    loadFile(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}

该方法主要是缓存当前扩展接口指定的默认扩展实现类(@SPI注解指定),并调用loadFile读取配置文件,从这里我们可以看到Dubbo默认是读取以下3个文件夹中的配置文件:

代码语言:javascript复制
 private static final String SERVICES_DIRECTORY = "META-INF/services/";
 private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
 private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY   "internal/";

然后是loadFile,该方法很长,不全部放上来了,这里提取关键的代码:

代码语言:javascript复制
String fileName = dir   type.getName();

首先通过文件全路径找到对应的文件,并用BufferedReader一行行读取文件内容:

代码语言:javascript复制
String name = null;
int i = line.indexOf('=');
if (i > 0) {
	// 配置文件中的键
    name = line.substring(0, i).trim();
    // 扩展点全类名
    line = line.substring(i   1).trim();
}
if (line.length() > 0) {
	// 加载class,如果有些类的依赖jar包未导入,这里就会抛出异常(比如WebserviceProtocol)
    Class<?> clazz = Class.forName(line, true, classLoader);
    // 验证当前类型是否是扩展类的父类型
    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 注解,表示为一个自定义的自适应扩展类
    // 如果是将其缓存到cachedAdaptiveClass
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        if(cachedAdaptiveClass == null) {
            cachedAdaptiveClass = clazz;
        } else if (! cachedAdaptiveClass.equals(clazz)) {
        	// 超过一个自定义的自适应扩展类就抛出异常
            throw new IllegalStateException("More than 1 adaptive class found: "
                      cachedAdaptiveClass.getClass().getName()
                      ", "   clazz.getClass().getName());
        }
    } else {
        try {
     		// 进入到该分支表示为Wrapper装饰扩展类,该类都有一个特征:包含
     		// 一个有参的构造器,如果没有,就抛出异常进入到另一个分支,
     		// Wrapper类的作用我们后面再分析
            clazz.getConstructor(type);
            // 缓存Wrapper到cachedWrapperClasses中
            Set<Class<?>> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                wrappers = cachedWrapperClasses;
            }
            wrappers.add(clazz);
        } catch (NoSuchMethodException e) {
        	// 进入此分支表示为一个普通的扩展类
            clazz.getConstructor();
            if (name == null || name.length() == 0) {
            	// 由于历史原因,Dubbo最开始配置文件中并不是以K-V来配置的
            	// 扩展点,而是会通过@Extension注解指定,所以这里会通过
            	// 该注解去获取到name
                name = findAnnotationName(clazz);
                // 由于@Extension废弃使用,但配置文件中仍存在非K-V的配置,
                // 所以这里是直接通过类名获取简单的name
                if (name == null || name.length() == 0) {
                    if (clazz.getSimpleName().length() > type.getSimpleName().length()
                            && clazz.getSimpleName().endsWith(type.getSimpleName())) {
                        name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
                    } else {
                        throw new IllegalStateException("No such extension name for the class "   clazz.getName()   " in the config "   url);
                    }
                }
            }
            String[] names = NAME_SEPARATOR.split(name);
            if (names != null && names.length > 0) {
                // 判断当前扩展点是否标注有@Activate注解,该注解表示
                // 该扩展点自动激活
                Activate activate = clazz.getAnnotation(Activate.class);
                if (activate != null) {
                    cachedActivates.put(names[0], activate);
                }
                for (String n : names) {
                    if (! cachedNames.containsKey(clazz)) {
                        cachedNames.put(clazz, n);
                    }
                    Class<?> c = extensionClasses.get(n);
                    if (c == null) {
                        extensionClasses.put(n, clazz);
                    } else if (c != clazz) {
                        throw new IllegalStateException("Duplicate extension "   type.getName()   " name "   n   " on "   c.getName()   " and "   clazz.getName());
                    }
                }
            }
        }
    }
}

至此,我们就看到了Dubbo SPI的实现全过程,我们也了解了Dubbo强大的扩展性是如何实现的,但是这么多扩展,Dubbo在运行中是如何决定调用哪一个扩展点的方法呢?这就是Dubbo另一强大的机制:自适应扩展。(PS:这里需要留意cachedAdaptiveClasscachedWrapperClasses两个变量的赋值,后面会用到。)

二、自适应扩展机制

什么是自适应扩展?上文刚刚也说了,Dubbo中存在很多的扩展类,这些扩展类不可能一开始就全部初始化,那样非常的耗费资源,所以我们应该在使用到该类的时候再进行初始化,也就是懒加载。但是这是比较矛盾的,拓展未被加载,那么拓展方法就无法被调用(静态方法除外)。拓展方法未被调用,拓展就无法被加载(官网原话)。所以也就有了自适应扩展机制,那么这个原理是怎样的呢?

首先需要了解@Adaptive注解,该注解可以标注在类和方法上:

  • 标注在类上,表明该类为自定义的适配类
  • 标注在方法上,表明需要动态的为该方法创建适配类

当有地方调用扩展类的方法时,首先会调用适配类的方法,然后适配类再根据扩展名称调用getExtension方法拿到对应的扩展类对象,最后调用该对象的方法即可。流程就这么简单,下面看看代码怎么实现的。

首先我们回到ExtensionLoader的构造方法中:

代码语言:javascript复制
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());

其中调用了getAdaptiveExtension方法,从方法名不难看出就是去获取一个适配类对象:

代码语言:javascript复制
private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
public T getAdaptiveExtension() {
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
        if(createAdaptiveInstanceError == null) {
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        instance = createAdaptiveExtension();
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
                        createAdaptiveInstanceError = t;
                        throw new IllegalStateException("fail to create adaptive instance: "   t.toString(), t);
                    }
                }
            }
        }
        else {
            throw new IllegalStateException("fail to create adaptive instance: "   createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
        }
    }

    return (T) instance;
}

该方法很简单,就是从缓存中获取适配类对象,未获取到就调用createAdaptiveExtension方法加载适配类并通过反射创建对象:

代码语言:javascript复制
private T createAdaptiveExtension() {
    try {
    	// 这里又注入了些东西,先略过
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can not create adaptive extenstion "   type   ", cause: "   e.getMessage(), e);
    }
}

调用getAdaptiveExtensionClass加载适配类:

代码语言:javascript复制
private Class<?> getAdaptiveExtensionClass() {
	// 这里刚刚分析过了,从配置文件中加载配置类
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

cachedAdaptiveClass这个变量应该还没忘,在loadFile里赋值的,即我们自定义的适配扩展类,若没有则调用createAdaptiveExtensionClass动态创建:

代码语言:javascript复制
private Class<?> createAdaptiveExtensionClass() {
	// 生成适配类的Java代码,主要实现标注了@Adaptive的方法逻辑
    String code = createAdaptiveExtensionClassCode();
    ClassLoader classLoader = findClassLoader();
    // 调用compiler编译
    com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    return compiler.compile(code, classLoader);
}

该方法就是生成适配类的字节码,你一定好奇适配类的代码是怎样的,只需要打断点就可以看到了,这里我们以Protocol类的适配类为例:

代码语言:javascript复制
import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url("   url.toString()   ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }

    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url("   url.toString()   ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
}

后面会讲到Protocol扩展类都是通过export方法暴露服务,refer方法引用服务,而这两个方法在接口中都标注了@Adaptive注解,所以Dubbo为其生成了动态的适配类(这个和Java的动态代理的原理有点像,感兴趣的可以阅读我之前的文章《设计之禅——深入剖析代理模式》)。同时我们看到这两个方法中都通过getExtension方法去获取指定的扩展类的实例(这个扩展类名称来自于Invoker(后面会讲)中的url,因为dubbo是基于url驱动的,所有的配置都在url中)。

这就是Dubbo强大的自适应扩展机制的实现原理,我们可以将其运用到我们的项目中去,这就是看源码的好处。不过还有个问题,刚刚在createAdaptiveExtensionClass方法中你一定疑惑compiler是什么,它也是调用的getAdaptiveExtension获取适配类,这不就进入了死循环么?

当然不会,首先我们可以去配置文件看看Compiler的扩展类都有哪些:

代码语言:javascript复制
adaptive=com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler
jdk=com.alibaba.dubbo.common.compiler.support.JdkCompiler
javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler

有一个AdaptiveCompiler类,从名字上我们就能猜到它是一个自定义的适配类了,然后在其类上可以看到@Adaptive注解验证我们的猜想,那么上文也说了在loadFile方法中会将该类赋值给cachedAdaptiveClass变量缓存,然后在createAdaptiveExtension -> getAdaptiveExtensionClass方法中获取并实例化对象,所以并不会死循环,那么在该类中做了什么呢?

代码语言:javascript复制
public class AdaptiveCompiler implements Compiler {

	// 这个是在哪赋值的?
    private static volatile String DEFAULT_COMPILER;

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

    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) {
        	// 根据 DEFAULT_COMPILER 名称获取
            compiler = loader.getExtension(name);
        } else {
			// 获取@SPI注解值指定的默认扩展
            compiler = loader.getDefaultExtension();
        }
        return compiler.compile(code, classLoader);
    }

}

该适配类会从两个地方获取扩展类,先来看看getDefaultExtension

代码语言:javascript复制
public T getDefaultExtension() {
   		getExtensionClasses();
       if(null == cachedDefaultName || cachedDefaultName.length() == 0
               || "true".equals(cachedDefaultName)) {
           return null;
       }
       return getExtension(cachedDefaultName);
}

cachedDefaultName 这个不陌生吧,在loadExtensionClasses方法中赋值的,其值为@SPI的值,这里就是javassist。再看另外一条分支,是通过DEFAULT_COMPILER的值去获取的,这个变量提供了一个setter方法,点过去我们可以看到是在ApplicationConfig类中的setCompiler方法调用的,因为该类是配置类实例,也就是说可以通过dubbo:application的compiler参数来配置编译器类型,查看文档,也确实有这个配置参数。所以看源码能让我们了解平时项目中配置各个参数的意义,从而有针对的选择和配置适当的参数,而不是一味的照搬文档就完事。

三、Dubbo IOC

在上文中我们看到injectExtension这样一个方法,它是做什么的呢?接下来就详细分析它的作用和实现。

代码语言:javascript复制
private T injectExtension(T instance) {
    try {
        if (objectFactory != null) {
            for (Method method : instance.getClass().getMethods()) {
                if (method.getName().startsWith("set")
                        && method.getParameterTypes().length == 1
                        && Modifier.isPublic(method.getModifiers())) {
                    Class<?> pt = method.getParameterTypes()[0];
                    try {
                        String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase()   method.getName().substring(4) : "";
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) {
                        logger.error("fail to inject via method "   method.getName()
                                  " of interface "   type.getName()   ": "   e.getMessage(), e);
                    }
                }
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}

这个方法就是Dubbo依赖注入的实现,从上面代码中我们可以看出该方法是通过setter方法注入依赖扩展的(因为有些扩展点是需要依赖其它扩展点的,所以单单初始化当前扩展点还不行,还需要注入依赖的扩展):首先通过反射拿到参数的类型,然后从setter方法名中获取到扩展点的名称,最后从objectFactory中获取依赖的扩展实例并通过反射注入。objectFactory这个参数还记得是什么,怎么初始化赋值的么?这里具体的实例对象是(不清楚怎么来的忘记了就往上翻翻)AdaptiveExtensionFactory适配类类的对象,首先看看该类的初始化:

代码语言:javascript复制
private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory() {
    ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
    List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
    for (String name : loader.getSupportedExtensions()) {
        list.add(loader.getExtension(name));
    }
    factories = Collections.unmodifiableList(list);
}

这里不难理解,初始化时将所有的ExtensionFactory的扩展对象缓存到factories对象,然后在getExtension中循环,别分别调用它们的getExtension方法:

代码语言:javascript复制
public <T> T getExtension(Class<T> type, String name) {
	// SpiExtensionFactory和SpringExtensionFactory
    for (ExtensionFactory factory : factories) {
        T extension = factory.getExtension(type, name);
        if (extension != null) {
            return extension;
        }
    }
    return null;
}

ExtensionFactory的扩展配置文件中只有三个类:

代码语言:javascript复制
adaptive=com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory
spring=com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory

除开上面的适配类,下面分别看看spi和spring做了哪些事:

代码语言:javascript复制
public class SpiExtensionFactory implements ExtensionFactory {

    public <T> T getExtension(Class<T> type, String name) {
    	// 判断是否为@SPI标注的扩展接口
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (loader.getSupportedExtensions().size() > 0) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

}

该类主要是获取标了@SPI的扩展接口的适配类,其中getSupportedExtensions就是加载所有的扩展类。想一想ExtensionFactory本身就是被@SPI标注的,会在这里再次返回适配类么?

再来看SpringExtensionFactory类:

代码语言:javascript复制
public class SpringExtensionFactory implements ExtensionFactory {
    
    private static final Set<ApplicationContext> contexts = new ConcurrentHashSet<ApplicationContext>();
    
    public static void addApplicationContext(ApplicationContext context) {
        contexts.add(context);
    }

    public static void removeApplicationContext(ApplicationContext context) {
        contexts.remove(context);
    }

    @SuppressWarnings("unchecked")
    public <T> T getExtension(Class<T> type, String name) {
        for (ApplicationContext context : contexts) {
            if (context.containsBean(name)) {
                Object bean = context.getBean(name);
                if (type.isInstance(bean)) {
                    return (T) bean;
                }
            }
        }
        return null;
    }

}

这个类也很简单,就是从Spring IOC容器中返回对应的扩展对象。

以上就是Dubbo IOC的实现原理,非常简单,但也很重要,我们通过idea快捷键可以看到只有以下两处调用:

一个是createExtension创建扩展类实例时:

代码语言:javascript复制
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && wrapperClasses.size() > 0) {
    for (Class<?> wrapperClass : wrapperClasses) {
        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
    }
}

另一个是createAdaptiveExtension创建适配类实例的时候:

代码语言:javascript复制
private T createAdaptiveExtension() {
    try {
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can not create adaptive extenstion "   type   ", cause: "   e.getMessage(), e);
    }
}

记住这两个地方,后面再深入服务注册调用时,时常会联系到这里。

总结

今天这部分源码我们可以从中看到Dubbo是如何是实现对扩展开放,对修改关闭以及如何优雅地使用设计模式的,今后在实际的Dubbo的使用中,也可以轻易的进行自定义扩展开发。最后我们可以想一想,之前的项目是否可以运用今天的所学进行重构呢?

0 人点赞