jdk的SPI(Service Provider Interface)

1、定义

  SPI是Java提供的一种服务发现机制,用于在运行时动态查找和加载实现特定接口的服务提供商。按照字面的意思是服务提供接口

将接口与具体业务独立开来。实现调用方与实现方解耦。

1.1API与SPI

  最简单的区别就是接口的属于哪一方,API接口属于实现方,SPI接口属于调用方,SPI是调用方定义接口,由实现方去实现接口。

API是实现方定义接口,由实现方实现接口。

2、实现

  2.1 定义 服务提供者接口

    public interface LoggerService {
        void log(String message);
    }

      2.2 实现方实现这个接口

  public class ConsoleLoggerService implements LoggerService {
        @Override
        public void log(String message) {
            System.out.println("Console Logger: " + message);
        }
    }

    public class FileLoggerService implements LoggerService {
        @Override
        public void log(String message) {
            // 假设将日志写入到文件中...
            System.out.println("File Logger: " + message); // 这里为了演示简化为输出到控制台
        }
    }

   2.3 调用

    public void testFun20() throws UnsupportedEncodingException {

        ServiceLoader<LoggerService> loggerServices = ServiceLoader.load(LoggerService.class);
        for (LoggerService loggerService : loggerServices) {
            loggerService.log("Hello, SPI!");
        }
    }

3、注册

  实现服务接口的类需要在自身的JAR包的 META-INF/services 目录下创建一个文件,该文件的名字应为接口的全限定名(即包含完整包路径的类名)。文件内容则为服务提供者的全限定名,每一行对应一个实现类。

com.mvc.testdemo2.dynamic.ConsoleLoggerService
com.mvc.testdemo2.dynamic.FileLoggerService

  原理Java 中的 SPI 机制就是在每次类加载的时候会先去找到 class 相对目录下的 META-INF 文件夹下的 services 文件夹下的文件,将这个文件夹下面的所有文件先加载到内存中,然后根据这些文件的文件名和里面的文件内容找到相应接口的具体实现类,找到实现类后就可以通过反射去生成对应的对象,保存在一个 list 列表里面,所以可以通过迭代或者遍历的方式拿到对应的实例对象,生成不同的实现。

所以会提出一些规范要求:文件名一定要是接口的全类名,然后里面的内容一定要是实现类的全类名,实现类可以有多个,直接换行就好了,多个实现类的时候,会一个一个的迭代加载

4、原理

  在客户端代码中,使用 java.util.ServiceLoader 类的 load(Class<T> service) 方法来加载指定接口的所有服务提供者。这个方法会查找当前类加载器可以访问的所有 JAR 包下的 META-INF/services 目录下的对应接口的服务提供者列表文件

  4.1实现方法

//生成ServerLoader
public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
  //new一个ServerLoader对象
  public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader){
      return new ServiceLoader<>(service, loader);
  }
    //初始化构造函数
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
      service = Objects.requireNonNull(svc, "Service interface cannot be null");
      loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
      acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
      reload();
    }
  //加载
    public void reload() {
      providers.clear();
      lookupIterator = new LazyIterator(service, loader);
}

5、反射与实例化

  5.1方法迭代

  首先会在 ServiceLoader 的 Provider 缓存中进行查找,如果缓存中没有命中那么则在 LazyIterator 中进行查找

 public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();
        
            public boolean hasNext() {
          //先从knowProviders中查询 if (knownProviders.hasNext()) return true;
           //没有查询 LazyIterator中查询
return lookupIterator.hasNext(); } public S next() { if (knownProviders.hasNext()) return knownProviders.next().getValue(); return lookupIterator.next(); } public void remove() { throw new UnsupportedOperationException(); } }; }

   5.2lookupIterator迭代

     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);
            }
        }
    
      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
        }

6、迭代使用服务

  客户端可以通过遍历 ServiceLoader 返回的迭代器来依次使用每一个服务提供者的实例,这样就可以灵活地在运行时选择和切换不同的服务实现

   public void testFun20() throws UnsupportedEncodingException {

        ServiceLoader<LoggerService> loggerServices = ServiceLoader.load(LoggerService.class);
        for (LoggerService loggerService : loggerServices) {
            loggerService.log("Hello, SPI!");
        }
    }
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值