zoukankan      html  css  js  c++  java
  • Java:SPI机制

    一、SPI是什么?

    SPI全称为Service Provider Interface,是一种服务发现机制。SPI的本质是将接口的全限定类名配置在文件中,并由服务加载器 ServiceLoader 读取配置文件,加载实现类。这样可以再运行的时候,动态的替换接口的实现类。我们可以通过SPI的这种机制为我们的程序提供拓展功能。常见的SPI包括JDBC、日志门面接口、Spring、SpringBook相关的starter组件、Dubbo、JNDI等

    二、关于SPI的一些概念

    服务是一组公共的接口(通常是抽象类),提供对某些特定应用程序功能或特性。

    服务服务提供者接口服务定义的一组公共接口和抽象类。SPI 定义了可用于您的应用程序的类和方法。

    服务提供者是服务的特定实现,比如说一个APP上需要支付,日常生活中常见的支付方式有支付宝支付和微信支付,这两种支付方式都是支付的服务提供者。

    多个相同的服务,为同一个相同的服务提供多个程序,系统可以任意选择其中一个服务提供者,用户可以选择已安装的程序的一个。

    服务提供者以特殊格式的 JAR 文件提供他们的新服务,通过位于资源目​​录 META-INF/services 中的提供文件标志服务提供者,配置文件的名称是服务提供者的全限定类名,其中名称的每个组成部分用句点 (.) 分隔。该文件必须采用 UTF-8 编码。此外,还可以通过以数字符号 (#)开头的注释行来在文件中包含注释。然后服务接口的实现类的jar包要放在主程序的Classpath下,这样服务启动时,主程序就会用ServiceLoader动态加载实现模块,扫描所有jar包中的约定好的类名,调用Class.forname加载。

    三、原理分析

    服务加载器 ServiceLoader 的源码的成员变量。

     1 public final class ServiceLoader<S>
     2     implements Iterable<S>
     3 {
     4 
     5     private static final String PREFIX = "META-INF/services/";
     6 
     7     // 表示正在加载的服务的类或接口
     8     private final Class<S> service;
     9 
    10     // 用于定位、加载和实例化提供者的类加载器
    11     private final ClassLoader loader;
    12 
    13     // 创建 ServiceLoader 时获取的访问控制上下文
    14     private final AccessControlContext acc;
    15 
    16     // 缓存提供程序,按实例化顺序
    17     private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    18 
    19     // 当前的懒加载查找迭代器
    20     private LazyIterator lookupIterator;
    21     ... ... 
    22 }

    ServiceLoader的私有构造方法,服务的接口,类加载器。

    1     private ServiceLoader(Class<S> svc, ClassLoader cl) {
    2         service = Objects.requireNonNull(svc, "Service interface cannot be null");
    3         loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    4         acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    5         reload();
    6     }

    ServiceLoader的 load 方法,使用了单例模式?

    1     public static <S> ServiceLoader<S> load(Class<S> service,
    2                                             ClassLoader loader)
    3     {
    4         return new ServiceLoader<>(service, loader);
    5     }

     reload 方法,清除次加载程序的所有缓存,重新加载所有的提供程序,并且实例了一个懒加载查找迭代器,等到主程序需要加载提供者程序时,才会使用类加载器去查找所有jar包的服务提供者的class,实例化服服务提供程序并缓存起来。

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

    ServiceLoader的私有内部类,用来懒加载服务提供者程序。

     1     private class LazyIterator
     2         implements Iterator<S>
     3     {
     4 
     5         Class<S> service;
     6         ClassLoader loader;
     7         Enumeration<URL> configs = null;
     8         Iterator<String> pending = null;
     9         String nextName = null;
    10 
    11         private LazyIterator(Class<S> service, ClassLoader loader) {
    12             this.service = service;
    13             this.loader = loader;
    14         }
    15 
    16         private boolean hasNextService() {
    17             if (nextName != null) {
    18                 return true;
    19             }
    20             if (configs == null) {
    21                 try {
    22                     String fullName = PREFIX + service.getName();
    23                     if (loader == null)
    24                         configs = ClassLoader.getSystemResources(fullName);
    25                     else
    26                         configs = loader.getResources(fullName);
    27                 } catch (IOException x) {
    28                     fail(service, "Error locating configuration files", x);
    29                 }
    30             }
    31             while ((pending == null) || !pending.hasNext()) {
    32                 if (!configs.hasMoreElements()) {
    33                     return false;
    34                 }
    35                 pending = parse(service, configs.nextElement());
    36             }
    37             nextName = pending.next();
    38             return true;
    39         }
    40 
    41         private S nextService() {
    42             if (!hasNextService())
    43                 throw new NoSuchElementException();
    44             String cn = nextName;
    45             nextName = null;
    46             Class<?> c = null;
    47             try {
    48                 c = Class.forName(cn, false, loader);
    49             } catch (ClassNotFoundException x) {
    50                 fail(service,
    51                      "Provider " + cn + " not found");
    52             }
    53             if (!service.isAssignableFrom(c)) {
    54                 fail(service,
    55                      "Provider " + cn  + " not a subtype");
    56             }
    57             try {
    58                 S p = service.cast(c.newInstance());
    59                 providers.put(cn, p);
    60                 return p;
    61             } catch (Throwable x) {
    62                 fail(service,
    63                      "Provider " + cn + " could not be instantiated",
    64                      x);
    65             }
    66             throw new Error();          // This cannot happen
    67         }
    68 
    69         public boolean hasNext() {
    70             if (acc == null) {
    71                 return hasNextService();
    72             } else {
    73                 PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
    74                     public Boolean run() { return hasNextService(); }
    75                 };
    76                 return AccessController.doPrivileged(action, acc);
    77             }
    78         }
    79 
    80         public S next() {
    81             if (acc == null) {
    82                 return nextService();
    83             } else {
    84                 PrivilegedAction<S> action = new PrivilegedAction<S>() {
    85                     public S run() { return nextService(); }
    86                 };
    87                 return AccessController.doPrivileged(action, acc);
    88             }
    89         }
    90 
    91         public void remove() {
    92             throw new UnsupportedOperationException();
    93         }
    94 
    95     }
    View Code

    ServiceLoader首先会去确认是否有缓存有实例对象,有则直接从缓存中返回。没有则会执行懒加载的方法,从使用反射方法,从ClassLoader加载并实例化服务提供者,并放入缓存。

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

    ServiceLoader可以跨jar包获取META-INF/services目录中的文件,读取文件得到所有可以实例化的类的名称。

    使用反射方法,Class.forName()用于加载类对象,instance()用于实例化类。最后放入缓存providers中,返回实例的服务提供者对象。

     1     private S nextService() {
     2             if (!hasNextService())
     3                 throw new NoSuchElementException();
     4             String cn = nextName;
     5             nextName = null;
     6             Class<?> c = null;
     7             try {
     8                 c = Class.forName(cn, false, loader);
     9             } catch (ClassNotFoundException x) {
    10                 fail(service,
    11                      "Provider " + cn + " not found");
    12             }
    13             if (!service.isAssignableFrom(c)) {
    14                 fail(service,
    15                      "Provider " + cn  + " not a subtype");
    16             }
    17             try {
    18                 S p = service.cast(c.newInstance());
    19                 providers.put(cn, p);
    20                 return p;
    21             } catch (Throwable x) {
    22                 fail(service,
    23                      "Provider " + cn + " could not be instantiated",
    24                      x);
    25             }
    26             throw new Error();          // This cannot happen
    27         }
  • 相关阅读:
    javascript ext 闭包
    Hibernate HQL from superclass 问题
    sql查询按in顺序排序显示数据 oracle
    Hibernate createSQLquery()
    sql 分页
    javasript 闭包测试
    Excel 批量快速导入mySQL 解决方案~~
    C# 注册COM+组件步骤~
    QT错误集锦~
    QuartzNet Test~~
  • 原文地址:https://www.cnblogs.com/magic-sea/p/15506189.html
Copyright © 2011-2022 走看看