JAVA SPI机制概述

SPI概述

1.SPI概念

①全称:Service Provider Interface
②是什么:是Java提供的一套用来被第三方实现或者扩展接口,从JDBC4.0,JDK6开始Java实现了SPI机制
③作用:用来启用框架扩展和替换组件,为这些被扩展的API寻找服务实现

2.SPI和API区别

概念实现方调用方使用人员
API制定接口并完成对接口的实现仅仅依赖接口调用,且无权选择不同实现应用开发人员
SPI制定接口规范,提供给外部来实现,在调用时则选择自己需要的外部实现框架扩展人员使用

3.SPI简单示例

①定义一个接口

package com.lemon.spi;

/**
 * @author lemon
 * @Desc
 * @date 2020-09-22 11:14
 */
public interface RenShu {
   void renShu();
}

②定义两个接口实现类

package com.lemon.spi;

/**
 * @author lemon
 * @Desc
 * @date 2020-09-22 11:18
 */
public class ShenLuoTianZheng implements RenShu{

  @Override
  public void renShu() {
    System.out.println("神罗天征1!");
  }
}
package com.lemon.spi;

/**
 * @author lemon
 * @Desc
 * @date 2020-09-22 11:19
 */
public class WanXiangTianYin implements RenShu{

  @Override
  public void renShu() {
    System.out.println("万象天引1!");
  }
}

③在resources下(非固定,只要相对路径是META-INF/services/接口全路径名)新建package META_INF和services,在services下新建和接口名相同的文件
在这里插入图片描述
④新建测试类

package com.lemon.spi;

import java.util.ServiceLoader;

/**
 * @author lemon
 * @Desc
 * @date 2020-09-22 11:10
 */
public class Test {

  public static void main(String[] args) {
    ServiceLoader<RenShu> renShus = ServiceLoader.load(RenShu.class);
    for(RenShu renShu:renShus){
      renShu.renShu();
    }
  }
}

在这里插入图片描述

4.SPI源码分析

①ServiceLoader类实现流程
在这里插入图片描述

核心代码
①:实例化ServiceLoader及私有内部类LazyIterator
    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

    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();
    }
②进行迭代,实际调用的是LazyIterator的hasNextService与nextService方法
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
        }

5.JDBC的SPI

在这里插入图片描述
com.mysql.cj.jdbc.Driver是其默认的实现类

public class DriverManager {


    // List of registered JDBC drivers
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    private static volatile int loginTimeout = 0;
    private static volatile java.io.PrintWriter logWriter = null;
    private static volatile java.io.PrintStream logStream = null;
    // Used in println() to synchronize logWriter
    private final static  Object logSync = new Object();

    /* 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 {
        //此处进行spi机制调用
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

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

6.SPI优缺点

优点:
1.不需要改动源码就可以实现扩展,解耦。
2.实现扩展对原来的代码几乎没有侵入性。
3.只需要添加配置就可以实现扩展,符合开闭原则。

缺点:
1.不能按需加载,需要遍历所有的实。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。
2.ServiceLoader 线程不安全。

参考链接: https://www.cnblogs.com/jy107600/p/11464985.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值