ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

dubbo的SPI扩展机制

2020-02-03 12:01:25  阅读:215  来源: 互联网

标签:dubbo return service 迭代 扩展 public SPI ServiceLoader 加载


  dubbo的SPI(Service Provider Interface)是在java的SPI基础上做了功能特性的扩展;但本质上都是做了和Spring IOC\AOP一样的事,服务(或者bean)的注册统一管理、以及实例化;此外spi提供的是一种jvm级别的服务发现机制,我们只需按照spi的要求,在jar包中进行适当的配置,jvm就会在运行时通过懒加载,帮我们找到所需要的服务并加载。如果我们一直不适用这个服务,那么他就不会被加载,一定程度上避免了资源浪费;

SPI怎么用到我们的业务服务的注册解析管理上?dubbo存在大量的扩展点,业务代码也存在大量扩展点,so.........(此处应该有总结//todo)

1、Java的SPI扩展机制

使用上就只需要注意下面几点:

  • 虽然没有强制要求使用功能interface或者abstract class作为spi的注册服务,但是作为可扩展服务,尽量养成使用interface或者abstract class的习惯;
  • 必须放在JAR包或项目的指定路径,即 META-INF/services 下;
  • 必须以服务的全限定名命名配置文件,比如本例中,配置文件必须命名为 cn.jinlu.spi.demo.Animal,java会根据此名进行服务查找;
  • 内容必须是一个实现类的全限定名,如果要注册多个实现类,按行分割。注释以#开头。;

源码解析部分:

(1)、代码入口:通过ServiceLoader的静态方法load(Class<?> service)进行服务加载

ServiceLoader的SPI服务注册发现机制

public static void main(String[] args) {

    String content = "";

    // ContentExtractorAware为需要被注册、服务的服务接口,通过静态方法ServiceLoader#load进行加载

    ServiceLoader<ContentExtractorAware> loader = ServiceLoader.load(ContentExtractorAware.class);

    System.out.println("list all the implements sub class to invoke abstract method:\n");

    // 遍历服务的实现

    Iterator iterator = loader.iterator();

    while (iterator.hasNext()) {

        ContentExtractorAware aware = (ContentExtractorAware) iterator.next();

        System.out.println(aware.extract(content));

    }

}

(2)、ServiceLoader构造:默认会使用当前线程的上下文class loader构造ServiceLoader,ServiceLoader通过实现Iterable<?>接口,使得在构造完ServiceLoader后,ServiceLoader实例并不会立刻扫描当前进程中的服务实例,而是实例化创建了一个LazyIterator懒加载迭代器,只有在实际遍历使用时再扫描所有jar包找到对应的服务。懒加载迭代器被保存在一个内部成员lookupIterator中(核心理念);

ServiceLoader

/**

 * 静态方法构建ServiceLoader实例

 *

 * @param service

 * @param <S>

 * @return

 */

public static <S> ServiceLoader<S> load(Class<S> service) {

    // 当前线程上下文class loader

    ClassLoader cl = Thread.currentThread().getContextClassLoader();

    return ServiceLoader.load(service, cl);

}

 

/**

 * 构建ServiceLoader实例

 *

 * @param service

 * @param loader

 * @param <S>

 * @return

 */

public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {

    return new ServiceLoader<>(service, loader);

}

 

/**

 * 私有构造方法,必须通过ServiceLoader.load(Class<?>)静态方法来创建ServiceLoader实例

 *

 * @param svc

 * @param cl

 */

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

}

 

/**

 * 重新load指定service的实现。通过LazyIterator实现懒加载。

 */

public void reload() {

    providers.clear();

    lookupIterator = new LazyIterator(service, loader);

}

(3)、服务的加载和遍历

a、ServiceLoader迭代器:ServiceLoader通过实现Iterable<?>接口,将服务的实现的实例化延后到遍历是执行

  • 未进行迭代时,不对jar包做扫描(即不实例化服务实现类)
  • 首次迭代时,Serviceloader.providers中没有任何缓存,总是会通过LazyIterator进行加载,并将service实现的全限定名与加载的service实例作为key-value缓存到ServiceLoader.providers中
  • 之后再进行迭代时,直接从ServiceLoader.providers中获取

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

     ....

    // 缓存的service provider,按照初始化顺序排列。

    private LinkedHashMap<String, S> providers = new LinkedHashMap<>();

 

    // 懒加载迭代器被保存在一个内部成员lookupIterator中

    // 当前的LazyIterator迭代器指针,服务懒加载迭代器

    private ServiceLoader.LazyIterator lookupIterator;

 

 

    /**

     * 创建ServiceLoader迭代器,隐藏了LazyIterator的实现细节

     *

     * @return

     */

    public Iterator<S> iterator() {

        return new Iterator<S>() {

            // 创建Iterator迭代器时的ServiceLoader.providers快照,

            // 因此在首次迭代时,iterator总是会通过LazyIterator进行懒加载

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

 

            @Override

            public boolean hasNext() {

                // 如果已经扫描过,则对providers进行迭代;

                if (knownProviders.hasNext())

                    return true;

                // 如果没有扫描过,则通过lookupIterator进行扫描和懒加载

                return lookupIterator.hasNext();

            }

 

            @Override

            public S next() {

                // 如果已经扫描过,则对providers进行迭代;

                if (knownProviders.hasNext())

                    return knownProviders.next().getValue();

                // 如果没有扫描过,则通过lookupIterator进行扫描和懒加载

                return (S) lookupIterator.next();

            }

 

            @Override

            public void remove() {

                throw new UnsupportedOperationException();

            }

 

        };

    }

   ...

}

b、内部私有LazyIterator懒加载迭代器,主要实现功能:

  • 首次迭代时,通过ClassLoader.getResources(String)获得指定services文件的URL集合
  • 如果是首次遍历懒加载器,或者对上一个URL内容解析获得的service实现类集合完成了迭代,则从configs中取下一个services文件URL进行解析,按行获得具体的service实现类集合,并进行迭代。

  • 当前URL中解析得到的实现类集合进行迭代,每次返回一个service实现类。

/**

 * 懒加载迭代器

 */

private class LazyIterator implements Iterator<S> {

    // 服务

    Class<S> service;

 

    // jvm资源加载器

    ClassLoader loader;

 

    // 存放服务全限定名对应资源集合

    Enumeration<URL> configs = null;

 

    // 当前service配置文件的内容迭代器

    // 即对services进行遍历,取出一个services配置文件,再对该文件按行解析,

    // 每行代表一个具体的service实现类,pending是某个services配置文件中service实现类的迭代器

    Iterator<String> pending = null;

    // 下一个服务实现类名称

    String nextName = null;

 

    private LazyIterator(Class<S> service, ClassLoader loader) {

        this.service = service;

        this.loader = loader;

    }

 

 

    @Override

    public boolean hasNext() {

        if (acc == null) {

            return hasNextService();

        else {

            PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {

                @Override

                public Boolean run() {

                    return hasNextService();

                }

            };

            return AccessController.doPrivileged(action, acc);

        }

    }

 

 

    // 首次迭代时,configs为空,尝试通过classloader获取名为:

    // "META-INF/services/[服务全限定名]"的所有配置文件

    private boolean hasNextService() {

        if (nextName != null) {

            return true;

        }

        if (configs == null) {

            try {

                // 注意fullName的定义:"META-INF/services/[服务全限定名]"

                String fullName = PREFIX + service.getName();

                if (loader == null)

                    // 通过ClassLoader.getResources()获得资源URL集合

                    configs = ClassLoader.getSystemResources(fullName);

                else

                    configs = loader.getResources(fullName);

            catch (IOException x) {

                fail(service, "Error locating configuration files", x);

            }

        }

        // 如果pending为空,或者pending已经迭代到迭代器末尾,则尝试解析下一个services配置文件

        while ((pending == null) || !pending.hasNext()) {

            if (!configs.hasMoreElements()) {

                return false;

            }

            pending = parse(service, configs.nextElement());

        }

        // 对当前pending内容进行遍历,每一项代表services的一个实现类

        nextName = pending.next();

        return true;

    }

 

    @Override

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

        }

        // This cannot happen

        throw new Error();

    }

    @Override

    public void remove() {

        throw new UnsupportedOperationException();

    }

}

java的spi最典型的应用就是jdbc的驱动加载,jdbc4.0之前JDBC的开始,总是需要通过Class.forName显式实例化驱动,否则将找不到对应DB的驱动。但是JDBC4.0开始,这个显式的初始化不再是必选项了,它存在的意义只是为了向上兼容。

 

为什么JDBC4.0之后不需要了呢?答案就在下面的代码中。在系统启动时,DriverManager静态初始化时会通过ServiceLoader对所有jar包中被注册为 java.sql.Driver 服务的驱动实现类进行初始化,这样就避免了上面通过Class.forName手动初始化的繁琐工作。

public class DriverManager {

 

    // JDBC驱动注册中心,所有加载的JDBC驱动都注册在该CopyOnWriteArrayList中

    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

 

    ...

 

    /* Prevent the DriverManager class from being instantiated. */

    private DriverManager(){}

 

    /**

     * 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");

    }

 

    private static void loadInitialDrivers() {

        // 如果通过jdbc.drivers配置了驱动,则在本方法最后进行实例化

        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加载所有通过SPI方式注册的"java.sql.Driver"服务

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                // 遍历ServiceLoader实例进行强制实例化,因此除了遍历不做任何其他操作

                try{

                    while(driversIterator.hasNext()) {

                        driversIterator.next();

                    }

                catch(Throwable t) {

                    // Do nothing

                }

                return null;

                }

            }

        );

 

        println("DriverManager.initialize: jdbc.drivers = " + drivers);

 

        // 强制加载"jdbc.driver"环境变量中配置的DB驱动

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

            }

        }

    }

    ...

}

 

以mySql驱动为例看看驱动实例化时做了什么:

package com.mysql.jdbc;

 

public class Driver extends NonRegisteringDriver implements java.sql.Driver {

    //

    // Register ourselves with the DriverManager

    //

    static {

        try {

            // 向DriverManager注册自己

            java.sql.DriverManager.registerDriver(new Driver());

        catch (SQLException E) {

            throw new RuntimeException("Can't register driver!");

        }

    }

 

    /**

     * Construct a new driver and register it with DriverManager

     *

     * @throws SQLException

     *             if a database error occurs.

     */

    public Driver() throws SQLException {

        // Required for Class.forName().newInstance()

    }

}

因此,只要某个驱动以这种方式被引用并被上下文class loader加载,那么该驱动就会通过SPI的方式被自动发现和加载。实际使用时,Driver.getDriver(url)会通过DB连接url获取到正确的驱动并建立与DB的连接。

2、Dubbo的SPI扩展机制

 

 

3、SPI在业务代码中的应用

a1290123825 发布了24 篇原创文章 · 获赞 5 · 访问量 1196 私信 关注

标签:dubbo,return,service,迭代,扩展,public,SPI,ServiceLoader,加载
来源: https://blog.csdn.net/a1290123825/article/details/104126848

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有