龙空技术网

一文讲透Java核心技术之高可扩展利器SPI

程序员冰河 2401

前言:

当前你们对“throws函数”可能比较关怀,小伙伴们都需要知道一些“throws函数”的相关资讯。那么小编在网络上收集了一些对于“throws函数””的相关资讯,希望各位老铁们能喜欢,你们快快来学习一下吧!

「大家好,我是冰河~~」

SPI的概念

「JAVA SPI = 基于接口的编程+策略模式+配置文件 的动态加载机制」

SPI的使用场景

Java是一种面向对象语言,虽然Java8开始支持函数式编程和Stream,但是总体来说,还是面向对象的语言。在使用Java进行面向对象开发时,一般会推荐使用基于接口的编程,程序的模块与模块之前不会直接进行实现类的硬编码。而在实际的开发过程中,往往一个接口会有多个实现类,各实现类要么实现的逻辑不同,要么使用的方式不同,还有的就是实现的技术不同。为了使调用方在调用接口的时候,明确地知道自己调用的是接口的哪个实现类,或者说为了实现在模块装配的时候不用在程序里动态指明,这就需要一种服务发现机制。Java中的SPI加载机制能够满足这样的需求,它能够自动寻找某个接口的实现类。

「大量的框架使用了Java的SPI技术,如下:」

(1)JDBC加载不同类型的数据库驱动 (2)日志门面接口实现类加载,SLF4J加载不同提供商的日志实现类 (3)Spring中大量使用了SPI

对servlet3.0规范对ServletContainerInitializer的实现自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等

(4)Dubbo里面有很多个组件,每个组件在框架中都是以接口的形成抽象出来!具体的实现又分很多种,在程序执行时根据用户的配置来按需取接口的实现

SPI的使用

当服务的提供者,提供了接口的一种实现后,需要在Jar包的**META-INF/services/**目录下,创建一个以接口的名称(包名.接口名的形式)命名的文件,在文件中配置接口的实现类(完整的包名+类名)。

当外部程序通过「java.util.ServiceLoader」类装载这个接口时,就能够通过该Jar包的**META/Services/**目录里的配置文件找到具体的实现类名,装载实例化,完成注入。同时,SPI的规范规定了接口的实现类必须有一个无参构造方法。

SPI中查找接口的实现类是通过「java.util.ServiceLoader」,而在「java.util.ServiceLoader」类中有一行代码如下:

// 加载具体实现类信息的前缀,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目录下private static final String PREFIX = "META-INF/services/";

这也就是说,我们必须将接口的配置文件写到Jar包的**META/Services/**目录下。

SPI实例

这里,给出一个简单的SPI使用实例,演示在Java程序中如何使用SPI动态加载接口的实现类。

注意:实例是基于Java8进行开发的。

1.创建Maven项目

在IDEA中创建Maven项目spi-demo,如下:

img

2.编辑pom.xml

<?xml version="1.0" encoding="UTF-8"?><project xmlns=";         xmlns:xsi=";         xsi:schemaLocation=" ;> <artifactId>spi-demo</artifactId> <groupId>io.binghe.spi</groupId>    <packaging>jar</packaging>    <version>1.0.0-SNAPSHOT</version>    <modelVersion>4.0.0</modelVersion>    <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>            </plugin>            <plugin>                <groupId>org.apache.maven.plugins</groupId>                <artifactId>maven-compiler-plugin</artifactId>                <version>3.6.0</version>                <configuration>                    <source>1.8</source>                    <target>1.8</target>                </configuration>            </plugin>        </plugins>    </build></project>
3.创建类加载工具类

在io.binghe.spi.loader包下创建MyServiceLoader,MyServiceLoader类中直接调用JDK的ServiceLoader类加载Class。代码如下所示。

package io.binghe.spi.loader;import java.util.ServiceLoader;/** * @author binghe * @version 1.0.0 * @description 类加载工具 */public class MyServiceLoader {    /**     * 使用SPI机制加载所有的Class     */    public static <S> ServiceLoader<S> loadAll(final Class<S> clazz) {        return ServiceLoader.load(clazz);    }}
4.创建接口

在io.binghe.spi.service包下创建接口MyService,作为测试接口,接口中只有一个方法,打印传入的字符串信息。代码如下所示:

package io.binghe.spi.service;/** * @author binghe * @version 1.0.0 * @description 定义接口 */public interface MyService {    /**     *  打印信息     */    void print(String info);}
5.创建接口的实现类(1)创建第一个实现类MyServiceA

在io.binghe.spi.service.impl包下创建MyServiceA类,实现MyService接口。代码如下所示:

package io.binghe.spi.service.impl;import io.binghe.spi.service.MyService;/** * @author binghe * @version 1.0.0 * @description 接口的第一个实现 */public class MyServiceA implements MyService {    @Override    public void print(String info) {        System.out.println(MyServiceA.class.getName() + " print " + info);    }}
(2)创建第二个实现类MyServiceB

在io.binghe.spi.service.impl包下创建MyServiceB类,实现MyService接口。代码如下所示:

package io.binghe.spi.service.impl;import io.binghe.spi.service.MyService;/** * @author binghe * @version 1.0.0 * @description 接口第二个实现 */public class MyServiceB implements MyService {    @Override    public void print(String info) {        System.out.println(MyServiceB.class.getName() + " print " + info);    }}
6.创建接口文件

在项目的src/main/resources目录下创建**META/Services/**目录,在目录中创建io.binghe.spi.service.MyService文件,注意:文件必须是接口MyService的全名,之后将实现MyService接口的类配置到文件中,如下所示:

io.binghe.spi.service.impl.MyServiceAio.binghe.spi.service.impl.MyServiceB
7.创建测试类

在项目的io.binghe.spi.main包下创建Main类,该类为测试程序的入口类,提供一个main()方法,在main()方法中调用ServiceLoader类加载MyService接口的实现类。并通过Java8的Stream将结果打印出来,如下所示:

package io.binghe.spi.main;import io.binghe.spi.loader.MyServiceLoader;import io.binghe.spi.service.MyService;import java.util.ServiceLoader;import java.util.stream.StreamSupport;/** * @author binghe * @version 1.0.0 * @description 测试的main方法 */public class Main {    public static void main(String[] args){        ServiceLoader<MyService> loader = MyServiceLoader.loadAll(MyService.class);        StreamSupport.stream(loader.spliterator(), false).forEach(s -> s.print("Hello World"));    }}
8.测试实例

运行Main类中的main()方法,打印出的信息如下所示:

io.binghe.spi.service.impl.MyServiceA print Hello Worldio.binghe.spi.service.impl.MyServiceB print Hello WorldProcess finished with exit code 0

通过打印信息可以看出,通过Java SPI机制正确加载出接口的实现类,并调用接口的实现方法。

源码解析

这里,主要是对SPI的加载流程涉及到的「java.util.ServiceLoader」的源码的解析。

进入「java.util.ServiceLoader」的源码,可以看到ServiceLoader类实现了「java.lang.Iterable」接口,如下所示。

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

说明ServiceLoader类是可以遍历迭代的。

「java.util.ServiceLoader」类中定义了如下的成员变量:

// 加载具体实现类信息的前缀,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目录下private static final String PREFIX = "META-INF/services/";// 需要加载的接口private final Class<S> service;// 类加载器,用于加载以接口命名的文件中配置的接口的实现类private final ClassLoader loader;// 创建ServiceLoader时采用的访问控制上下文环境private final AccessControlContext acc;// 用来缓存已经加载的接口实现类,其中,Key是接口实现类的完整类名,Value为实现类对象private LinkedHashMap<String,S> providers = new LinkedHashMap<>();// 用于延迟加载实现类的迭代器private LazyIterator lookupIterator;

可以看到ServiceLoader类中定义了加载前缀为“META-INF/services/”,所以,接口文件必须要在项目的「src/main/resources」目录下的**META-INF/services/**目录下创建。

从MyServiceLoader类调用**ServiceLoader.load(clazz)**方法进入源码,如下所示:

//根据类的Class对象加载指定的类,返回ServiceLoader对象public static <S> ServiceLoader<S> load(Class<S> service) { //获取当前线程的类加载器 ClassLoader cl = Thread.currentThread().getContextClassLoader(); //动态加载指定的类,将类加载到ServiceLoader中 return ServiceLoader.load(service, cl);}

方法中调用了**ServiceLoader.load(service, cl)**方法,继续跟踪代码,如下所示:

//通过ClassLoader加载指定类的Class,并将返回结果封装到ServiceLoader对象中public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){ return new ServiceLoader<>(service, loader);}

可以看到**ServiceLoader.load(service, cl)**方法中,调用了ServiceLoader类的构造方法,继续跟进代码,如下所示:

//构造ServiceLoader对象private ServiceLoader(Class<S> svc, ClassLoader cl) { //如果传入的Class对象为空,则判处空指针异常 service = Objects.requireNonNull(svc, "Service interface cannot be null"); //如果传入的ClassLoader为空,则通过ClassLoader.getSystemClassLoader()获取,否则直接使用传入的ClassLoader loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload();}

继续跟**reload()**方法,如下所示。

//重新加载public void reload() { //清空保存加载的实现类的LinkedHashMap providers.clear(); //构造延迟加载的迭代器 lookupIterator = new LazyIterator(service, loader);}

继续跟进懒加载迭代器的构造函数,如下所示。

private LazyIterator(Class<S> service, ClassLoader loader) { this.service = service; this.loader = loader;}

可以看到,会将需要加载的接口的Class对象和类加载器赋值给「LazyIterator」的成员变量。

当我们在程序中迭代获取对象实例时,首先在成员变量**providers「中查找是否有缓存的实例对象。如果存在则直接返回,否则调用」lookupIterator**延迟加载迭代器进行加载。

迭代器进行逻辑判断的代码如下所示:

//迭代ServiceLoader的方法public Iterator<S> iterator() { return new Iterator<S>() {  //获取保存实现类的LinkedHashMap<String,S>的迭代器  Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();  //判断是否有下一个元素  public boolean hasNext() {   //如果knownProviders存在元素,则直接返回true   if (knownProviders.hasNext())    return true;   //返回延迟加载器是否存在元素   return lookupIterator.hasNext();  }  //获取下一个元素  public S next() {   //如果knownProviders存在元素,则直接获取   if (knownProviders.hasNext())    return knownProviders.next().getValue();   //获取延迟迭代器lookupIterator中的元素   return lookupIterator.next();  }  public void remove() {   throw new UnsupportedOperationException();  } };}

「LazyIterator」加载类的流程如下代码所示

//判断是否拥有下一个实例private boolean hasNextService() { //如果拥有下一个实例,直接返回true if (nextName != null) {  return true; } //如果实现类的全名为null if (configs == null) {  try {   //获取全文件名,文件相对路径+文件名称(包名+接口名)   String fullName = PREFIX + service.getName();   //类加载器为空,则通过ClassLoader.getSystemResources()方法获取   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()) {  //如果configs中没有更过的元素,则直接返回false  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 {  //通过c.newInstance()生成对象实例  S p = service.cast(c.newInstance());  //将生成的对象实例保存到缓存中(LinkedHashMap<String,S>)  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); }}

最后,给出整个「java.util.ServiceLoader」的类,如下所示:

package java.util;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.net.URL;import java.security.AccessControlContext;import java.security.AccessController;import java.security.PrivilegedAction;public final class ServiceLoader<S>  implements Iterable<S> {    // 加载具体实现类信息的前缀,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目录下    private static final String PREFIX = "META-INF/services/";    // 需要加载的接口    private final Class<S> service;    // 类加载器,用于加载以接口命名的文件中配置的接口的实现类    private final ClassLoader loader;    // 创建ServiceLoader时采用的访问控制上下文环境    private final AccessControlContext acc;    // 用来缓存已经加载的接口实现类,其中,Key是接口实现类的完整类名,Value为实现类对象    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();    // 用于延迟加载实现类的迭代器    private LazyIterator lookupIterator;    //重新加载    public void reload() {        //清空保存加载的实现类的LinkedHashMap        providers.clear();        //构造延迟加载的迭代器        lookupIterator = new LazyIterator(service, loader);    }    //构造ServiceLoader对象    private ServiceLoader(Class<S> svc, ClassLoader cl) {        //如果传入的Class对象为空,则判处空指针异常        service = Objects.requireNonNull(svc, "Service interface cannot be null");        //如果传入的ClassLoader为空,则通过ClassLoader.getSystemClassLoader()获取,否则直接使用传入的ClassLoader        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;        reload();    }    private static void fail(Class<?> service, String msg, Throwable cause)        throws ServiceConfigurationError    {        throw new ServiceConfigurationError(service.getName() + ": " + msg,                                            cause);    }    private static void fail(Class<?> service, String msg)        throws ServiceConfigurationError    {        throw new ServiceConfigurationError(service.getName() + ": " + msg);    }    private static void fail(Class<?> service, URL u, int line, String msg)        throws ServiceConfigurationError    {        fail(service, u + ":" + line + ": " + msg);    }    // Parse a single line from the given configuration file, adding the name    // on the line to the names 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;    }    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 inner class implementing fully-lazy provider lookupload    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() {            //如果拥有下一个实例,直接返回true            if (nextName != null) {                return true;            }            //如果实现类的全名为null            if (configs == null) {                try {                    //获取全文件名,文件相对路径+文件名称(包名+接口名)                    String fullName = PREFIX + service.getName();                    //类加载器为空,则通过ClassLoader.getSystemResources()方法获取                    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()) {                //如果configs中没有更过的元素,则直接返回false                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 {                //通过c.newInstance()生成对象实例                S p = service.cast(c.newInstance());                //将生成的对象实例保存到缓存中(LinkedHashMap<String,S>)                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();        }    }    //迭代ServiceLoader的方法    public Iterator<S> iterator() {        return new Iterator<S>() {            //获取保存实现类的LinkedHashMap<String,S>的迭代器            Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();            //判断是否有下一个元素            public boolean hasNext() {                //如果knownProviders存在元素,则直接返回true                if (knownProviders.hasNext())                    return true;                //返回延迟加载器是否存在元素                return lookupIterator.hasNext();            }            //获取下一个元素            public S next() {                //如果knownProviders存在元素,则直接获取                if (knownProviders.hasNext())                    return knownProviders.next().getValue();                //获取延迟迭代器lookupIterator中的元素                return lookupIterator.next();            }            public void remove() {                throw new UnsupportedOperationException();            }        };    }    //通过ClassLoader加载指定类的Class,并将返回结果封装到ServiceLoader对象中    public static <S> ServiceLoader<S> load(Class<S> service,                                            ClassLoader loader)    {        return new ServiceLoader<>(service, loader);    }    //根据类的Class对象加载指定的类,返回ServiceLoader对象    public static <S> ServiceLoader<S> load(Class<S> service) {        //获取当前线程的类加载器        ClassLoader cl = Thread.currentThread().getContextClassLoader();        //动态加载指定的类,将类加载到ServiceLoader中        return ServiceLoader.load(service, cl);    }        public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {        ClassLoader cl = ClassLoader.getSystemClassLoader();        ClassLoader prev = null;        while (cl != null) {            prev = cl;            cl = cl.getParent();        }        return ServiceLoader.load(service, prev);    }    /**     * Returns a string describing this service.     *     * @return  A descriptive string     */    public String toString() {        return "java.util.ServiceLoader[" + service.getName() + "]";    }}
SPI总结

最后,对Java提供的SPI机制进行简单的总结。

优点:

能够实现项目解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。

缺点:多个并发多线程使用ServiceLoader类的实例是不安全的虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。

参考:深入理解Java中的spi机制

「好了,今天就到这儿吧,我是冰河,我们下期见~~」

标签: #throws函数