• Android中Spi机制的使用及源码原理解析


    使用

    • 定义接口

      public interface IFunctionService {
          void test();
      }
      
      • 1
      • 2
      • 3
    • 定义接口实现,这里实现了三个实现类。

    public class FunctionService1 implements IFunctionService {
        private static final String TAG = "FunctionService1";
    
        @Override
        public void test() {
            Log.e(TAG, "test1: ");
        }
    }
    
    
    public class FunctionService2 implements IFunctionService {
        private static final String TAG = "FunctionService2";
        @Override
        public void test() {
            Log.e(TAG, "test2: " );
        }
    }
    
    public class FunctionService3 implements IFunctionService {
        private static final String TAG = "StrategyService3";
    
        @Override
        public void test() {
            Log.e(TAG, "test3: " );
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 编写配置文件,在src/main文件下创建resources/META-INF/services文件夹,然后在其中创建文件,文件名字是接口的全限定类名,内容是实现类的全限定类名,多个实现类用换行符分隔,如下:com.example.testspi.IFunctionService

      注意:META-INF/services/文件路径不可以改变,这个在代码中写死的。

    配置文件路径

    文件的内容如下:

    com.example.testspi.FunctionService1
    com.example.testspi.FunctionService2
    com.example.testspi.FunctionService3
    
    • 1
    • 2
    • 3
    • 测试方法
     public static void main(String[] args) {
            ServiceLoader<IFunctionService> load = ServiceLoader.load(IFunctionService.class);
    
            for (IFunctionService iFunctionService : load) {
                iFunctionService.test();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    概述

    简介

    服务提供者接口(Service Provider Interface,简写为SPI)是JDK内置的一种服务提供发现机制。可以用来加载框架扩展和替换组件,主要是被框架的开发人员使用。在java.util.ServiceLoader的文档里有比较详细的介绍。

    Java中SPI机制主要思想是将装配的控制权移到程序之外,是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,有点类似Spring的IOC机制。在模块化设计中这个机制尤其重要,其核心思想就是解耦。

    规范约束

    当服务的提供者,提供了服务接口的某种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能实现服务接口与实现的解耦。

    缺点

    1. 不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。

    2. 多个并发多线程使用ServiceLoader类的实例是不安全的。

    3. 扩展如果依赖其他的扩展,做不到自动注入和装配。

    源码解析

    今天按照方法的调用过程对源码进行分析。

    • 先看一下ServiceLoader对应的全局变量
    public final class ServiceLoader<S> implements Iterable<S> {
        //接口配置文件对应的路径。
    	private static final String PREFIX = "META-INF/services/";
    
        // 表示正在加载的服务的类或接口
        private final Class<S> service;
    
        // 用于定位、加载和实例化提供程序的类加载器
        private final ClassLoader loader;
    
       	//创建ServiceLoader时采用的访问控制上下文
        private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
    
        // 懒加载迭代器
        private LazyIterator lookupIterator;
    
    
    ................
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • load()

      为给定的服务类型创建一个新的服务加载器。

        public static <S> ServiceLoader<S> load(Class<S> service) {
            //获取类加载器
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            return ServiceLoader.load(service, cl);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 调用重载方法load

      public static <S> ServiceLoader<S> load(Class<S> service,
                                                  ClassLoader loader) {
              return new ServiceLoader<>(service, loader);
          }
      
      • 1
      • 2
      • 3
      • 4
    • 创建ClassLoader对象

       private ServiceLoader(Class<S> svc, ClassLoader cl) {
           //其实就是svc
              service = Objects.requireNonNull(svc, "Service interface cannot be null");
           
              loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
          
              reload();
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    • 调用reload

      清除此加载器的实现类对象缓存,以便重新加载所有实现类对象。

          public void reload() {
              providers.clear();
              lookupIterator = new LazyIterator(service, loader);
          }
      
      • 1
      • 2
      • 3
      • 4
    • LayIterator创建

      LazyIterator是一个迭代器,查找实现类和创建实现类的过程,都在LazyIterator完成。

       private class LazyIterator implements Iterator<S> {
      
              Class<S> service;
              ClassLoader loader;
              Enumeration<URL> configs = null;
              Iterator<String> pending = null;
              String nextName = null;
      
              private LazyIterator(Class<S> service, ClassLoader loader) {
                  this.service = service;
                  this.loader = loader;
              }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • 迭代

      当我们调用iterator.hasNext和iterator.next方法的时候,实际上调用的都是LazyIterator的相应方法。

          public Iterator<S> iterator() {
              return new Iterator<S>() {
      
                  Iterator<Map.Entry<String, S>> knownProviders
                          = providers.entrySet().iterator();
      
                  public boolean hasNext() {
                      if (knownProviders.hasNext())
                          return true;
                      return lookupIterator.hasNext();
                  }
      
                  public S next() {
                      if (knownProviders.hasNext())
                          return knownProviders.next().getValue();
                      return lookupIterator.next();
                  }
      
                  public void remove() {
                      throw new UnsupportedOperationException();
                  }
      
              };
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
    • hasNextService

             private boolean hasNextService() {
                 //第二次调用的时候,已经解析完成了,直接返回
                  if (nextName != null) {
                      return true;
                  }
                 //如果读取过则不在重新读取
                  if (configs == null) {
                      try {
                          //配置文件路径,加上接口的全限定类名,就是文件服务类的文件
                          String fullName = PREFIX + service.getName();
                          //从路径中读取配置,将文件路径转成URL对象
                          if (loader == null)
                              configs = ClassLoader.getSystemResources(fullName);
                          else
                              configs = loader.getResources(fullName);
                      } catch (IOException x) {
                          fail(service, "Error locating configuration files", x);
                      }
                  }
                  while ((pending == null) || !pending.hasNext()) {
                      if (!configs.hasMoreElements()) {
                          return false;
                      }
                      //解析URL文件对象,读取内容,最后返回
                      pending = parse(service, configs.nextElement());
                  }
                  nextName = pending.next();
                  return true;
              }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
    • nextService

      
              private S nextService() {
                  if (!hasNextService())
                      throw new NoSuchElementException();
                  String cn = nextName;
                  nextName = null;
                  Class<?> c = null;
                  try {
                      //读取类对象
                      c = Class.forName(cn, false, loader);
                  } catch (ClassNotFoundException x) {
                      fail(service,
                              // Android-changed: Let the ServiceConfigurationError have a cause.
                              "Provider " + cn + " not found", x);
                      // "Provider " + cn + " not found");
                  }
                  if (!service.isAssignableFrom(c)) {
                      // Android-changed: Let the ServiceConfigurationError have a cause.
                      ClassCastException cce = new ClassCastException(
                              service.getCanonicalName() + " is not assignable from " + c.getCanonicalName());
                      fail(service,
                              "Provider " + cn + " not a subtype", cce);
                      // fail(service,
                      //        "Provider " + cn  + " not a subtype");
                  }
                  try {
                      //强转。
                      S p = service.cast(c.newInstance());
                      providers.put(cn, p);
                      return p;
                  } catch (Throwable x) {
                      fail(service,
                              "Provider " + cn + " could not be instantiated",
                              x);
                  }
                  throw new Error();          // This cannot happen
              }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37

    总结

    1. 调用load方法后本质是读取配置文件,动态的加载配置文件中的实现类。
    2. 使用了懒加载的机制,只有在遍历寻找时才会创建对象。
  • 相关阅读:
    动态规划基础
    GET 请求和 POST 请求的区别和使用
    css基础
    中文标题相似度检测
    Linux常用命令
    Python Opencv实践 - Harris角点检测
    2023年Java毕业设计选题推荐,1000道创新创意Java毕业设计题目推荐,避免踩坑
    React源码分析2-深入理解fiber
    防雷接地+防雷工程施工综合方案
    GBase 8c 技术白皮书 三
  • 原文地址:https://blog.csdn.net/chendeshan330/article/details/126652623