Java SPI机制学习

发布时间:2023年12月18日

最近阅读源码时看到了SPI加载类的方式 这里学习一下

SPI??Service Provider Interface

服务提供接口

API和SPI区别

API是接口和实现类均有服务提供方负责 服务调用方更多是一种主动调用 只是调用SDK来实现某个功能;接口的归属权在于服务提供方

SPI是接口在调用方侧定义,但是具体实现类由服务提供方现? 接口归属权在于服务调用方


SPI实践

定义一个类 和三个实现

public interface ISpiService {
    
    public void diBiz();
}
public class ASpiService implements ISpiService {
    
    @Override
    public void diBiz() {
        System.out.println("ASpiService diBiz");
    }
}
public class BSpiService implements ISpiService {
    
    @Override
    public void diBiz() {
        System.out.println("BSpiService diBiz");
    }
}
public class CSpiService implements ISpiService {
    
    @Override
    public void diBiz() {
        System.out.println("CSpiService diBiz");
    }
}

定义实现类的目录文件

/META-INF/services 目录下

文件名为接口的全限定名

文件类型为实现类的全限定名

com.java.spi.ASpiService
com.java.spi.BSpiService
com.java.spi.CSpiService

使用ServiceLoad来加载实现类

public static void main(String[] args) {
        ServiceLoader<ISpiService> spiServices = ServiceLoader.load(ISpiService.class);
        Iterator<ISpiService> iterator = spiServices.iterator();
        while (iterator.hasNext()) {
            iterator.next().diBiz();
        }
    }

运行结果

ASpiService diBiz
BSpiService diBiz
CSpiService diBiz

可知可以调用所有类实现类的方法

ServiceLoad大致原理

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

类加载器使用线程上下文加载器

最终load方法初始化lazyIterator对象

 public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

hasNext方法

        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 static final String PREFIX = "META-INF/services/";

从约定目录下找实现类

next方法

        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
        }

使用反射根据类名来初始化对象

SPI应用

JDBC

日志框架slf4j

文章来源:https://blog.csdn.net/u014106644/article/details/134934314
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。