当前位置: 萬仟网 > IT编程>软件设计>架构 > Java编程技术之浅析SPI服务发现机制

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

2020年05月09日  | 萬仟网IT编程  | 我要评论
SPI服务发现机制 SPI是Java JDK内部提供的一种服务发现机制。 SPI Service Provider Interface,服务提供接口,是Java JDK内置的一种服务发现机制 通过在ClassPath路径下的META INF/services文件夹查找文件,自动加载文件里所定义的类 ...

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机制源码,自己动手实现序列化工具类等

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

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
Copyright © 2017-2020  萬仟网 保留所有权利. 粤ICP备17035492号-1