源码级深度理解 Java SPI( 七 )

可以看出 , 这个方法的作用就是:首先检查缓存,缓存未命中则调用 createExtension 方法创建拓展对象 。那么,createExtension 是如何创建拓展对象的呢,其源码如下:
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;if (wrapperClasses != null && !wrapperClasses.isEmpty()) {// 循环创建 Wrapper 实例for (Class<?> wrapperClass : wrapperClasses) {// 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例 。// 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));}}return instance;} catch (Throwable t) {throw new IllegalStateException("...");}}createExtension 方法的的工作步骤可以归纳为:

  1. 通过 getExtensionClasses 获取所有的拓展类
  2. 通过反射创建拓展对象
  3. 向拓展对象中注入依赖
  4. 将拓展对象包裹在相应的 Wrapper 对象中
以上步骤中 , 第一个步骤是加载拓展类的关键,第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现 。
5.2 获取所有的拓展类Dubbo 在通过名称获取拓展类之前,首先需要根据配置文件解析出拓展项名称到拓展类的映射关系表(Map<名称, 拓展类>) , 之后再根据拓展项名称从映射关系表中取出相应的拓展类即可 。相关过程的代码分析如下:
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;}这里也是先检查缓存,若缓存未命中 , 则通过 synchronized 加锁 。加锁后再次检查缓存 , 并判空 。此时如果 classes 仍为 null , 则通过 loadExtensionClasses 加载拓展类 。下面分析 loadExtensionClasses 方法的逻辑 。
private Map<String, Class<?>> loadExtensionClasses() {// 获取 SPI 注解,这里的 type 变量是在调用 getExtensionLoader 方法时传入的final SPI defaultAnnotation = type.getAnnotation(SPI.class);if (defaultAnnotation != null) {String value = https://www.huyubaike.com/biancheng/defaultAnnotation.value();if ((value = value.trim()).length() > 0) {// 对 SPI 注解内容进行切分String[] names = NAME_SEPARATOR.split(value);// 检测 SPI 注解内容是否合法,不合法则抛出异常if (names.length > 1) {throw new IllegalStateException("more than 1 default extension name on extension...");}// 设置默认名称,参考 getDefaultExtension 方法if (names.length == 1) {cachedDefaultName = names[0];}}}Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();// 加载指定文件夹下的配置文件loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);loadDirectory(extensionClasses, DUBBO_DIRECTORY);loadDirectory(extensionClasses, SERVICES_DIRECTORY);return extensionClasses;}loadExtensionClasses 方法总共做了两件事情 , 一是对 SPI 注解进行解析,二是调用 loadDirectory 方法加载指定文件夹配置文件 。SPI 注解解析过程比较简单,无需多说 。下面我们来看一下 loadDirectory 做了哪些事情 。
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {// fileName = 文件夹路径 + type 全限定名String fileName = dir + type.getName();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 resourceURL = urls.nextElement();// 加载资源loadResource(extensionClasses, classLoader, resourceURL);}}} catch (Throwable t) {logger.error("...");}}loadDirectory 方法先通过 classLoader 获取所有资源链接,然后再通过 loadResource 方法加载资源 。我们继续跟下去,看一下 loadResource 方法的实现 。
private void loadResource(Map<String, Class<?>> extensionClasses,ClassLoader classLoader, java.net.URL resourceURL) {try {BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));try {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 {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) {// 加载类,并通过 loadClass 方法对类进行缓存loadClass(extensionClasses, resourceURL,Class.forName(line, true, classLoader), name);}} catch (Throwable t) {IllegalStateException e = new IllegalStateException("Failed to load extension class...");}}}} finally {reader.close();}} catch (Throwable t) {logger.error("Exception when load extension class...");}}

推荐阅读