美文网首页
Java编程技术之浅析SPI服务发现机制

Java编程技术之浅析SPI服务发现机制

作者: 朝槿木兮 | 来源:发表于2020-05-09 00:49 被阅读0次

    SPI服务发现机制

    SPI是Java JDK内部提供的一种服务发现机制。

    • SPI->Service Provider Interface,服务提供接口,是Java JDK内置的一种服务发现机制

    • 通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类

    [⚠️注意事项]:
    面向对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行编码。如果涉及实现类就会违反可插拔的原则,针对于模块装配,Java SPI提供了为某个接口寻找服务的实现机制。

    SPI规范

    • 使用约定:
      [1].编写服务提供接口,可以是抽象接口和函数接口,JDK1.8之后推荐使用函数接口

    [2].在jar包的META-INF/services/目录里创建一个以服务接口命名的文件。其实就是实现该服务接口的具体实现类。

    提供一个目录:
    META-INF/services/
    放到ClassPath下面
    

    [3].当外部程序装配这个模块的时候,就能通过该Jar包META-INF/services/配置文件找到具体的实现类名,并装载实例化,完成模块注入。

    目录下放置一个配置文件:
    文件名是需要拓展的接口全限定名称
    文件内部为要实现的接口实现类
    文件必须为UTF-8编码
    

    [4].寻找服务接口实现,不用在代码中提供,而是利用JDK提供服务查找工具类:java.util.ServiceLoader类来加载使用:

    ServiceLoader.load(xxx.class)
    ServiceLoader<XXXInterface> loads = ServiceLoader.load(xxx.class)
    

    SPI源码分析

    [1].ServiceLoader源码:


    YMEMlV.png
    package java.util;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.net.URL;
    import java.security.AccessController;
    import java.security.AccessControlContext;
    import java.security.PrivilegedAction;
    import java.util.ArrayList;
    import java.util.Enumeration;
    import java.util.Iterator;
    import java.util.List;
    import java.util.NoSuchElementException;
    
    public final class ServiceLoader<S> implements Iterable<S>
    {
        //[1].初始化定义全局配置文件路径Path
        private static final String PREFIX = "META-INF/services/";
        //[2].初始化定义加载的服务类或接口
        private final Class<S> service;
        //[3].初始化定义类加载器
        private final ClassLoader loader;
        //[4].初始化定义访问控制上下文
        private final AccessControlContext acc;
        //[5].初始化定义加载服务类的缓存集合 
        private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
        //[6].初始化定义私有内部LazyIterator类,真正加载服务类的实现类
        private LazyIterator lookupIterator;
        
        //私有化有参构造-> ServiceLoader(Class<S> svc, ClassLoader cl)
        private ServiceLoader(Class<S> svc, ClassLoader cl) {   //[1].实例化服务接口->Class<S>
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        //[2].实例化类加载器->ClassLoader
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        //[3].实例化访问控制上下文->AccessControlContext
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        //[4].回调函数->reload
        reload();
        }
        
        public void reload() {
        //[1].清空缓存实例集合
        providers.clear();
        //[2].实例化私有内部LazyIterator类->LazyIterator
        lookupIterator = new LazyIterator(service, loader);
        }
        
        public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader)
        {
         return new ServiceLoader<>(service, loader);
        }
        
        public static <S> ServiceLoader<S> load(Class<S> service) {
          ClassLoader cl = Thread.currentThread().getContextClassLoader();
          return ServiceLoader.load(service, cl);
        } 
        
     }
    

    2.LazyIterator源码:


    YMeW11.png
      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;
        }
    
        private boolean hasNextService() {
          if (nextName != null) {
            return true;
          }
          if (configs == null) {
            try {
              String fullName = PREFIX + service.getName();
              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;
            }
            pending = parse(service, configs.nextElement());
          }
          nextName = pending.next();
          return true;
        }
    
        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, "Provider " + cn + " not found");
          }
          if (!service.isAssignableFrom(c)) {
            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
        }
    
        public boolean hasNext() {
          if (acc == null) {
            return hasNextService();
          } else {
            PrivilegedAction<Boolean> action =
                new PrivilegedAction<Boolean>() {
                  public Boolean run() {
                    return hasNextService();
                  }
                };
            return AccessController.doPrivileged(action, acc);
          }
        }
    
        public S next() {
          if (acc == null) {
            return nextService();
          } else {
            PrivilegedAction<S> action =
                new PrivilegedAction<S>() {
                  public S run() {
                    return nextService();
                  }
                };
            return AccessController.doPrivileged(action, acc);
          }
        }
    
        public void remove() {
          throw new UnsupportedOperationException();
        }
      }
    

    使用举例

    [1].Dubbo SPI 机制:

    META-INF/dubbo.internal/xxx=接口全限定名
    

    Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。
    Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下。
    与Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。
    [2].Cache SPI 机制:

      META-INF/service/javax.cache.spi.CachingProvider=xxx
    

    [3]Spring SPI 机制:

    META-INF/services/org.apache.commons.logging.LogFactory=xxx
    

    [4].SpringBoot SPI机制:

    META-INF/spring.factories/org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxx
    

    在springboot的自动装配过程中,最终会加载META-INF/spring.factories文件,而加载的过程是由SpringFactoriesLoader加载的。从CLASSPATH下的每个Jar包中搜寻所有META-INF/spring.factories配置文件,然后将解析properties文件,找到指定名称的配置后返回
    源码:

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    // spring.factories文件的格式为:key=value1,value2,value3
    // 从所有的jar包中找到META-INF/spring.factories文件
    // 然后从文件中解析出key=factoryClass类名称的所有value值
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        // 取得资源文件的URL
        Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        List<String> result = new ArrayList<String>();
        // 遍历所有的URL
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            // 根据资源文件URL解析properties文件,得到对应的一组@Configuration类
            Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
            String factoryClassNames = properties.getProperty(factoryClassName);
            // 组装数据,并返回
            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
        }
        return result;
    }
    

    [5].自定义序列化实现SPI:META-INF/services/xxx=接口全限定名

    参考学习Java SPI 和Dubbo SPI机制源码,自己动手实现序列化工具类等
    

    版权声明:本文为博主原创文章,遵循相关版权协议,如若转载或者分享请附上原文出处链接和链接来源。

    相关文章

      网友评论

          本文标题:Java编程技术之浅析SPI服务发现机制

          本文链接:https://www.haomeiwen.com/subject/gigcnhtx.html