美文网首页
Java SPI 机制

Java SPI 机制

作者: 爱健身的兔子 | 来源:发表于2021-01-12 15:21 被阅读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);
         }
       }
    

    相关文章

      网友评论

          本文标题:Java SPI 机制

          本文链接:https://www.haomeiwen.com/subject/cqqeaktx.html