SPI 加载机制详解


一、SPI 是什么

   SPI(Service Provider Interface)机制是Java平台提供的一种用于服务发现和服务提供者查找的机制。它允许在运行时动态地加载和实例化实现特定接口的类,从而达到扩展性和灵活性的目的。

二、使用示例

1.定义服务接口

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

2.实现类

public class FileLogger implements Logger {
    @Override
    public void log(String message) {
        // 实现文件日志记录
    }
}

public class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        // 实现控制台日志输出
    }
}

3.配置文件

   在每个 JAR 文件的 META-INF/services 目录下,创建名为 com.example.Logger 的文件(假设接口在com.example包下),然后在文件中分别写入:

com.example.FileLogger
com.example.ConsoleLogger

4.加载服务提供者

import java.util.ServiceLoader;

public class Main {
    public static void main(String[] args) {
        ServiceLoader<Logger> loggerLoaders = ServiceLoader.load(Logger.class).findFirst();
        for (Logger logger : loggerLoaders) {
            logger.log("Hello, SPI!");
        }
    }
}

三、SPI 的优缺点

1.优点

  • 解耦:SPI允许将接口与其实现分离,这有助于减少模块间的依赖,提高系统的可维护性和可扩展性。
  • 动态加载:SPI能够根据配置文件动态加载服务实现,无需重新编译或部署整个应用,增强了灵活性。
  • 扩展性 :应用程序可以通过SPI机制轻松地添加新的服务实现,而不需要修改现有代码。
  • 插件化: SPI机制支持插件式的架构,允许第三方开发者为系统提供额外的功能或替代实现。
  • 配置驱动:SPI的实现通常由配置文件(如META-INF/services下的文件)驱动,这样可以更方便地更改或切换服务实现。

2.缺点

  • 解耦:遍历加载所有的实现类,这样效率还是相对较低的。
  • 动态加载:当多个 ServiceLoader 同时 load 时,会有并发问题。

四、SPI 的使用场景

  • 数据库驱动管理:当应用程序需要连接到不同的数据库时,可以通过SPI机制来动态地加载和使用不同的数据库驱动,而无需在代码中硬编码具体的驱动类名。
  • 日志框架选择:类似于数据库驱动,不同的日志框架(如Logback, Log4j,
    SLF4J等)可以作为SPI的实现,允许应用程序在运行时选择并使用不同的日志系统。
  • 框架扩展和插件机制:许多框架(如Spring
    Boot)使用SPI来允许用户扩展框架的功能,比如通过SPI注册自定义的bean或者配置初始化器。
  • 模块间解耦:SPI可以用于在模块之间提供一种松耦合的交互方式,使得模块可以在不知道具体实现细节的情况下工作。
  • 服务发现:SPI机制可以帮助系统在运行时发现可用的服务提供者,从而动态地选择和使用服务。

五、拓展(ServiceLoader 源码理解)

1.ServiceLoader 的 load() 方法

// 定义一个静态泛型方法 load,它接受一个 Class 对象作为参数,
// 这个 Class 对象应该代表一个服务接口(即,任何实现了该接口的类都可以被视为该服务的提供者)。
public static <S> ServiceLoader<S> load(Class<S> service) {

    // 获取当前线程的上下文类加载器。
    // 上下文类加载器通常用于当前线程的类加载需求,如加载类路径上的类和资源。
    // 这里使用上下文类加载器是为了确保我们可以从正确的类路径加载服务提供者。
    ClassLoader cl = Thread.currentThread().getContextClassLoader();

    // 使用获取到的类加载器、服务接口以及调用者的类信息,创建并返回一个新的 ServiceLoader 实例。
    // 调用者的类信息是通过 Reflection.getCallerClass() 方法获得的,
    // 这可以确保 ServiceLoader 从正确的类加载器中加载服务提供者。
    return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
}

2.ServiceLoader 的 findFirst() 方法,创建一个 ServiceLoader 的迭代器

// 定义了一个名为 findFirst 的公共方法,返回类型是 Optional<S>。
// 这个方法用于寻找并返回第一个可用的服务实现。
public Optional<S> findFirst() {

    // 创建一个 ServiceLoader 的迭代器。
    Iterator<S> iterator = iterator();
    
    // 检查迭代器是否有下一个元素(即是否有至少一个服务实现)。
    if (iterator.hasNext()) {
        
        // 如果有下一个元素,调用 next() 方法获取第一个服务实现,
        // 并将其封装在一个 Optional 对象中返回。
        return Optional.of(iterator.next());
    } else {
        
        // 如果没有可用的服务实现,返回一个空的 Optional 对象。
        return Optional.empty();
    }
}

3.newLookupIterator() 返回一个路径迭代器

private Iterator<Provider<S>> newLookupIterator() {
    // 断言:如果 layer 不为 null,则 loader 必须为 null,反之亦然。
    // 这是因为在模块化系统中,如果服务是在模块层面查找的,则不应同时使用类路径查找。
    assert layer == null || loader == null;
    
    // 如果 layer 不为 null,这意味着服务提供者应当从模块中查找。
    // 这里返回一个新的 LayerLookupIterator 实例,用于从模块中查找服务提供者。
    if (layer != null) {
        return new LayerLookupIterator<>();
    } else {
        // 如果 layer 为 null,说明将从类路径中查找服务提供者。
        // 这里创建两个迭代器:一个用于模块服务查找,另一个用于类路径查找。
        Iterator<Provider<S>> first = new ModuleServicesLookupIterator<>();
        Iterator<Provider<S>> second = new LazyClassPathLookupIterator<>();
        
        // 创建并返回一个新的 Iterator 实例,它组合了上述两个迭代器。
        // 这个 Iterator 实现了 hasNext 和 next 方法,用于按需返回服务提供者。
        return new Iterator<Provider<S>>() {
            
            // 实现了 Iterator 的 hasNext 方法,检查是否有更多的服务提供者。
            // 如果任意一个迭代器有下一个元素,就返回 true。
            @Override
            public boolean hasNext() {
                return (first.hasNext() || second.hasNext());
            }
            
            // 实现了 Iterator 的 next 方法,返回下一个服务提供者。
            // 首先尝试从模块服务迭代器中获取下一个元素,
            // 如果没有,再从类路径查找迭代器中获取。
            // 如果两者都没有下一个元素,抛出 NoSuchElementException。
            @Override
            public Provider<S> next() {
                if (first.hasNext()) {
                    return first.next();
                } else if (second.hasNext()) {
                    return second.next();
                } else {
                    throw new NoSuchElementException();
                }
            }
        };
    }
}

4.ServiceLoader 内部的一个私有类 LazyClassPathLookupIterator

private final class LazyClassPathLookupIterator<T>
    implements Iterator<Provider<T>> {
    
    // 配置文件的前缀,指向服务提供者列表的位置。
    static final String PREFIX = "META-INF/services/";
    
    // 用于存储已发现的提供者名称,以避免重复加载。
    Set<String> providerNames = new HashSet<>();
    
    // 枚举类路径上的配置文件。
    Enumeration<URL> configs;
    
    // 待处理的配置文件条目迭代器。
    Iterator<String> pending;
    
    // 下一个要返回的提供者。
    Provider<T> nextProvider;
    
    // 下一个配置错误。
    ServiceConfigurationError nextError;
    
    LazyClassPathLookupIterator() { }
    
    /**
     * 解析给定配置文件中的单行,将行上的名称添加到名称集合中,如果之前未见过的话。
     */
    private int parseLine(URL u, BufferedReader r, int lc, Set<String> names)
        throws IOException {
        
        // 读取一行配置。
        String ln = r.readLine();
        
        // 如果行为空,返回 -1 表示文件结束。
        if (ln == null) {
            return -1;
        }
        
        // 移除注释并修剪空白字符。
        int ci = ln.indexOf('#');
        if (ci >= 0) ln = ln.substring(0, ci);
        ln = ln.trim();
        
        // 如果行非空,检查格式是否合法。
        int n = ln.length();
        if (n != 0) {
            if (ln.contains(" ") || ln.contains("\t"))
                fail(service, u, lc, "非法配置文件语法");
            
            // 检查名称是否符合 Java 标识符规则。
            int cp = ln.codePointAt(0);
            if (!Character.isJavaIdentifierStart(cp))
                fail(service, u, lc, "非法提供者类名称: " + ln);
            
            int start = Character.charCount(cp);
            for (int i = start; i < n; i += Character.charCount(cp)) {
                cp = ln.codePointAt(i);
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                    fail(service, u, lc, "非法提供者类名称: " + ln);
            }
            
            // 如果名称未在 providerNames 中出现过,则添加到集合中。
            if (providerNames.add(ln)) {
                names.add(ln);
            }
        }
        
        // 返回下一行的行号。
        return lc + 1;
    }
    
    /**
     * 解析给定 URL 作为提供者配置文件的内容。
     */
    private Iterator<String> parse(URL u) {
        // 创建一个 LinkedHashSet 来保持插入顺序。
        Set<String> names = new LinkedHashSet<>();
        
        try {
            // 打开 URL 连接,禁用缓存。
            URLConnection uc = u.openConnection();
            uc.setUseCaches(false);
            
            // 读取并解析配置文件。
            try (InputStream in = uc.getInputStream();
                 BufferedReader r = new BufferedReader(new InputStreamReader(in, UTF_8.INSTANCE))) {
                
                int lc = 1;
                while ((lc = parseLine(u, r, lc, names)) >= 0);
            }
        } catch (IOException x) {
            fail(service, "访问配置文件时出错", x);
        }
        
        // 返回名称的迭代器。
        return names.iterator();
    }
    
    /**
     * 加载并返回下一个提供者类。
     */
    private Class<?> nextProviderClass() {
        // 如果 configs 为空,初始化它。
        if (configs == null) {
            String fullName = PREFIX + service.getName();
            if (loader == null) {
                configs = ClassLoader.getSystemResources(fullName);
            } else if (loader == ClassLoaders.platformClassLoader()) {
                if (BootLoader.hasClassPath()) {
                    configs = BootLoader.findResources(fullName);
                } else {
                    configs = Collections.emptyEnumeration();
                }
            } else {
                configs = loader.getResources(fullName);
            }
        }
        
        // 处理配置文件直到找到下一个提供者类。
        while ((pending == null) || !pending.hasNext()) {
            if (!configs.hasMoreElements()) {
                return null;
            }
            pending = parse(configs.nextElement());
        }
        
        // 加载下一个提供者类。
        String cn = pending.next();
        try {
            return Class.forName(cn, false, loader);
        } catch (ClassNotFoundException x) {
            fail(service, "找不到提供者 " + cn);
            return null;
        }
    }
    
    // ...(省略了部分代码,例如 hasNextService 和 nextService 方法)
    
    // 实现 Iterator 接口的 hasNext 方法。
    @Override
    public boolean hasNext() {
        if (acc == null) {
            return hasNextService();
        } else {
            // 使用 AccessControlContext 来安全地调用 hasNextService 方法。
            PrivilegedAction<Boolean> action = () -> hasNextService();
            return AccessController.doPrivileged(action, acc);
        }
    }
    
    // 实现 Iterator 接口的 next 方法。
    @Override
    public Provider<T> next() {
        if (acc == null) {
            return nextService();
        } else {
            // 使用 AccessControlContext 来安全地调用 nextService 方法。
            PrivilegedAction<Provider<T>> action = () -> nextService();
            return AccessController.doPrivileged(action, acc);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值