【Java实战】源码解析Java SPI(Service Provider Interface )机制原理

来源:互联网 发布:外婆的澎湖湾 知乎 编辑:程序博客网 时间:2024/06/05 09:12

一、背景知识

在阅读开源框架源码时,发现许多框架都支持SPI(Service Provider Interface ),前面有篇文章JDBC对Driver的加载时应用了SPI,参考【Hibernate实战】源码解析Hibernate参数绑定及PreparedStatement防SQL注入原理 ,于是借着JDBC对Driver的加载实现,分析下SPI机制。

二、什么是SPI

看下 Wikipedia对其的解释
Service Provider Interface (SPI) is an API intended to be implemented or extended by a third party. It can be used to enable framework extension and replaceable components.

简单翻译下:
服务提供者接口(SPI)是一个API,它是由第三方实现或扩展的。它可以用于支持框架扩展和可替换组件。

简单来说SPI就是为了框架扩展而生的。在不修改原始应用程序的基础上扩展应用,可以看下Creating Extensible Applications With the Java Platform对其的解释,其中也有详细的示例。
A service provider interface (SPI) is the set of public interfaces and abstract classes that a service defines. The SPI defines the classes and methods available to your application.A service provider implements the SPI. An application with extensible services will allow you, vendors, and perhaps even customers to add service providers without modifying the original application.
简单翻译如下:
服务提供者接口(SPI)是服务定义的公共接口和抽象类的集合。SPI定义了应用程序可用的类和方法。 服务提供者实现SPI。具有可扩展服务的应用程序将允许您、供应商甚至客户在不修改原始应用程序的情况下添加服务提供者。

自JDK1.6对外提供了对SPI的支持,java.util.ServiceLoader.java,下面以Driver加载为例分析SPI机制。jdk1.8、mySQLDriver5.1.38

三、SPI原理

1、JDBC示例程序

public static void JDBCExample(){try {//Class.forName("com.mysql.jdbc.Driver");Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/hhl?useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSize=25&prepStmtCacheSqlLimit=2048&characterEncoding=utf8&useSSL=false","root", "123456");PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM product p WHERE p.productName=?");preparedStatement.setString(1,"Mango");ResultSet resultSet = preparedStatement.executeQuery();while (resultSet.next()){System.out.println(resultSet.getString(1));}resultSet.close();preparedStatement.close();connection.close();} catch (Exception e) {e.printStackTrace();}}
自JDBC4.0以后就支持SPI了,不在需要用Class.forName()加载数据库驱动了,当然以前程序中用Class.forName()加载数据库驱动的仍然可以正常工作(用数据库连接池的还都是用Class.forName()加载数据库驱动的)。那么加载数据库驱动的操作在哪儿实现呢,java.sql.DriverManager.java
/**     * Load the initial JDBC drivers by checking the System property     * jdbc.properties and then use the {@code ServiceLoader} mechanism     */    static {        loadInitialDrivers();        println("JDBC DriverManager initialized");    }
根据注释看出从jdbc.properties和利用ServiceLoader机制加载JDBC驱动。jdbc.properties的先不管,看下ServiceLoader机制
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() {                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;            }        });        println("DriverManager.initialize: jdbc.drivers = " + drivers);        if (drivers == null || drivers.equals("")) {            return;        }        String[] driversList = drivers.split(":");        println("number of Drivers:" + driversList.length);        for (String aDriver : driversList) {            try {                println("DriverManager.Initialize: loading " + aDriver);                Class.forName(aDriver, true,                        ClassLoader.getSystemClassLoader());            } catch (Exception ex) {                println("DriverManager.Initialize: load failed: " + ex);            }        }    }


ServiceLoader关键代码如下几行
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);         2                Iterator<Driver> driversIterator = loadedDrivers.iterator();    3                try{                    while(driversIterator.hasNext()) {                          4                        driversIterator.next();                                 5                    }                } catch(Throwable t) {                // Do nothing                }


2、逐行看下 java.util.ServiceLoader.java

public static <S> ServiceLoader<S> load(Class<S> service) {        ClassLoader cl = Thread.currentThread().getContextClassLoader();        return ServiceLoader.load(service, cl);    }
其中的classLoader是当前线程上下文的加载器,泛型S代表服务类型的类,本例中就是Driver;参数service 为代表服务的接口或者抽象类,本例中是Driver.class。

public static <S> ServiceLoader<S> load(Class<S> service,                                            ClassLoader loader)    {        return new ServiceLoader<>(service, loader);    }

最终根据service和loader创建了一个ServiceLoader
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();    }
本例中,其中acc为null,接着看reload方法
 public void reload() {        providers.clear();        lookupIterator = new LazyIterator(service, loader);    }
清空了providers缓存
// Cached providers, in instantiation order    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
创建了lookupIterator
// The current lazy-lookup iterator    private LazyIterator lookupIterator;

// Private inner class implementing fully-lazy provider lookup    //    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();        }    }

创建改对象的原因就是为了实现延迟服务提供者查找。延迟到什么时候,继续看代码。



3、loadedDrivers.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();            }        };    }

创建了一个内部类Iterator,用于操作缓存providers和延迟加载类lookupIterator

4、driversIterator.hasNext()

操作的就是上面的hasNext
  public boolean hasNext() {                if (knownProviders.hasNext())                    return true;                return lookupIterator.hasNext();            }

用到了LazyIterator中的hasNext()
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);            }        }
这里acc为null,不需要特权继续执行hasNextService
 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;        }
起初nextName为null,configs也为null,这个时候就会根据fullName获取包含其的资源,然后parse解析
private static final String PREFIX = "META-INF/services/";
本例中fullName就是META-INF/services/java.sql.Driver,mysql驱动中该文件内容如下:
com.mysql.jdbc.Drivercom.mysql.fabric.jdbc.FabricMySQLDriver

 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();    }

改文件需要utf-8编码,返回的就是包含有文件内容的集合迭代器。

5、driversIterator.next()

接着看Next

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


起初走lookupIterator.next()

 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);            }        }

直接走nextService

 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        }

这里会加载并初始化获取到的驱动,例如com.mysql.jdbc.Driver,这里还是需要Class.forName。采用

 S p = service.cast(c.newInstance());                providers.put(cn, p);
初始化驱动,因此驱动需要有一个默认的构造函数。


至此,利用ServiceLoader加载并初始化驱动的操作就完成了。那么那么多驱动,要选择哪个驱动呢,就是根据url确定

jdbc:mysql://127.0.0.1:3306/hhl

如上url就会选择com.mysql.jdbc.Driver进行连接操作,其判断由驱动自己去做,由驱动中的acceptsURL及parseURL判断驱动支持的url,具体可看代码com.mysql.jdbc.NonRegisteringDriver.java


总结:SPI的机制就是在不修改原有程序的基础上实现扩展

当服务的提供者,提供了服务接口(java.sql.Driver)的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里指定。












阅读全文
0 0
原创粉丝点击