Dubbo—SPI及自适应扩展原理,学习java工程师

// 如果是将其缓存到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(cl
【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】

浏览器打开:qq.cn.hn/FTf 免费领取

azz);
// 由于@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:这里需要留意cachedAdaptiveClass和cachedWrapperClasses两个变量的赋值,后面会用到。)

二、自适应扩展机制

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

  • 标注在类上,表明该类为自定义的适配类

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

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

objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());

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

private final Holder cachedAdaptiveInstance = new Holder();
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方法加载适配类并通过反射创建对象:

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加载适配类:

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

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

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类的适配类为例:

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的扩展类都有哪些:

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方法中获取并实例化对象,所以并不会死循环,那么在该类中做了什么呢?

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 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:

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这样一个方法,它是做什么的呢?接下来就详细分析它的作用和实现。

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适配类类的对象,首先看看该类的初始化:

private final List factories;
public AdaptiveExtensionFactory() {
ExtensionLoader loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List list = new ArrayList();
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}

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

public T getExtension(Class type, String name) {
// SpiExtensionFactory和SpringExtensionFactory
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}

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

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做了哪些事:

public class SpiExtensionFactory implements ExtensionFactory {

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

}

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

public class SpringExtensionFactory implements ExtensionFactory {

private static final Set contexts = new ConcurrentHashSet();

public static void addApplicationContext(ApplicationContext context) {
contexts.add(context);
}

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

@SuppressWarnings(“unchecked”)
public T getExtension(Class 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创建扩展类实例时:

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创建适配类实例的时候:

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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值