Dubbo扩展点加载机制 - ExtensionLoader
如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK标准的ScriptEngine,通过getName();获取脚本类型的名称,但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因。
约定:
在扩展类的jar包内,放置扩展点配置文件:META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。
(注意:这里的配置文件是放在你自己的jar包内,不是dubbo本身的jar包内,Dubbo会全ClassPath扫描所有jar包内同名的这个文件,然后进行合并)
扩展Dubbo的协议示例:
在协议的实现jar包内放置文本文件:META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol,内容为:
| |--AdaptiveExtensionFactory #稍后解释 | |--SpiExtensionFactory #稍后解释 |--support | |--ActivateComparator |--Activate #自动激活加载扩展的注解 |--Adaptive #自适应扩展点的注解 |--ExtensionFactory #扩展点对象生成工厂接口 |--ExtensionLoader #扩展点加载器,扩展点的查找,校验,加载等核心逻辑的实现类 |--SPI #扩展点注解
ExtensionLoader没有提供public的构造方法,但是提供了一个public static的getExtensionLoader,这个方法就是获取ExtensionLoader实例的工厂方法。其public成员方法中有三个比较重要的方法:
getActivateExtension :根据条件获取当前扩展可自动激活的实现 getExtension : 根据名称获取当前扩展的指定实现 getAdaptiveExtension : 获取当前扩展的自适应实现这三个方法将会是我们重点关注的方法;* 每一个ExtensionLoader实例仅负责加载特定SPI扩展的实现*。因此想要获取某个扩展的实现,首先要获取到该扩展对应的ExtensionLoader实例,下面我们就来看一下获取ExtensionLoader实例的工厂方法getExtensionLoader:
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)) { // 只接受使用@SPI注解注释的接口类型 throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!"); } // 先从静态缓存中获取对应的ExtensionLoader实例 ExtensionLoader T loader = (ExtensionLoader T ) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader T (type)); // 为Extension类型创建ExtensionLoader实例,并放入静态缓存 loader = (ExtensionLoader T ) EXTENSION_LOADERS.get(type); } return loader;
该方法需要一个Class类型的参数,该参数表示希望加载的扩展点类型,该参数必须是接口,且该接口必须被@SPI注解注释,否则拒绝处理。检查通过之后首先会检查ExtensionLoader缓存中是否已经存在该扩展对应的ExtensionLoader,如果有则直接返回,否则创建一个新的ExtensionLoader负责加载该扩展实现,同时将其缓存起来。可以看到对于每一个扩展,dubbo中只会有一个对应的ExtensionLoader实例。
接下来看下ExtensionLoader的私有构造函数:
// 如果扩展类型是ExtensionFactory,那么则设置为null // 这里通过getAdaptiveExtension方法获取一个运行时自适应的扩展类型(每个Extension只能有一个@Adaptive类型的实现,如果没有dubbo会动态生成一个类) objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
这里保存了对应的扩展类型,并且设置了一个额外的objectFactory属性,他是一个ExtensionFactory类型,ExtensionFactory主要用于加载扩展的实现:
同时ExtensionFactory也被@SPI注解注释,说明他也是一个扩展点,从前面com.alibaba.dubbo.common.extension包的结构图中可以看到,dubbo内部提供了两个实现类:SpiExtensionFactory 和 AdaptiveExtensionFactory,实际上还有一个SpringExtensionFactory,不同的实现可以已不同的方式来完成扩展点实现的加载,这块稍后再来学习。从ExtensionLoader的构造函数中可以看到,如果要加载的扩展点类型是ExtensionFactory是,object字段被设置为null。由于ExtensionLoader的使用范围有限(基本上局限在ExtensionLoader中),因此对他做了特殊对待:在需要使用ExtensionFactory的地方,都是通过对应的自适应实现来代替。
默认的ExtensionFactory实现中,AdaptiveExtensionFactotry被@Adaptive注解注释,也就是它就是ExtensionFactory对应的自适应扩展实现(每个扩展点最多只能有一个自适应实现,如果所有实现中没有被@Adaptive注释的,那么dubbo会动态生成一个自适应实现类),也就是说,所有对ExtensionFactory调用的地方,实际上调用的都是AdpativeExtensionFactory,那么我们看下他的实现代码:
public AdaptiveExtensionFactory() { ExtensionLoader ExtensionFactory loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class); List ExtensionFactory list = new ArrayList ExtensionFactory for (String name : loader.getSupportedExtensions()) { // 将所有ExtensionFactory实现保存起来 list.add(loader.getExtension(name)); } factories = Collections.unmodifiableList(list); } public T T getExtension(Class T type, String name) { // 依次遍历各个ExtensionFactory实现的getExtension方法,一旦获取到Extension即返回 // 如果遍历完所有的ExtensionFactory实现均无法找到Extension,则返回null for (ExtensionFactory factory : factories) { T extension = factory.getExtension(type, name); if (extension != null) { return extension; } } return null; }
看完代码大家都知道是怎么回事了,这货就相当于一个代理入口,他会遍历当前系统中所有的ExtensionFactory实现来获取指定的扩展实现,获取到扩展实现或遍历完所有的ExtensionFactory实现。这里调用了ExtensionLoader的getSupportedExtensions方法来获取ExtensionFactory的所有实现,又回到了ExtensionLoader类,下面我们就来分析ExtensionLoader的几个重要的实例方法。
方法调用流程
getExtension
- createExtension(name) #如果无缓存则创建 - getExtensionClasses().get(name) #获取name对应的扩展类型 - 实例化扩展类 - injectExtension(instance) # 扩展点注入 - instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)) #循环遍历所有wrapper实现,实例化wrapper并进行扩展点注入
- createAdaptiveExtension() #如果无缓存则创建 - getAdaptiveExtensionClass().newInstance() #获取AdaptiveExtensionClass - getExtensionClasses() # 加载当前扩展所有实现,看是否有实现被标注为@Adaptive - createAdaptiveExtensionClass() #如果没有实现被标注为@Adaptive,则动态创建一个Adaptive实现类 - createAdaptiveExtensionClassCode() #动态生成实现类java代码 - compiler.compile(code, classLoader) #动态编译java代码,加载类并实例化 - injectExtension(instance)
该方法有多个重载方法,不过最终都是调用了三个参数的那一个重载形式。其代码结构也相对剪短,就不需要在列出概要流程了。
getAdaptiveExtension
从前面ExtensionLoader的私有构造函数中可以看出,在选择ExtensionFactory的时候,并不是调用getExtension(name)来获取某个具体的实现类,而是调用getAdaptiveExtension来获取一个自适应的实现。那么首先我们就来分析一下getAdaptiveExtension这个方法的实现吧:
public T getAdaptiveExtension() { Object instance = cachedAdaptiveInstance.get(); // 首先判断是否已经有缓存的实例对象 if (instance == null) { if(createAdaptiveInstanceError == null) { synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null) { try { instance = createAdaptiveExtension(); // 没有缓存的实例,创建新的AdaptiveExtension实例 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;
首先检查缓存的adaptiveInstance是否存在,如果存在则直接使用,否则的话调用createAdaptiveExtension方法来创建新的adaptiveInstance并且缓存起来。也就是说对于某个扩展点,每次调用ExtensionLoader.getAdaptiveExtension获取到的都是同一个实例。
try { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); // 先获取AdaptiveExtensionClass,在获取其实例,最后进行注入处理 } catch (Exception e) { throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e); }
在createAdaptiveExtension方法中,首先通过getAdaptiveExtensionClass方法获取到最终的自适应实现类型,然后实例化一个自适应扩展实现的实例,最后进行扩展点注入操作。先看一个getAdaptiveExtensionClass方法的实现:
private Class ? getAdaptiveExtensionClass() { getExtensionClasses(); // 加载当前Extension的所有实现,如果有@Adaptive类型,则会赋值为cachedAdaptiveClass属性缓存起来 if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); // 没有找到@Adaptive类型实现,则动态创建一个AdaptiveExtensionClass
他只是简单的调用了getExtensionClasses方法,然后在判adaptiveCalss缓存是否被设置,如果被设置那么直接返回,否则调用createAdaptiveExntesionClass方法动态生成一个自适应实现,关于动态生成自适应实现类然后编译加载并且实例化的过程这里暂时不分析,留到后面在分析吧。这里我们看getExtensionClassses方法:
private Map String, Class ? getExtensionClasses() { Map String, Class ? classes = cachedClasses.get(); // 判断是否已经加载了当前Extension的所有实现类 if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null) { classes = loadExtensionClasses(); // 如果还没有加载Extension的实现,则进行扫描加载,完成后赋值给cachedClasses变量 cachedClasses.set(classes); } } } return classes;
在getExtensionClasses方法中,首先检查缓存的cachedClasses,如果没有再调用loadExtensionClasses方法来加载,加载完成之后就会进行缓存。也就是说对于每个扩展点,其实现的加载只会执行一次。我们看下loadExtensionClasses方法:
private Map String, Class ? loadExtensionClasses() { final SPI defaultAnnotation = type.getAnnotation(SPI.class); if(defaultAnnotation != null) { String value = defaultAnnotation.value(); // 解析当前Extension配置的默认实现名,赋值给cachedDefaultName属性 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;
从代码里面可以看到,在loadExtensionClasses中首先会检测扩展点在@SPI注解中配置的默认扩展实现的名称,并将其赋值给cachedDefaultName属性进行缓存,后面想要获取该扩展点的默认实现名称就可以直接通过访问cachedDefaultName字段来完成,比如getDefaultExtensionName方法就是这么实现的。从这里的代码中又可以看到,具体的扩展实现类型,是通过调用loadFile方法来加载,分别从一下三个地方加载:
META-INF/dubbo/internal/ META-INF/dubbo/ META-INF/services/那么这个loadFile方法则至关重要了,看看其源代码:
span private void loadFile(Map String, Class ? extensionClasses, String dir) { String fileName = dir + type.getName(); // 配置文件名称,扫描整个classpath try { // 先获取该路径下所有文件 Enumeration java.net.URL urls; ClassLoader classLoader = findClassLoader(); if (classLoader != null) { urls = classLoader.getResources(fileName); } else { urls = ClassLoader.getSystemResources(fileName); } if (urls != null) { // 遍历这些文件并进行处理 while (urls.hasMoreElements()) { java.net.URL url = urls.nextElement(); // 获取配置文件路径 try { BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8")); try { String line = null; while ((line = reader.readLine()) != null) { // 一行一行读取(一行一个配置) final int ci = line.indexOf(#); if (ci = 0) line = line.substring(0, ci); line = line.trim(); if (line.length() 0) { try { 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 ? 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."); } if (clazz.isAnnotationPresent(Adaptive.class)) { // 判断该实现类是否@Adaptive,是的话不会放入extensionClasses/cachedClasses缓存 if(cachedAdaptiveClass == null) { // 第一个赋值给cachedAdaptiveClass属性 cachedAdaptiveClass = clazz; } else if (! cachedAdaptiveClass.equals(clazz)) { // 只能有一个@Adaptive实现,出现第二个就报错了 throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName()); } } else { // 不是@Adaptive类型 try { clazz.getConstructor(type); // 判断是否Wrapper类型 Set Class ? wrappers = cachedWrapperClasses; if (wrappers == null) { cachedWrapperClasses = new ConcurrentHashSet Class ? (); wrappers = cachedWrapperClasses; } wrappers.add(clazz); //放入到Wrapper实现类缓存中 } catch (NoSuchMethodException e) { //不是Wrapper类型,普通实现类型 clazz.getConstructor(); if (name == null || name.length() == 0) { name = findAnnotationName(clazz); 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); // 看是否配置了多个name if (names != null names.length 0) { Activate activate = clazz.getAnnotation(Activate.class); // 是否@Activate类型 if (activate != null) { cachedActivates.put(names[0], activate);// 是则放入cachedActivates缓存 } // 遍历所有name for (String n : names) { if (! cachedNames.containsKey(clazz)) { cachedNames.put(clazz, n); // 放入Extension实现类与名称映射缓存,每个class只对应第一个名称有效 } Class ? c = extensionClasses.get(n); if (c == null) { extensionClasses.put(n, clazz); // 放入到extensionClasses缓存,多个name可能对应一个Class } else if (c != clazz) { // 存在重名 throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName()); } } } } } } } catch (Throwable t) { IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t); exceptions.put(line, e); } } } // end of while read lines } finally { reader.close(); } } catch (Throwable t) { logger.error("Exception when load extension class(interface: " + type + ", class file: " + url + ") in " + url, t); } } // end of while urls } } catch (Throwable t) { logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", t); } } /span
代码比较长,大概的事情呢就是解析配置文件,获取扩展点实现对应的名称和实现类,并进行分类处理和缓存。当loadFile方法执行完成之后,以下几个变量就会被附上值:
cachedAdaptiveClass : 当前Extension类型对应的AdaptiveExtension类型(只能一个) cachedWrapperClasses : 当前Extension类型对应的所有Wrapper实现类型(无顺序) cachedActivates : 当前Extension实现自动激活实现缓存(map,无序) cachedNames : 扩展点实现类对应的名称(如配置多个名称则值为第一个)当loadExtensionClasses方法执行完成之后,还有一下变量被赋值:
cachedDefaultName : 当前扩展点的默认实现名称当getExtensionClasses方法执行完成之后,除了上述变量被赋值之外,还有以下变量被赋值:
cachedClasses : 扩展点实现名称对应的实现类(一个实现类可能有多个名称)其实也就是说,在调用了getExtensionClasses方法之后,当前扩展点对应的实现类的一些信息就已经加载进来了并且被缓存了。后面的许多操作都可以直接通过这些缓存数据来进行处理了。
回到createAdaptiveExtension方法,他调用了getExtesionClasses方法加载扩展点实现信息完成之后,就可以直接通过判断cachedAdaptiveClass缓存字段是否被赋值盘确定当前扩展点是否有默认的AdaptiveExtension实现。如果没有,那么就调用createAdaptiveExtensionClass方法来动态生成一个。在dubbo的扩展点框架中大量的使用了缓存技术。
创建自适应扩展点实现类型和实例化就已经完成了,下面就来看下扩展点自动注入的实现injectExtension:
if (objectFactory != null) { for (Method method : instance.getClass().getMethods()) { if (method.getName().startsWith("set") method.getParameterTypes().length == 1 Modifier.isPublic(method.getModifiers())) {// 处理所有set方法 Class ? pt = method.getParameterTypes()[0];// 获取set方法参数类型 try { // 获取setter对应的property名称 String property = method.getName().length() 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : ""; Object object = objectFactory.getExtension(pt, property); // 根据类型,名称信息从ExtensionFactory获取 if (object != null) { // 如果不为空,说set方法的参数是扩展点类型,那么进行注入 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;
这里可以看到,扩展点自动注入的一句就是根据setter方法对应的参数类型和property名称从ExtensionFactory中查询,如果有返回扩展点实例,那么就进行注入操作。到这里getAdaptiveExtension方法就分析完毕了。
getExtension
这个方法的主要作用是用来获取ExtensionLoader实例代表的扩展的指定实现。已扩展实现的名字作为参数,结合前面学习getAdaptiveExtension的代码,我们可以推测,这方法中也使用了在调用getExtensionClasses方法的时候收集并缓存的数据,其中涉及到名字和具体实现类型对应关系的缓存属性是cachedClasses。具体是是否如我们猜想的那样呢,学习一下相关代码就知道了:
if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null"); if ("true".equals(name)) { // 判断是否是获取默认实现 return getDefaultExtension(); } Holder Object holder = cachedInstances.get(name);// 缓存 if (holder == null) { cachedInstances.putIfAbsent(name, new Holder Object holder = cachedInstances.get(name); } Object instance = holder.get(); if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { instance = createExtension(name);// 没有缓存实例则创建 holder.set(instance);// 缓存起来 } } } return (T) instance;
private T createExtension(String name) { Class ? clazz = getExtensionClasses().get(name); // getExtensionClass内部使用cachedClasses缓存 if (clazz == null) { throw findException(name); } try { T instance = (T) EXTENSION_INSTANCES.get(clazz); // 从已创建Extension实例缓存中获取 if (instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } injectExtension(instance); // 属性注入 // Wrapper类型进行包装,层层包裹 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方法来获取当前扩展的所有实现,而getExtensionClassse方法会在第一次被调用的时候将结果缓存到cachedClasses变量中,后面的调用就直接从缓存变量中获取了。这里还可以看到一个缓存EXTENSION_INSTANCES,这个缓存是ExtensionLoader的静态成员,也就是全局缓存,存放着所有的扩展点实现类型与其对应的已经实例化的实例对象(是所有扩展点,不是某一个扩展点),也就是说所有的扩展点实现在dubbo中最多都只会有一个实例。
拿到扩展点实现类型对应的实例之后,调用了injectExtension方法对该实例进行扩展点注入,紧接着就是遍历该扩展点接口的所有Wrapper来对真正的扩展点实例进行Wrap操作,都是对通过将上一次的结果作为下一个Wrapper的构造函数参数传递进去实例化一个Wrapper对象,最后总返回回去的是Wrapper类型的实例而不是具体实现类的实例。
这里或许有一个疑问: 从代码中看,不论instance是否存在于EXTENSION_INSTANCE,都会进行扩展点注入和Wrap操作。那么如果对于同一个扩展点,调用了两次createExtension方法的话,那不就进行了两次Wrap操作么?
如果外部能够直接调用createExtension方法,那么确实可能出现这个问题。但是由于createExtension方法是private的,因此外部无法直接调用。而在ExtensionLoader类中调用它的getExtension方法(只有它这一处调用),内部自己做了缓存(cachedInstances),因此当getExtension方法内部调用了一次createExtension方法之后,后面对getExtension方法执行同样的调用时,会直接使用cachedInstances缓存而不会再去调用createExtension方法了。
getActivateExtension
getActivateExtension方法主要获取当前扩展的所有可自动激活的实现。可根据入参(values)调整指定实现的顺序,在这个方法里面也使用到getExtensionClasses方法中收集的缓存数据。
public List T getActivateExtension(URL url, String[] values, String group) { List T exts = new ArrayList T List String names = values == null ? new ArrayList String (0) : Arrays.asList(values); // 解析配置要使用的名称 // 如果未配置"-default",则加载所有Activates扩展(names指定的扩展) if (! names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) { getExtensionClasses(); // 加载当前Extension所有实现,会获取到当前Extension中所有@Active实现,赋值给cachedActivates变量 for (Map.Entry String, Activate entry : cachedActivates.entrySet()) { // 遍历当前扩展所有的@Activate扩展 String name = entry.getKey(); Activate activate = entry.getValue(); if (isMatchGroup(group, activate.group())) { // 判断group是否满足,group为null则直接返回true T ext = getExtension(name); // 获取扩展示例 // 排除names指定的扩展;并且如果names中没有指定移除该扩展(-name),且当前url匹配结果显示可激活才进行使用 if (! names.contains(name) ! names.contains(Constants.REMOVE_VALUE_PREFIX + name) isActive(activate, url)) { exts.add(ext); } } } Collections.sort(exts, ActivateComparator.COMPARATOR); // 默认排序 } // 对names指定的扩展进行专门的处理 List T usrs = new ArrayList T for (int i = 0; i names.size(); i ++) { // 遍历names指定的扩展名 String name = names.get(i); if (! name.startsWith(Constants.REMOVE_VALUE_PREFIX) ! names.contains(Constants.REMOVE_VALUE_PREFIX + name)) { // 未设置移除该扩展 if (Constants.DEFAULT_KEY.equals(name)) { // default表示上面已经加载并且排序的exts,将排在default之前的Activate扩展放置到default组之前,例如:ext1,default,ext2 if (usrs.size() 0) { // 如果此时user不为空,则user中存放的是配置在default之前的Activate扩展 exts.addAll(0, usrs); // 注意index是0,放在default前面 usrs.clear(); // 放到default之前,然后清空 } } else { T ext = getExtension(name); usrs.add(ext); } } } if (usrs.size() 0) { // 这里留下的都是配置在default之后的 exts.addAll(usrs); // 添加到default排序之后 } return exts;
每个ExtensionLoader实例只负责加载一个特定扩展点实现 每个扩展点对应最多只有一个ExtensionLoader实例 对于每个扩展点实现,最多只会有一个实例 一个扩展点实现可以对应多个名称(逗号分隔) 对于需要等到运行时才能决定使用哪一个具体实现的扩展点,应获取其自使用扩展点实现(AdaptiveExtension) @Adaptive注解要么注释在扩展点@SPI的方法上,要么注释在其实现类的类定义上 如果@Adaptive注解注释在@SPI接口的方法上,那么原则上该接口所有方法都应该加@Adaptive注解(自动生成的实现中默认为注解的方法抛异常) 每个扩展点最多只能有一个被AdaptiveExtension 每个扩展点可以有多个可自动激活的扩展点实现(使用@Activate注解) 由于每个扩展点实现最多只有一个实例,因此扩展点实现应保证线程安全 如果扩展点有多个Wrapper,那么最终其执行的顺序不确定(内部使用ConcurrentHashSet存储)
TODO:
学习一下动态生成AdaptiveExtension类的实现过程官方文档描述动态生成的AdaptiveExtension代码如下:
public class 扩展点接口名 $Adpative implements 扩展点接口 { public 有@Adaptive注解的接口方法 ( 方法参数 ) { if(是否有URL类型方法参数?) 使用该URL参数 else if(是否有方法类型上有URL属性) 使用该URL属性 # else 在加载扩展点生成自适应扩展点类时抛异常,即加载扩展点失败! if(获取的URL == null) { throw new IllegalArgumentException("url == null"); } 根据@Adaptive注解上声明的Key的顺序,从URL获致Value,作为实际扩展点名。 如URL没有Value,则使用缺省扩展点实现。如没有扩展点, throw new IllegalStateException("Fail to get extension"); 在扩展点实现调用该方法,并返回结果。 } public 有@Adaptive注解的接口方法 ( 方法参数 ) { throw new UnsupportedOperationException("is not adaptive method!"); }
先在URL上找@Adaptive注解指定的Extension名; 如果不设置则缺省使用Extension接口类名的点分隔小写字串(即对于Extension接口com.alibaba.dubbo.xxx.YyyInvokerWrapper的缺省值为String[] {“yyy.invoker.wrapper”})。 使用默认实现(@SPI指定),如果没有设定缺省扩展,则方法调用会抛出IllegalStateException。 原文链接:[http://wely.iteye.com/blog/2304718]
Dubbo3 源码解读-宋小生-6:Dubbo的SPI扩展机制之普通扩展对象的创建与Wrapper机制的源码解析 Dubbo3 已经全面取代 HSF2 成为阿里的下一代服务框架,2022 双十一基于 Dubbo3 首次实现了关键业务不停推、不降级的全面用户体验提升,从技术上,大幅提高研发与运维效率的同时地址推送等关键资源利用率提升超 40%,基于三位一体的开源中间件体系打造了阿里在云上的单元化最佳实践和统一标准,同时将规模化实践经验与技术创新贡献开源社区,极大的推动了开源技术与标准的发展。 本文是
Dubbo3 源码解读-宋小生-7:Dubbo的SPI扩展机制之自动激活扩展Activate源码解析 Dubbo3 已经全面取代 HSF2 成为阿里的下一代服务框架,2022 双十一基于 Dubbo3 首次实现了关键业务不停推、不降级的全面用户体验提升,从技术上,大幅提高研发与运维效率的同时地址推送等关键资源利用率提升超 40%,基于三位一体的开源中间件体系打造了阿里在云上的单元化最佳实践和统一标准,同时将规模化实践经验与技术创新贡献开源社区,极大的推动了开源技术与标准的发展。 本文是
Dubbo3 源码解读-宋小生-4:Dubbo的扩展机制 Dubbo3 已经全面取代 HSF2 成为阿里的下一代服务框架,2022 双 11 基于 Dubbo3 首次实现了关键业务不停推、不降级的全面用户体验提升,从技术上,大幅提高研发与运维效率的同时地址推送等关键资源利用率提升超 40%,基于三位一体的开源中间件体系打造了阿里在云上的单元化最佳实践和统一标准,同时将规模化实践经验与技术创新贡献开源社区,极大的推动了开源技术与标准的发展。 本文是 Du
这个Dubbo注册中心扩展,有点意思! 迁移注册中心的方案大致有两种: 方案一:使用Dubbo提供的多注册中心能力,Provider先进行双注册,Consumer逐步迁移消费新注册中心,最后下线老注册中心。该方案的缺点是修改时有上下游依赖关系。 方案二:使用一个同步工具把老注册中心的数据同步到新注册中心,Consumer逐步迁移到新注册中心,最后下线老注册中心。同步工具有开源的Nacos-sync,我之前的文章《zookeeper到nacos的迁移实践》就提到了这个方案。这个方案的缺点是架构变得复杂,需要解决同步数据的顺序性、一致性、同步组件的高可用等问题。
Dubbo系列讲解之扩展点实现原理分析【2万字分享】 Apache Dubbo 是一款微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力。这意味着,使用 Dubbo 开发的微服务,将具备相互之间的远程发现与通信能力, 同时利用 Dubbo 提供的丰富服务治理能力,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。同时 Dubbo 是高度可扩展的,用户几乎可以在任意功能点去定制自己的实现,以改变框架的默认行为来满足自己的业务需求
dubbo之Protocol获取适应扩展过程分析 这篇文章尝试分析Protocol的getAdaptiveExtension过程,和dubbo之ExtensionFactory获取适应扩展过程分析不一样的过程在于ExtensionFactory的扩展类是定义的AdaptiveExtensionFactory,而Protocol的适应扩展是动态生成的。
相关文章
- ABP源码分析十五:ABP中的实用扩展方法
- 基于 Wasm 和 ORAS 简化扩展服务网格功能
- KubeVela 正式开源:一个高可扩展的云原生应用平台与核心引擎
- 【NLP】条件随机场知识扩展延伸(五)
- .NET Core的文件系统[5]:扩展文件系统构建一个简易版“云盘”
- Dubbo学习(四) dubbo的特点,8种通信协议之对比
- Dubbo学习(六) dubbo 架构图 以及调用过程
- SAP CRM WebClient UI搜索结果界面的扩展字段显示
- 基于 Kyma 的企业级云原生应用的扩展案例分享
- PostgreSQL 内核扩展之 - 管理十亿级3D扫描数据(基于Lidar产生的point cloud数据)
- 枚举实现接口——模拟可扩展的枚举
- 【Groovy】Gradle 构建工具 ( 自动下载并配置构建环境 | 提供 API 扩展与开发工具集成 | 内置 Maven 和 Ivy 依赖管理 | 使用 Groovy 编写构建脚本 )
- 002-多线程-JUC线程池-ThreadPoolExecutor运转机制详解,线程池的扩展beforeExcute,afterExcute,terminated
- Dubbo报错RpcException: Forbid consumer access service from registry use dubbo version , Please check registry access list (whitelist/blacklist).)
- cassandra mongodb选择——cassandra:分布式扩展好,写性能强,以及可以预料的查询;mongodb:非事务,支持复杂查询,但是不适合报表