Dubbo中的ExtensionLoader扩展(二)
dubbo中自己实现了不同于java的SPI插件化机制,使得Dubbo可以在对多个指定的目录中加载扩展实现,同时与Java SPI不同的是可以实现按需加载。
Dubbo的扩展SPI有如下特点:
1. 单例,对于某个类型扩展,只会有一个ExtensionLoader;
2. 延迟加载,可以一次只获取想要的扩展点,一次获取想要的扩展点实现;
3. 对于扩展点的Ioc和Aop,就是一个扩展可以注入到另一个扩展中,也可以对一个扩展做wrap包装实现aop的功能;
4. 对于扩展点的调用,真正调用的时候才能确认具体使用的是那个实现。
源码实现
在Dubbo加载扩展点会代码示例如下,具体实现在ExtensionLoader中。接下来会详细介绍。
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("dubbo");
getExtensionLoader方法中先从缓存中拿ExtensionLoader,如果没有就new一个,new的过程如下:
private ExtensionLoader(Class<?> type) { this.type = type; //也是利用扩展实现,后面会看到在注入其他扩展点或bean到当前扩展时使用 objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }
objectFactory就是为了获取其他扩展点而设计的,其本身也是使用ExtensionLoader来获取。具体实现看下面的TODO1
1)ExtensionLoader#getAdaptiveExtension
DCL初始化一个默认的自适应扩展实现,如果没有则创建一个createAdaptiveExtension。
2)ExtensionLoader#createAdaptiveExtension
private T createAdaptiveExtension() { try { /** * 1. getAdaptiveExtensionClass:获取适配器类; * 2. injectExtension:为适配器类的setter方法插入其他扩展点或实现bean。 */ return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e); } }
接下来讲解一下如何获取到扩展点的,TODO1 最后再详细解释如何在扩展点上注入其他的扩展点
3)ExtensionLoader#getAdaptiveExtensionClass
private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
这边会类加载所有的实现class,但是只会实例化一个cachedAdaptiveClass, TODO2 如果找不到,则dubbo通过代码生成一个自适应了扩展实现。
4)ExtensionLoader#getExtensionClasses
初始化该类型下所有扩展点实现class,并缓存在cachedClasses中
5) ExtensionLoader # loadExtensionClasses
// synchronized in getExtensionClasses private Map<String, Class<?>> loadExtensionClasses() { // SPI注解中会指定一个默认的实现 列如 interface Cluster 上 @SPI(FailoverCluster.NAME) // 则cachedDefaultName = FailoverCluster.NAME cacheDefaultExtensionName(); // 将类型中的所有实现存放在extensionClasses中 Map<String, Class<?>> extensionClasses = new HashMap<>(); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName()); //META-INF/dubbo/internal/ loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName()); // //META-INF/dubbo/ loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName()); // //META-INF/services/ loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); return extensionClasses; }
6 ExtensionLoader#loadDirectory -> ExtensionLoader#loadResource -> ExtensionLoader#loadClass
主要是找到加载器,然后读取文件,忽略注释,通过class.forName来类加载具体实现。最后保存在extensionClasses。其中自适应的实现和包装类的实现class,单独赋值。
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { //必须是该扩展点的实现 if (!type.isAssignableFrom(clazz)) { throw new IllegalStateException("Error occurred when loading extension class (interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + " is not subtype of interface."); } //这里判断是否有自定义的适配器类,如果有,后面获取适配器的时候,就可以直接用这个创建返回,不用dubbo动态创建 if (clazz.isAnnotationPresent(Adaptive.class)) { cacheAdaptiveClass(clazz); } else if (isWrapperClass(clazz)) { // 包装类会有一个参数为type的构造器 cacheWrapperClass(clazz); } else { //处理不是包裹类的情况 且 又不带有Adaptive注解 clazz.getConstructor(); if (StringUtils.isEmpty(name)) { // 计算出实现类的name 列如 // clazz = org.apache.dubbo.common.compiler.support.JdkCompiler.class // type = org.apache.dubbo.common.compiler.Compiler.class // 则 name = JdkCompiler - Compiler = jdk name = findAnnotationName(clazz); if (name.length() == 0) { throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL); } } String[] names = NAME_SEPARATOR.split(name); if (ArrayUtils.isNotEmpty(names)) { //判断下Activate注解,后面讲,这里只需要知道,会缓存Activate注解的实现 cacheActivateClass(clazz, names[0]); for (String n : names) { cacheName(clazz, n); //缓存扩展实现 saveInExtensionClass(extensionClasses, clazz, name); } } } }
这边不得不强调一下,关于Adaptive注解。这个注解可以修饰具体的扩展实现类,也可以修饰具体扩展的实现类中的方法。
当修饰的是类的时候,将作为默认的扩展点,如果是修饰SPI的方法的时候,将会生成类的Methed$Adaptive中重新该方法,即上面的TODO2的内容。
另外还缓存了Activate类,关于Activate类将会后续介绍 TODO3
整个ExtensionLoader的流程相对是比较简单的,无非就是使用到@SPI注解的值或者是@Adaptive自定义的扩展。接下来具体讲解下上面流程中提到的TODO。
TODO1
实现IOC,一个扩展中初始化另一个扩展, ExtensionLoader#injectExtension
/** 插入该扩展需要其他扩展或bean */ private T injectExtension(T instance) { try { if (objectFactory != null) { // 默认objectFactory = AdaptiveExtensionFactory for (Method method : instance.getClass().getMethods()) { if (isSetter(method)) { /** * 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 { // for instance: setVersion, return "version" String property = getSetterProperty(method); // 根据参数类型class, 以及方法名中获取的property,通过具体的ExtensionFactory来获取到具体的扩展点 Object object = objectFactory.getExtension(pt, property); if (object != null) { // 将扩展实现作为setter方法的参数,来实现注入 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; }
其中AdaptiveExtensionFactory实际上就是加载所有的非Adaptive的扩展点,然后将其实例保存在List<ExtensionFactory> factories中,当需要获取指定name和type的Extension上的时候,实际上就是遍历所有的ExtensionFactory,直到加载到对应的扩展点。列如需要注入的扩展,即setter方法的参数类型也是注解了SPI,那么就会默认使用到SPIExtensionFactory,即自适应加载。
@Adaptive public class AdaptiveExtensionFactory implements ExtensionFactory { 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()) { // 对所有的ExtensionFactory扩展点 实例化, 实际上就一个SpiExtensionFactory list.add(loader.getExtension(name)); } factories = Collections.unmodifiableList(list); } @Override public <T> T getExtension(Class<T> type, String name) { for (ExtensionFactory factory : factories) { T extension = factory.getExtension(type, name); if (extension != null) { return extension; } } return null; }
TODO2
如果所有的SPI扩展实现都没有找到@Adaptive的扩展,那么dubbo就会生成自适应扩展的代码。根据SPI接口会重写@Adaptive注解的方法,方法参数一定是能够获取到URL的。如果在URL获取不到扩展名,则默认取SPI注解指定的值,然后用这个扩展名,通过ExtensionLoad加载对应的扩展点。最后通过编译器来编译code,加载到$Adaptive结尾的class,编译器也有多种实现,也是通过ExtensionLoad加载
/** Dubbo生成适配类 */ 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); }
这边我们做一个简单的测试,看一下自动生成自适应代码是什么样的。
public interface Compiler { /** * Compile java source code. * * @param code Java source code * @param classLoader classloader * @return Compiled class */ Class<?> compile(String code, ClassLoader classLoader); @Adaptive({"p1", "p2"}) Validator getValidator(URL url); } public static void main(String[] args) { String c = new AdaptiveClassCodeGenerator(org.apache.dubbo.common.extension.ExtensionLoader.Compiler.class, "jdk").generate(); System.out.println(c); }
这个测试做的事情就是加载一个org.apache.dubbo.common.extension.ExtensionLoader.Compiler.class的实现,但是由于没有找到任何的扩展点,所以就采用自动生成code,编译的方式。
其结果如下:
package org.apache.dubbo.common.extension; import org.apache.dubbo.common.extension.ExtensionLoader; public class Compiler$Adaptive implements org.apache.dubbo.common.extension.ExtensionLoader.Compiler { public java.lang.Class compile(java.lang.String arg0, java.lang.ClassLoader arg1) { throw new UnsupportedOperationException("The method public abstract java.lang.Class org.apache.dubbo.common.extension.ExtensionLoader$Compiler.compile(java.lang.String,java.lang.ClassLoader) of interface org.apache.dubbo.common.extension.ExtensionLoader$Compiler is not adaptive method!"); } public javax.xml.validation.Validator getValidator(org.apache.dubbo.common.URL arg0) { if (arg0 == null) throw new IllegalArgumentException("url == null"); org.apache.dubbo.common.URL url = arg0; String extName = url.getParameter("p1", url.getParameter("p2", "jdk")); if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ExtensionLoader$Compiler) name from url (" + url.toString() + ") use keys([p1, p2])"); org.apache.dubbo.common.extension.ExtensionLoader$Compiler extension = (org.apache.dubbo.common.extension.ExtensionLoader$Compiler) ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ExtensionLoader$Compiler.class).getExtension(extName); return extension.getValidator(arg0); } }
其特点如下:
1) 生成一个$Adaptive后缀的class
2) 如果没有@Adaptive注解的方法,则throw一个UnsupportedOperationException
3)对于使用的Adaptive注解的方法,其参数列表一定需要一个URL类型的参数,根据上面的配置,从URL中获取P1是值,如果获取不到则获取P2的值,如果再获取不到,则使用默认的值
4)根据第三步获取的值,从ExtensionLoader加载指定的扩展点实现。
TODO3
先来看一下@Activate注解
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Activate { /** * Group过滤条件。 * <br /> * 包含{@link ExtensionLoader#getActivateExtension}的group参数给的值,则返回扩展。 * <br /> * 如没有Group设置,则不过滤。 */ String[] group() default {}; /** * Key过滤条件。包含{@link ExtensionLoader#getActivateExtension}的URL的参数Key中有,则返回扩展。 * <p /> * 示例:<br/> * 注解的值 <code>@Activate("cache,validatioin")</code>, * 则{@link ExtensionLoader#getActivateExtension}的URL的参数有<code>cache</code>Key,或是<code>validatioin</code>则返回扩展。 * <br/> * 如没有设置,则不过滤。 */ String[] value() default {}; /** * 排序信息,可以不提供。 */ String[] before() default {}; /** * 排序信息,可以不提供。 */ String[] after() default {}; /** * 排序信息,可以不提供。 */ int order() default 0; }
group基本表示用在服务端还是消费端,value表示激活这个扩展点的条件,before、after、order用于排序。为了加深理解,我们拿一个UT例子看一下,
getActivateExtension方法基本都会传入url作为参数,用法是获取激活条件的所有扩展点实现类。
@Test public void testLoadDefaultActivateExtension() throws Exception { // test default URL url = URL.valueOf("test://localhost/test?ext=order1,default"); List<ActivateExt1> list = ExtensionLoader.getExtensionLoader(ActivateExt1.class) .getActivateExtension(url, "ext", "default_group"); Assertions.assertEquals(2, list.size()); Assertions.assertTrue(list.get(0).getClass() == OrderActivateExtImpl1.class); Assertions.assertTrue(list.get(1).getClass() == ActivateExt1Impl1.class); url = URL.valueOf("test://localhost/test?ext=default,order1"); list = ExtensionLoader.getExtensionLoader(ActivateExt1.class) .getActivateExtension(url, "ext", "default_group"); Assertions.assertEquals(2, list.size()); Assertions.assertTrue(list.get(0).getClass() == ActivateExt1Impl1.class); Assertions.assertTrue(list.get(1).getClass() == OrderActivateExtImpl1.class); } 扩展配置如下: group=org.apache.dubbo.common.extension.activate.impl.GroupActivateExtImpl value=org.apache.dubbo.common.extension.activate.impl.ValueActivateExtImpl order1=org.apache.dubbo.common.extension.activate.impl.OrderActivateExtImpl1 order2=org.apache.dubbo.common.extension.activate.impl.OrderActivateExtImpl2 old1=org.apache.dubbo.common.extension.activate.impl.OldActivateExt1Impl2 old2=org.apache.dubbo.common.extension.activate.impl.OldActivateExt1Impl3
接下里看一下ExtensionLoader#getActivateExtension
//获取满足激活条件的扩展实现 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); if (! names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) { //加载扩展 getExtensionClasses(); //上一步会缓存所有Active注解的实现类到cachedActivates for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) { String name = entry.getKey(); Activate activate = entry.getValue(); //匹配group,provider还是consumer
// activateGroup = activate扩展点注解activate配置的Group
// group URL中的group
if (isMatchGroup(group, activate.group())) { //获取扩展实现类 T ext = getExtension(name); //isActive匹配激活条件 if (! names.contains(name) && ! names.contains(Constants.REMOVE_VALUE_PREFIX + name) && isActive(activate, url)) { exts.add(ext); } } } //这里会重新排序,用到Active里面before、after和order Collections.sort(exts, ActivateComparator.COMPARATOR); } .... .... return exts; }
就是说根据URL,会把需要的扩展都会收集起来,并排好序。