SPI机制(1)-Java 原生SPI支持

74 阅读5分钟

ServiceLoader

接口实现类加载器

成员变量

PREFIX:接口实现类加载路径前缀 jar 包"META-INF/services/"
service:需要加载的接口
loader:类加载器
providers(Map):接口实现类-》对象  key:实现类全限定名,value:实现类实例 
lookupIterator :多个实现类遍历器,ServiceLoader 内部实现遍历

加载流程

// 默认使用当前线程上下文类加载器,继续往下看     
public static <S> ServiceLoader<S> load(Class<S> service) {         
ClassLoader cl = Thread.currentThread().getContextClassLoader();        
return ServiceLoader.load(service, cl);     }  

// 创建新实例  
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();
}

// 初始化service 遍历器
public void reload() {
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
}


// 还是简单初始化了遍历器内部变量,重点在哪呢???
//还是从接口实现类实例获取流程入手
private LazyIterator(Class<S> service, ClassLoader loader) {
    this.service = service;
    this.loader = loader;
}

接口实现类实例获取流程

ServiceLoader.iterator() 返回一个遍历器,需要依次遍历才能获取到真正用到的实例

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

        Iterator<Map.Entry<String,S>> knownProviders
            = providers.entrySet().iterator();

        public boolean hasNext() {
            if (knownProviders.hasNext())
                return true;
            return lookupIterator.hasNext();
        }

        public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            return lookupIterator.next();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    };
}

乍一看用的是SericeLoader 内部遍历器LazyIterator(还记得加载流程已经创建这个遍历器对象不?),下面重点分析LazyIterator

成员变量

Class<S> service; 当前要加载的接口
ClassLoader loader; 当前类加载器
Enumeration<URL> configs = null; 接口实现类配置文件,具体待后仔细看
Iterator<String> pending = null; 接口实现类遍历器(同一个接口可以有多个实现了in)
String nextName = null; 下一个要加载的实现类全限定名

每个成员变量含义请稍后仔细看。

成员方法

hasNext 判断是否还有实现类

next 接口实现类实例获取

下面分别分析两个成员方法,注:每个方法都有访问控制 AccessController 里面提供一些native 方法 具体有兴趣的可继续分析,在此不往下讨论。

// 里面继续调 hasNextService
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);
    }
}

private boolean hasNextService() {
//下一个需要加载的类已经存在直接返回
    if (nextName != null) {
        return true;
    }
    //实现类配置为空则需要先加载配置
    if (configs == null) {
        try {
        //配置加载路径构建,还记得配置路径前缀不 "META-INF/services/"
        //在此完整的配置文件路径变为 "META-INF/services/"+接口全限定名
            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;
}

// 没有什么逻辑,多去文件,解析文件每行parseLine ,
private Iterator<String> parse(Class<?> service, URL u)
    throws ServiceConfigurationError
{
    InputStream in = null;
    BufferedReader r = null;
    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();
}


//作用是解析配置文件中实现类名称,具体配置,配置内容限制可自行了解一下
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;
}


//next 里面使用 nextService
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 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

还记得 使用原生JDBC 操作步骤吗

1.加载数据库驱动(MySql,Oracle)

2.获取数据库连接

3.执行SQL

还有人知道第一步有多少种方式? 1.new com.mysql.jdbc.Driver() 以Mysql 为例 2.Class.forName(“com.mysql.jdbc.Driver”)

下面简单看一下每一种方式 com.mysql.jdbc.Driver()

public Driver() throws SQLException {
}

static {
    try {
        DriverManager.registerDriver(new Driver());
    } catch (SQLException var1) {
        throw new RuntimeException("Can't register driver!");
    }
}

以上可以看见里面什么都没干,只是用DriverManager 注册了一下Mysql 驱动 间接引起 DriverManager类加载,只是初始化代码块(类加载三步骤自行复习吧)

static {
//加载数据库驱动
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                return System.getProperty("jdbc.drivers");
            }
        });
    } catch (Exception ex) {
        drivers = null;
    }
    // If the driver is packaged as a Service Provider, load it.
    // Get all the drivers through the classloader
    // exposed as a java.sql.Driver.class service.
    // ServiceLoader.load() replaces the sun.misc.Providers()

    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
           //利用原生SPI 加载具体数据库驱动实现,具体流程可参考SPI 加载流程
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();

            /* Load these drivers, so that they can be instantiated.
             * It may be the case that the driver class may not be there
             * i.e. there may be a packaged driver with the service class
             * as implementation of java.sql.Driver but the actual class
             * may be missing. In that case a java.util.ServiceConfigurationError
             * will be thrown at runtime by the VM trying to locate
             * and load the service.
             *
             * Adding a try catch block to catch those runtime errors
             * if driver not available in classpath but it's
             * packaged as service and that service is there in classpath.
             */
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {
            // Do nothing
            }
            return null;
        }
    });

Class.forName(“com.mysql.jdbc.Driver”) 只是调用方式不同,最终还是会引起com.mysql.jdbc.Driver 静态初始化块代码执行-》DriverManager 加载

总结

SPI 优点:接口定义,实现完全分开,使用方可以只关注自己需要用到的部分。 JDK 原生SPI 支持的缺点:可能会加载实例化所有实现类,实现类的获取没那么灵活。