文章会顺着这四个特点解析源码,在分析四个特性的源码前,会先分析一下 配置加载 。
大部分的框架的生命周期往往都必定会包括: 配置加载 和 实例使用 两个阶段。在 Dubbo SPI 中,加载的部分就是 注解 和 配置文件 。
这四个特性都有一个前置步骤,就是类和配置的加载。所以在分析特性前,需要先解析 前置的加载过程 。
加载某个接口的扩展,大概步骤大概如下:
Robot defaultExtension = ExtensionLoader.getExtensionLoader(Robot.class).getDefaultExtension(); // ExtensionLoader.java 以加载 DefaultExtension 为入口 public T getDefaultExtension() { getExtensionClasses(); // 加载 Robot.class 所有扩展类 从这里切入 if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) { return null; } return getExtension(cachedDefaultName); } // 做一次线程安全的双重检测加载 private Map<String, Class<?>> getExtensionClasses() { // 双重判定初始化 Map<String, Class<?>> classes = cachedClasses.get(); if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null) { // 加载可扩展的类 classes = loadExtensionClasses(); // 真正的加载入口 从这里切入 cachedClasses.set(classes); } } } return classes; } // 根据不同的策略(其实就是目录),把不同的目录下的配置 /XXXX/XXX/com.qpm.dubbo.test.spi.Robot 扫描并加载 private Map<String, Class<?>> loadExtensionClasses() { // 通过 SPI 注解获得默认的扩展名称 cacheDefaultExtensionName(); Map<String, Class<?>> extensionClasses = new HashMap<>(); // 通过三种不同的加载策略,加载三个目录下的所有扩展类 for (LoadingStrategy strategy : strategies) { // loadDirecotry 加载某个目录下的Robot配置 从这里切入 loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.excludedPackages()); loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.excludedPackages()); } return extensionClasses; } // private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst, String... excludedPackages) { String fileName = dir + type; // 配置文件的路径 try { Enumeration<java.net.URL> urls = null; // 找到 CLassLoader ClassLoader classLoader = findClassLoader(); // try to load from ExtensionLoader's ClassLoader first // fixme 策略可能会prefer使用 扩展类加载器,为什么? if (extensionLoaderClassLoaderFirst) { ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader(); if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) { urls = extensionLoaderClassLoader.getResources(fileName); } } if(urls == null || !urls.hasMoreElements()) { if (classLoader != null) { // 虽然我们寻找的只是一个文件,但不同的jar包可以也打包着相同的文件 // 例如 Mysql 的 jar 带有 Java.sql.Driver SPI 的配置 // Oracle 的 jar 也会有 Java.sqlDriver SPI 的配置 // 因此需要 ClassLoader 帮忙把所有 Java.sql.Driver 的URL都定位出来 urls = classLoader.getResources(fileName); } else { urls = ClassLoader.getSystemResources(fileName); } } if (urls != null) { while (urls.hasMoreElements()) { // urls 获得了多个 element java.net.URL resourceURL = urls.nextElement(); // 获得绝对路径 // 获得了绝对路径,根据绝对路径逐个进行加载。 从这里 切入 loadResource(extensionClasses, classLoader, resourceURL, excludedPackages); } } } catch (Throwable t) { logger.error("Exception occurred when loading extension class (interface: " + type + ", description file: " + fileName + ").", t); } } // 加载某个资源文件的配置 private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL, String... excludedPackages) { try { try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) { // 把文件读进来 String line; 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 { // 从一行数据中提取 K-V 值 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 && !isExcluded(line, excludedPackages)) { // 通过 classLoader 加载类文件,然后再执行 SPI 的 loadClass 从此处切入 loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name); } } catch (Throwable t) { IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t); exceptions.put(line, e); } } } } } catch (Throwable t) { logger.error("Exception occurred when loading extension class (interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t); } } // 真正的加载 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."); } if (clazz.isAnnotationPresent(Adaptive.class)) { // 假如有自适应注解,则把该 class 记录在 ExtensionLoader 自适应缓存中 cacheAdaptiveClass(clazz); } else if (isWrapperClass(clazz)) { // 通过反射获得构造器,假如获取成功,则认为其是一个包装类 // 假如是一个包装类,则加到包装类的缓存中 cacheWrapperClass(clazz); } else { // 处理正在的扩展实现类 clazz.getConstructor(); // 检查无参构造函数 if (StringUtils.isEmpty(name)) { 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)) { cacheActivateClass(clazz, names[0]); for (String n : names) { cacheName(clazz, n); // 缓存名称 saveInExtensionClass(extensionClasses, clazz, n); // 把类配置写入 extensionClasses, 至此 SPI 的类加载过程就结束了 } } } } 复制代码
上述代码写得比较杂乱,主要是调试了 Dubbo SPI 机制,对一个标有 SPI
注解的 Robot
接口,完整的类加载跟踪,最终的结果是构造到一个 ExtensionLoader<Robot>
对象 ,里面通过扫描,加载,把 Robot
的实现类,包装类 Wrapper ,自适应 Adaptive
,自激活等信息都分别记录在 ExtensionLoader<Robot>
对象 ExtensionLoader<Robot>
对象里。
当程序执行完: ExtensionLoader.getExtensionClasses()
,就会配置文件都加载到 ExtensionLoader
中了。即为四个特性提供了足够的信息配置信息。
上一小节是分析了 SPI 配置的加载源码,这一小节会分析实例的创建。这个就很有 IOC 的味道了。获得实例的入口可以从: getExtension(String name)
// ExtensionLoader.java public T getExtension(String name) { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("Extension name == null"); } if ("true".equals(name)) { return getDefaultExtension(); } final Holder<Object> holder = getOrCreateHolder(name); // 使用 Holder 提前发布对象,类似先通过 Holder 占住坑位 Object instance = holder.get(); if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { // double Check instance = createExtension(name); // 从这里进入 holder.set(instance); // 把对象创建好后,设置到 句柄 中 } } } return (T) instance; } // 构建一个 Extension 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, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } // 依赖注入 (扩展点自动装配) injectExtension(instance); // 获取包装类集合 一个类是有多个包装类的 Set<Class<?>> wrapperClasses = cachedWrapperClasses; // Wrapper 包装类的实现过程,这里要实现的是多层包装类的包装,具体实现是这样的。 // Wrapper 包装过程和实现细节 // 假设有实现类 A,包装类 A1Wrapper,A2Wrapper,连续包装的步骤如下: // 1、迭代到A1Wrapper,A 给 A1Wrapper 做构造参数,然后获得 A1Wrapper[持有A] // 2、迭代到A2Wrapper,A1Wrapper 给 A2Wrapper 做构造参数,然后获得 A2Wrapper[持有A1Wrapper[A]] // 3、有更多的迭代器时也以此类推 if (CollectionUtils.isNotEmpty(wrapperClasses)) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } initExtension(instance); return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(), t); } } 复制代码
以上就是创建一个 Extension 对象的过程,简单就是:
构建 Extension 对象时,除了通过反射构造对象实例,还需要进行依赖注入(inject),要进行依赖注入,必须要先拥有对象容器(Container, 在 Spring 里就是 BeanFactory),在分析 injectExtension 方法时,要分析对象容器 Factory 的来源。
private T injectExtension(T instance) { if (objectFactory == null) { return instance; } try { for (Method method : instance.getClass().getMethods()) { if (!isSetter(method)) { // 获取所有的 Setter Method continue; } /** * 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 { String property = getSetterProperty(method); // 取出参数 Object object = objectFactory.getExtension(pt, property); // 根据参数向 Factory 获取对象 这里需要知道 objectFactory 的对象从哪里来的??? if (object != null) { method.invoke(instance, object); // 执行 setter 对象 } } 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; } // 上述并不难,关键是知道 ExtensionLoader.objectFactory 对象的来源。对象的来源也是在 ExtensionLoader 构造的时候设置的。 private ExtensionLoader(Class<?> type) { this.type = type; // 构造时,先获取工厂对象,用于进行依赖注入的,使用的是自适应类型 objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); } 复制代码
上述代码可以查看到,SPI 机制的 ExtensionFactory 容器也是通过 ExtensionLoader 获得的。通过查看 ExtensionFactory 的实现,可以发现有以下两个实现。
先查看 ExtensionFactory 的自适应实现,它就是为了
@Adaptive // 自适应注解 public class AdaptiveExtensionFactory implements ExtensionFactory { private final List<ExtensionFactory> factories; // 有两个对象 public AdaptiveExtensionFactory() { // 一个是 Dubbo SPI 本身的容器 (SpiExtensionFactory), // 一个是 Spring 容器 (SpringExtensionFactory) // 这里就意味着 SpringExtensionFactory 是被 Dubbo SPI 注册的 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); } ... 省略代码 } 复制代码
从上面的注释上表明,SPI 的注入对象会从 SpiExtensionFactory 和 SpringExtensionFactory 这两个工厂里获得。
SpiExtensionFactory 类相比比较简单,就是获得根据接口
public class SpiExtensionFactory implements ExtensionFactory { @Override public <T> T getExtension(Class<T> type, String name) { if (type.isInterface() && type.isAnnotationPresent(SPI.class)) { ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type); if (!loader.getSupportedExtensions().isEmpty()) { return loader.getAdaptiveExtension(); // 获取自适应对象 } } return null; } } 复制代码
SpringExtensionFactory 就是链接 Dubbo SPI 容器和 Spring 容器的 桥梁 ,有了这个类, @SPI
注解就可以直接设置在 Spring 的 Bean 上,借用 Spring 更加强大的 IOC 和 AOP 功能了。
SpringExtensionFactory 的类是被设置在 dubbo-config-spring
的模块内的。
/** * SpringExtensionFactory */ public class SpringExtensionFactory implements ExtensionFactory { private static final Logger logger = LoggerFactory.getLogger(SpringExtensionFactory.class); private static final Set<ApplicationContext> CONTEXTS = new ConcurrentHashSet<ApplicationContext>(); // Spring 一调用这个静态方法,就可以把自己的 Context 设置进来了 public static void addApplicationContext(ApplicationContext context) { CONTEXTS.add(context); if (context instanceof ConfigurableApplicationContext) { ((ConfigurableApplicationContext) context).registerShutdownHook(); } } ... 省略其他方法 } 复制代码
有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。 —— Dubbo 官方文档
设定有接口:
- Robot.java @SPI - sayHello @Adaptive - Bumblebee implements Robot - OptimusPrime implements Robot 复制代码
假如可以设计一个特别的 XClass implements Robot
,可以根据 参数 调用 Bumblebee
或 OptimusPrime
,岂不美哉。这就是 适应
,假如是 Dubbo SPI 自己生成的,那就可以理解成 自适应 了。
自适应机制:从 ExtensionLoader.getAdaptiveExtension() 方法获得一个对象,这个对象是 Dubbo SPI 机制自行建立的,可以根据 URL 自定义转发。
@Test public void testAdaptive() { System.out.println("Dubbo SPI Adaptive "); ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class); // 从 getAdaptiveExtension 切入查看 Robot autoRobot = extensionLoader.getAdaptiveExtension(); assertTrue( autoRobot.sayHelloAndReturnSelf(URL.valueOf("dubbo://127.0.0.1:20880/RobotService?robot=bumblebee")) instanceof Bumblebee); // console: Hello, I am Bumblebee assertTrue( autoRobot.sayHelloAndReturnSelf(URL.valueOf("dubbo://127.0.0.1:20880/RobotService?robot=optimusPrime")) instanceof OptimusPrime); // console:hello, I am Optimus Prime. } // ExtensionLoader.java 构建自适应的类 private T createAdaptiveExtension() { try { // getAdaptiveExtensionClass Dubbo SPI 构建类对象 // newInstance 通过反射构建 // injectExtension 依赖注入 return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e); } } /** * 自适应类的构建 * * 步骤: * 1、根据自适应的定义生成代理类代码 (XXX.java) 官方文档 https://dubbo.apache.org/zh-cn/docs/source_code_guide/adaptive-extension.html * 2、编译成 (XXX.class) 并加载类 (Class<XXX> 对象) * * @return */ private Class<?> createAdaptiveExtensionClass() { String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); // XXX.java 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); } // 以上是构建的源码,demo构建出来的代码如下: /** * 这是 Dubbo SPI 自动生成的代码 * https://dubbo.apache.org/zh-cn/docs/source_code_guide/adaptive-extension.html * 有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载 */ public class Robot$Adaptive implements com.qpm.dubbo.test.spi.Robot { public Robot sayHelloAndReturnSelf(org.apache.dubbo.common.URL arg0) { // 1、URL 参数是否为 null, 因此 URL 参数是必须的 if (arg0 == null) throw new IllegalArgumentException("url == null"); org.apache.dubbo.common.URL url = arg0; // 2、根据 URL 获取选择哪个扩展实例 String extName = url.getParameter("robot", "bumblebee"); if(extName == null) throw new IllegalStateException("Failed to get extension (com.qpm.dubbo.test.spi.Robot) name from url (" + url.toString() + ") use keys([robot])"); // 3、获取实例 com.qpm.dubbo.test.spi.Robot extension = (com.qpm.dubbo.test.spi.Robot)ExtensionLoader.getExtensionLoader(com.qpm.dubbo.test.spi.Robot.class).getExtension(extName); // 4、执行实例方法 return extension.sayHelloAndReturnSelf(arg0); } } 复制代码
至此,自适应机制就实现了,其实还是比较简单的。
自激活机制 @Activate
,定义比较简单:根据 URL
的参数和组别等信息对 扩展对象
做过滤,最终获得一系列符合 URL
规则的 扩展对象
,这个过程就是自激活过程。
源码分析如下:
@Activate(value = "robot:autobotFighter", group = "autobot") // 定义这个 扩展对象 一些用于过滤的 K-V 值 public class Bumblebee implements Robot{ @Override public Robot sayHelloAndReturnSelf(URL url) { System.out.println("Hello, I am Bumblebee"); return this; } } @Activate(value = "robot:autobotBoss", group = "autobot") public class OptimusPrime implements Robot{ @Override public Robot sayHelloAndReturnSelf(URL url) { System.out.println("hello, I am Optimus Prime."); return this; } } // ExtensionLoader.java /** * Get activate extensions. * * 自激活实现。规则如下: * 1、REMOVE_VALUE_PREFIX+name eg: "-name" 表示过滤掉某个 Extension * 2、URL的K-V,可以和 @Activate 中的注解 value 进行匹配 * 3、group 参数指定 @Activate 的组别,和 2 一起进行匹配,两者关系为 且 * 4、2和3条件都不合适(扩展对象没写 @Activate 注解),URL 提取出来的 value 匹配了 Extension 的 name * * @param url url * @param values extension point names * @param group group * @return extension list which are activated * @see org.apache.dubbo.common.extension.Activate */ public List<T> getActivateExtension(URL url, String[] values, String group) { // 最终返回的激活扩展点列表 List<T> activateExtensions = new ArrayList<>(); List<String> names = values == null ? new ArrayList<>(0) : Arrays.asList(values); // 名字中不包含 "-default" if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) { getExtensionClasses(); for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) { String name = entry.getKey(); Object activate = entry.getValue(); // 获取 group、activateGroup String[] activateGroup, activateValue; if (activate instanceof Activate) { // 从注解中获得 自激活 的 Group 和 Value activateGroup = ((Activate) activate).group(); activateValue = ((Activate) activate).value(); } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) { activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group(); activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value(); } else { continue; } if (isMatchGroup(group, activateGroup) // 匹配组是否一致 && !names.contains(name) && !names.contains(REMOVE_VALUE_PREFIX + name) && isActive(activateValue, url)) { activateExtensions.add(getExtension(name)); } } // 设置排序 activateExtensions.sort(ActivateComparator.COMPARATOR); } // 自激活不一定要走 K-V 匹配,也可以直接通过 Extensions.name 直接进行匹配,这时候就不一定需要 @Activate 注解了 List<T> loadedExtensions = new ArrayList<>(); for (int i = 0; i < names.size(); i++) { String name = names.get(i); if (!name.startsWith(REMOVE_VALUE_PREFIX) && !names.contains(REMOVE_VALUE_PREFIX + name)) { if (DEFAULT_KEY.equals(name)) { if (!loadedExtensions.isEmpty()) { // 假如是 default,则强行插入在 扩展链条 的第一位 activateExtensions.addAll(0, loadedExtensions); loadedExtensions.clear(); } } else { loadedExtensions.add(getExtension(name)); } } } if (!loadedExtensions.isEmpty()) { activateExtensions.addAll(loadedExtensions); } return activateExtensions; } // 判断一个 @Activate 注解的k-v值,是否和 url 的值相匹配 private boolean isActive(String[] keys, URL url) { if (keys.length == 0) { return true; } for (String key : keys) { // @Active(value="key1:value1, key2:value2") String keyValue = null; if (key.contains(":")) { String[] arr = key.split(":"); key = arr[0]; // 注解K keyValue = arr[1]; // 注解 V } for (Map.Entry<String, String> entry : url.getParameters().entrySet()) { String k = entry.getKey(); // URL K String v = entry.getValue(); // URL V // 查看是否匹配。假如 URL 和 注解 有一项匹配就可以返回 true if ((k.equals(key) || k.endsWith("." + key)) && ((keyValue != null && keyValue.equals(v)) || (keyValue == null && ConfigUtils.isNotEmpty(v)))) { return true; } } } return false; } 复制代码
自激活至此也分析完了,整体也说不是很难,但直接看代码去反推匹配规则会比较吃力。整体来说就是根据条件返回符合条件的 扩展对象
。