SPI机制详细讲解

文章目录

  • SPI机制
  • 案例分析
    • 建立DriverManager
    • 建立MysqlDriver来实现扩展
    • 建立OracleDriver来实现扩展
    • 测试spitest
  • 源码分析
    • ServiceLoader类的结构
    • reload加载类
    • LazyIterator类
    • parse解析URL对象方法
    • parseLine方法

SPI机制

SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的
META-INF/services 文件夹查找文件,自动加载文件里所定义的类。这一机制为很多框架扩展提供了
可能,比如在Springboot,Dubbo、JDBC中都使用到了SPI机制。

案例分析

以驱动加载为例。分别定义不同的实现,根据调用方引入不同的实现来进行加载具体的实现类。

建立DriverManager

定义一个获取驱动连接信息的接口。

/**
 * 公共的接口 com.elite.common.DriverManager
 */
public interface DriverManager {
    //获取连接信息
    String getConnectionInfo();
}

建立MysqlDriver来实现扩展

/**
 * SPI:MySQL对于 getConnectionInfo 的一种实现
 *
 */
public class MysqlDriver implements DriverManager
{

    @Override
    public String getConnectionInfo() {
        return "this is mysqldriver";
    }
}

此时我们需要在ClassPath下新建META-INF/services,新建一个名称必须是 定义的接口的全类路径,文件中写上接口的实现类的全类路径名称
SPI机制详细讲解_第1张图片

建立OracleDriver来实现扩展

/**
 * 扩展实现  com.elite.oracle.OracleDriver
 * com.elite.common.DriverManager
 */
public class OracleDriver implements DriverManager {
    @Override
    public String getConnectionInfo() {
        return "这是oracle数据库连接的扩展实现";
    }
}

SPI机制详细讲解_第2张图片

测试spitest

需要在pom.xml引入具体的实现

        <dependency>
            <groupId>com.elitegroupId>
            <artifactId>MysqlDriverartifactId>
            <version>1.0-SNAPSHOTversion>
        dependency>






测试代码

import java.util.Iterator;
import java.util.ServiceLoader;

public class Main {
    public static void main(String[] args) {
        ServiceLoader<DriverManager> providers = ServiceLoader.load(DriverManager.class);
        Iterator<DriverManager> iterator = providers.iterator();
        while(iterator.hasNext()){
            DriverManager next = iterator.next();
            String connectionInfo = next.getConnectionInfo();
            System.out.println(connectionInfo);
        }
    }
}

SPI机制详细讲解_第3张图片

源码分析

ServiceLoader类的结构

  //定义了配置文件的路径,这就是为何需要新建配置文件
  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
    //ServiceLoader创建时的访问控制上下文
    private final AccessControlContext acc;

    // Cached providers, in instantiation order
    //缓存加载的服务,根据实例化的顺序
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // The current lazy-lookup iterator
    //加载服务的类
    private LazyIterator lookupIterator;

reload加载类

  //加载lookupIterator 
  public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(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();
    }

LazyIterator类

1.加载配置文件
2.解析配置文件
3.解析每一行
4.获取配置的实现类的类名加入list
5.利用反射加载 c = Class.forName(cn, false, loader);
6.转换为具体的class对象添加到providers S p = service.cast(c.newInstance()); providers.put(cn, p);

 private class LazyIterator implements Iterator<S>{

        Class<S> service;
        ClassLoader loader;
        //URL对象
        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 {
                    //加载完全类限定名 META-INF/servie+接口类名
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        //通过类加载转换为URL对象
                        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;
                }
                //parse加载对应的服务
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }
        //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
        }
    }

parse解析URL对象方法

解析URL对象

private Iterator<String> parse(Class<?> service, URL u)throws ServiceConfigurationError {
        InputStream in = null;
        BufferedReader r = null;
        //解析文件的配置实现类名list
        ArrayList<String> names = new ArrayList<>();
        try {
            in = u.openStream();
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
            while ((lc = parseLine(service, u, r, lc, names)) >= 0);
        } catch (IOException x) {
            fail(service, "Error reading configuration file", x);
        } finally {
            try {
                if (r != null) r.close();
                if (in != null) in.close();
            } catch (IOException y) {
                fail(service, "Error closing configuration file", y);
            }
        }
        return names.iterator();
    }

parseLine方法

从配置文件解析单行,添加实现类的明名字到list

private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names)
        throws IOException, ServiceConfigurationError {
        //读取行数据
        String ln = r.readLine();
        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.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                fail(service, u, lc, "Illegal configuration-file syntax");
            int cp = ln.codePointAt(0);
            if (!Character.isJavaIdentifierStart(cp))
                fail(service, u, lc, "Illegal provider-class name: " + ln);
            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
                cp = ln.codePointAt(i);
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                    fail(service, u, lc, "Illegal provider-class name: " + ln);
            }
            //提供的服务不包含在添加
            if (!providers.containsKey(ln) && !names.contains(ln))
                names.add(ln);
        }
        return lc + 1;
    }

你可能感兴趣的:(Java,java,SPI)