Java SPI 机制

2021-01-12  本文已影响0人  爱健身的兔子

1 什么是SPI

SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,是一种将服务接口与服务实现分离以达到解耦、大大提升了程序可扩展性的机制。引入服务提供者就是引入了spi接口的实现者,通过本地的注册发现获取到具体的实现类,轻松可插拔

2 SPI的使用场景

JDBC就是典型的SPI的使用场景。通常各大厂商(如Mysql、Oracle)会根据一个统一的规范(java.sql.Driver)开发各自的驱动实现逻辑。客户端使用jdbc时不需要去改变代码,直接引入不同的spi接口服务即可。Mysql的则是com.mysql.jdbc.Driver,Oracle则是oracle.jdbc.driver.OracleDriver

3 SPI实战

3.1 定义接口
public interface Driver {
     void upload(String url);
}
3.2 定义接口的实现
public class MysqlDriver implements Driver{  
      @Override
     public void upload(String url) {
         System.out.println("upload to Mysql:"+url);
     }
 }
 
 public class OracleDriver implements Driver{
     @Override
    public void upload(String url) {
        System.out.println("upload to Oracle:"+url);
    }
 }
3.3 创建接口文件

在resources目录下新建META-INF/services目录,并且在这个目录下新建一个与上述接口的全限定名一致的文件,并在文件中添加实现类。

com.spi.MysqlDriver
com.spi.OracleDriver
3.4 加载SPI实现类
public static void main(String[] args) {
         ServiceLoader<Driver > driver= ServiceLoader.load(Driver .class);
         for (Driver d : driver) {
             d.upload("dirPath");
         }
    }

4 SPI原理

4.1 ServiceLoader核心成员
public final class ServiceLoader<S> implements Iterable<S> {


    //扫描目录前缀
    private static final String PREFIX = "META-INF/services/";

    // 被加载的类或接口
    private final Class<S> service;

    // 用于定位、加载和实例化实现方实现的类的类加载器
    private final ClassLoader loader;

    // 安全访问器
    private final AccessControlContext acc;

    // 按照实例化的顺序缓存已经实例化的类
    private LinkedHashMap<String, S> providers = new LinkedHashMap<>();

    // 懒查找迭代器
    private java.util.ServiceLoader.LazyIterator lookupIterator;

    // 私有内部类,提供对所有的service的类的加载与实例化
    private class LazyIterator implements Iterator<S> {
        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        String nextName = null;

        //...
        private boolean hasNextService() {
            if (configs == null) {
                try {
                    //通过相对路径读取classpath中META-INF目录的文件,也就是读取服务提供者的实现类全限定名
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    //...
                }
                //....
            }
        }

        private S nextService() {
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                //反射加载类
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
            }
            try {
                //实例化
                S p = service.cast(c.newInstance());
                //放进缓存
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                //..
            }
            //..
        }
    }
}
4.2 SPI的加载过程

ServiceLoader.load()方法只是创建了ServiceLoader对象。

    //初始化一个ServiceLoader,load参数分别是需要加载的接口class对象,当前类加载器
    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);
    }

LazyIterator的hasNext()方法将META-INFO下面的类文件去出来。

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

LazyIterator的next()方法通过上面读取的实现类的全限定名称将类加载进来。

public S next() {
     if (acc == null) {//用来判断serviceLoader对象是否完成初始化
           return nextService();
     } else {
          PrivilegedAction<S> action = new PrivilegedAction<S>() {
          public S run() { return nextService(); }
           };
          return AccessController.doPrivileged(action, acc);
     }
   }
上一篇下一篇

猜你喜欢

热点阅读