前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Dubbo——SPI及自适应扩展原理

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

作者头像
夜勿语
发布2020-09-07 11:02:10
5610
发布2020-09-07 11:02:10
举报
文章被收录于专栏:Java升级之路

文章目录

  • 引言
  • 正文
    • 一、什么是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的使用中,也可以轻易的进行自定义扩展开发。最后我们可以想一想,之前的项目是否可以运用今天的所学进行重构呢?

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019/08/27 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 引言
  • 正文
    • 一、什么是SPI?
      • 1. Java SPI的实现
      • 2. Dubbo SPI实现原理
    • 二、自适应扩展机制
      • 三、Dubbo IOC
      • 总结
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档