zoukankan      html  css  js  c++  java
  • JAVA的SPI机制

    1.什么是SPI

         SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。 SPI的作用就是为这些被扩展的API寻找服务实现。

    3.SPI的简单实现

        下面我们来简单实现一个jdk的SPI的简单实现。

        首先第一步,定义一组接口:

    1 public interface UploadCDN {
    2     void upload(String url);
    3 }

       这个接口分别有两个实现:

    复制代码
     1 public class QiyiCDN implements UploadCDN {  //上传爱奇艺cdn
     2     @Override
     3     public void upload(String url) {
     4         System.out.println("upload to qiyi cdn");
     5     }
     6 }
     7 
     8 public class ChinaNetCDN implements UploadCDN {//上传网宿cdn
     9     @Override
    10     public void upload(String url) {
    11         System.out.println("upload to chinaNet cdn");
    12     }
    13 }
    复制代码

        然后需要在resources目录下新建META-INF/services目录,并且在这个目录下新建一个与上述接口的全限定名一致的文件,在这个文件中写入接口的实现类的全限定名:

     

        这时,通过serviceLoader加载实现类并调用:

    1  public static void main(String[] args) {
    2         ServiceLoader<UploadCDN> uploadCDN = ServiceLoader.load(UploadCDN.class);
    3         for (UploadCDN u : uploadCDN) {
    4             u.upload("filePath");
    5         }
    6     }

        输出如下:

         这样一个简单的spi的demo就完成了。可以看到其中最为核心的就是通过ServiceLoader这个类来加载具体的实现类的。

    4. SPI原理解析

         通过上面简单的demo,可以看到最关键的实现就是ServiceLoader这个类,可以看下这个类的源码,如下:

    复制代码
     1 public final class ServiceLoader<S> implements Iterable<S> {
     2 
     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     // 上下文对象
    14     private final AccessControlContext acc;
    15 
    16     // 按照实例化的顺序缓存已经实例化的类
    17     private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
    18 
    19     // 懒查找迭代器
    20     private java.util.ServiceLoader.LazyIterator lookupIterator;
    21 
    22     // 私有内部类,提供对所有的service的类的加载与实例化
    23     private class LazyIterator implements Iterator<S> {
    24         Class<S> service;
    25         ClassLoader loader;
    26         Enumeration<URL> configs = null;
    27         String nextName = null;
    28 
    29         //...
    30         private boolean hasNextService() {
    31             if (configs == null) {
    32                 try {
    33                     //获取目录下所有的类
    34                     String fullName = PREFIX + service.getName();
    35                     if (loader == null)
    36                         configs = ClassLoader.getSystemResources(fullName);
    37                     else
    38                         configs = loader.getResources(fullName);
    39                 } catch (IOException x) {
    40                     //...
    41                 }
    42                 //....
    43             }
    44         }
    45 
    46         private S nextService() {
    47             String cn = nextName;
    48             nextName = null;
    49             Class<?> c = null;
    50             try {
    51                 //反射加载类
    52                 c = Class.forName(cn, false, loader);
    53             } catch (ClassNotFoundException x) {
    54             }
    55             try {
    56                 //实例化
    57                 S p = service.cast(c.newInstance());
    58                 //放进缓存
    59                 providers.put(cn, p);
    60                 return p;
    61             } catch (Throwable x) {
    62                 //..
    63             }
    64             //..
    65         }
    66     }
    67 }
    复制代码

         上面的代码只贴出了部分关键的实现,有兴趣的读者可以自己去研究,下面贴出比较直观的spi加载的主要流程供参考:

    Spring Boot的扩展机制之Spring Factories

    Spring Boot中的SPI机制

    在Spring中也有一种类似与Java SPI的加载机制。它在META-INF/spring.factories文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。
    这种自定义的SPI机制是Spring Boot Starter实现的基础。

     
    spring.factories

    Spring Factories实现原理

    spring-core包里定义了SpringFactoriesLoader类,这个类实现了检索META-INF/spring.factories文件,并获取指定接口的配置的功能。在这个类中定义了两个对外的方法:

    loadFactories 根据接口类获取其实现类的实例,这个方法返回的是对象列表。
    loadFactoryNames 根据接口获取其接口类的名称,这个方法返回的是类名的列表。
    上面的两个方法的关键都是从指定的ClassLoader中获取spring.factories文件,并解析得到类名列表,具体代码如下

        private static Map<String, List<String>> loadSpringFactories(

    从代码中我们可以知道,在这个方法中会遍历整个ClassLoader中所有jar包下的spring.factories文件。也就是说我们可以在自己的jar中配置spring.factories文件,不会影响到其它地方的配置,也不会被别人的配置覆盖。

    spring.factories的是通过Properties解析得到的,所以我们在写文件中的内容都是安装下面这种方式配置的:

    com.xxx.interface=com.xxx.classname
    

    如果一个接口希望配置多个实现类,可以使用’,’进行分割




    参考,SPI详解Spring Factories

  • 相关阅读:
    js全局变量和局部变量
    mysql分组后保留n条数据
    记一次微信公众号的开发与后台搭建
    Excel 导入 Sql Server出错——“文本被截断,或者一个或多个字符在目标代码页中没有匹配项”错误的解决
    一言之思-3
    时间获取
    sql基础的基础
    一言之思-2
    一言之思
    node.js日期
  • 原文地址:https://www.cnblogs.com/liran123/p/15507115.html
Copyright © 2011-2022 走看看