Java的SPI机制

  created  by  鱼鱼 {{tag}}
创建于 2024年10月14日 22:21:16 最后修改于 2024年10月14日 22:21:44

         SPI(Service Provider Interface) 是JDK内部提供的一种用于服务能力扩展的机制。在服务中通过不同的下沉方法实现能够加载不同的接口实现类,从而实现功能的热插拔。相比一些类似的设计模式(例如策略模式), SPI作为Java自带的实现特性,相对更加灵活和开放。

        我们常见的JDBC、日志框架slf4j、JavaMail、Spring等组件都基于 SPI实现(例如JDBC针对不同数据源的驱动)。


SPI代码实战

        之所以说区别于Java的一些设计模式,因为Java有一些实现能实现 SPI的动态加载。首先让我们定义 SPI对外提供抽象能力的接口类,这里为了便于理解展示包路径:

package com.fishmaple.diantao;

public interface   SPITest {

    //定义一个获取version的方法
    Integer getVersion();

}


    然后我们定义两个不同的实现:

package com.fishmaple.diantao;

public class   SPITestImpl implements   SPITest {

    public Integer getVersion() {
        System.out.println("version1");
        return 1;
    }

}
package com.fishmaple.diantao;

public class   SPITestImpl2 implements   SPITest {

    public Integer getVersion() {
        System.out.println("version2");
        return 2;
    }

}


    截止到目前,上面还没有使用到Java SPI的特性。随后我们在资源路径下创建一个 META-INF/services/{ SPI接口类名} 的文件,如下图:

文件内容只要写 SPI需要使用的实现类全路径就可以了,可以配置多个,使用换行符分隔,例如我们这里将文件内容配置为:

com.fishmaple.diantao.  SPITestImpl2

    随后在启动服务后,我们使用 SPI提供的类(ServiceLoader),通过load方法,然后基于迭代器就可以访问到 SPI的实现类:

public class DiantaoApplication {

    public static void main(String[] args) {
        ServiceLoader<  SPITest> load = ServiceLoader.load(  SPITest.class);
        for(  SPITest spiTest:load){
            spiTest.getVersion();
        }
    }

}

SPI原理浅析

    细看代码,不难发现, SPI的实现全依赖于ServiceLoader类,而ServiceLoader又依赖于 类加载器的能力,让我们看一下关键方法:

public final class ServiceLoader<S>
    implements Iterable<S>
{

    private static final String PREFIX = "META-INF/services/";
    
    // The class or interface representing the service being loaded
    private final Class<S> service;
    
    // The class loader used to locate, load, and instantiate providers
    private final ClassLoader loader;
    
    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;
    //缓存已经被加载过的  SPI    // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    
    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;
    public static <S> ServiceLoader<S> load(Class<S> service) {
        //1 获取  类加载器        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
    
    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        //2 这里创建了一个新实例
        return new ServiceLoader<>(service, loader);
    }
    
    //3 不难看出,这里的初始化和之后的reload其实并没有创建出  SPI需要的实例,(因为是lazyload)
    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);
    }
    // 4 迭代器,捡重点看
    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();
        }
    
    }

如此便可以实现在首次轮询时加载对应的 SPI实现。基于这种实现, SPI的使用一般在初始化过程中,并且无法支持并发调用,很容易引发数据问题。


案例 - JDBC

SPI以上的实现思想被广泛应用,最常见的就是JDBC-driver,在引用的driver的依赖中,其实都有这种 SPI对应的配置文件:


评论区
评论
{{comment.creator}}
{{comment.createTime}} {{comment.index}}楼
评论

Java的SPI机制

Java的SPI机制

         SPI(Service Provider Interface) 是JDK内部提供的一种用于服务能力扩展的机制。在服务中通过不同的下沉方法实现能够加载不同的接口实现类,从而实现功能的热插拔。相比一些类似的设计模式(例如策略模式), SPI作为Java自带的实现特性,相对更加灵活和开放。

        我们常见的JDBC、日志框架slf4j、JavaMail、Spring等组件都基于 SPI实现(例如JDBC针对不同数据源的驱动)。


SPI代码实战

        之所以说区别于Java的一些设计模式,因为Java有一些实现能实现 SPI的动态加载。首先让我们定义 SPI对外提供抽象能力的接口类,这里为了便于理解展示包路径:

package com.fishmaple.diantao;

public interface   SPITest {

    //定义一个获取version的方法
    Integer getVersion();

}


    然后我们定义两个不同的实现:

package com.fishmaple.diantao;

public class   SPITestImpl implements   SPITest {

    public Integer getVersion() {
        System.out.println("version1");
        return 1;
    }

}
package com.fishmaple.diantao;

public class   SPITestImpl2 implements   SPITest {

    public Integer getVersion() {
        System.out.println("version2");
        return 2;
    }

}


    截止到目前,上面还没有使用到Java SPI的特性。随后我们在资源路径下创建一个 META-INF/services/{ SPI接口类名} 的文件,如下图:

文件内容只要写 SPI需要使用的实现类全路径就可以了,可以配置多个,使用换行符分隔,例如我们这里将文件内容配置为:

com.fishmaple.diantao.  SPITestImpl2

    随后在启动服务后,我们使用 SPI提供的类(ServiceLoader),通过load方法,然后基于迭代器就可以访问到 SPI的实现类:

public class DiantaoApplication {

    public static void main(String[] args) {
        ServiceLoader<  SPITest> load = ServiceLoader.load(  SPITest.class);
        for(  SPITest spiTest:load){
            spiTest.getVersion();
        }
    }

}

SPI原理浅析

    细看代码,不难发现, SPI的实现全依赖于ServiceLoader类,而ServiceLoader又依赖于 类加载器的能力,让我们看一下关键方法:

public final class ServiceLoader<S>
    implements Iterable<S>
{

    private static final String PREFIX = "META-INF/services/";
    
    // The class or interface representing the service being loaded
    private final Class<S> service;
    
    // The class loader used to locate, load, and instantiate providers
    private final ClassLoader loader;
    
    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;
    //缓存已经被加载过的  SPI    // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    
    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;
    public static <S> ServiceLoader<S> load(Class<S> service) {
        //1 获取  类加载器        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
    
    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        //2 这里创建了一个新实例
        return new ServiceLoader<>(service, loader);
    }
    
    //3 不难看出,这里的初始化和之后的reload其实并没有创建出  SPI需要的实例,(因为是lazyload)
    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);
    }
    // 4 迭代器,捡重点看
    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();
        }
    
    }

如此便可以实现在首次轮询时加载对应的 SPI实现。基于这种实现, SPI的使用一般在初始化过程中,并且无法支持并发调用,很容易引发数据问题。


案例 - JDBC

SPI以上的实现思想被广泛应用,最常见的就是JDBC-driver,在引用的driver的依赖中,其实都有这种 SPI对应的配置文件:



Java的SPI机制2024-10-14鱼鱼

{{commentTitle}}

评论   ctrl+Enter 发送评论