SPI机制浅谈

1.简述

SPI(Service Provider Interface)是一种服务发现机制,能够动态替换与发现服务,将接口与实现类解耦

SPI机制的实现方式是,在配置文件中定义好某个接口的实现类,然后根据接口名找到配置文件中的实现类名,从而加载这个实现类

SPI机制的优点:

1)解耦,将接口和具体实现分离开,使得应用程序可以根据自身业务需求启用不同的第三方实现服务

2)提高框架的扩展性,避免将实现类硬编码在代码中

2.基本使用

1)SPI机制基于接口实现,因此首先创建一个接口

package com.limin.study.spi;

public interface IService {
    void doSomething();
}

2)在resources目录下创建META-INF/services文件夹,其中放入以全限定接口名命名的文件,例如文件名称为com.limin.study.spi.IService

3)创建接口实现类

package com.limin.study.other;

import com.limin.study.spi.IService;

public class MyServiceImpl1 implements IService {
    @Override
    public void doSomething() {
        System.out.println("MyServiceImpl1 do something...");
    }
}
package com.limin.study.other;

import com.limin.study.spi.IService;

public class MyServiceImpl2 implements IService {
    @Override
    public void doSomething() {
        System.out.println("MyServiceImpl2 do something...");
    }
}

4)文件内容为该接口的实现类的全限定类名,例如:

com.limin.study.other.MyServiceImpl1
com.limin.study.other.MyServiceImpl2

5)测试通过SPI机制调用实现类doSomething(),代码如下:

package com.limin.study.spi;

import java.util.Iterator;
import java.util.ServiceLoader;

public class SpiTest {
    public static void main(String[] args) {
        ServiceLoader<IService> loader = ServiceLoader.load(IService.class);
        Iterator<IService> iterator = loader.iterator();
        while (iterator.hasNext()) {
            IService serviceImpl = iterator.next();
            serviceImpl.doSomething();
        }
    }
}

6)测试结果如下:

MyServiceImpl1 do something...
MyServiceImpl2 do something...

3.源码分析

SPI的核心类是ServiceLoader,核心原理分析如下:

1)通过ServiceLoader.load()创建ServiceLoader对象过程见源码:

public final class ServiceLoader<S> implements Iterable<S> {
    // 文件名称前缀,因此需要将文件放在resources/META-INF/services目录下
    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 LazyIterator lookupIterator;

    public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
        return new ServiceLoader<>(service, loader);
    }

    public static <S> ServiceLoader<S> load(Class<S> service) {
        // 获取线程上下文类加载器,一般为AppClassLoader
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        // 校验传入的接口类不能为空
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        // 如果传入的类加载器为空,则设置为系统类加载器,一般也为AppClassLoader
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

    public void reload() {
        providers.clear();
        // 创建迭代器对象
        lookupIterator = new LazyIterator(service, loader);
    }

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

        // 迭代器中其他方法
    }

    // 其他方法...
}

由于ServiceLoader位于rt.jar中,因此该类是被BootStrapClassLoader加载的,而ServiceLoader需要加载我们自定义的类,而BootStrapClassLoader是加载不了类路径下定义的类的,所以默认情况下,源码中通过Thread.currentThread().getContextClassLoader()获取了线程上线文类加载器,一般为AppClassLoader,使用线程上线文类加载器就能够加载我们的实现类了

2)ServiceLoader实现了Iterator接口,通过loader.iterator()可以获取迭代器对象

public Iterator<S> iterator() {
    // 返回了一个Iterator的实现类
    return new Iterator<S>() {
        // 缓存
        Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();

        public boolean hasNext() {
            if (knownProviders.hasNext())
                return true;
            // 如果缓存中没有,调用的是LazyIterator中的方法
            return lookupIterator.hasNext();
        }

        public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            // 如果缓存中没有,调用的是LazyIterator中的方法
            return lookupIterator.next();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }
    };
}

3)通过迭代器hasNext()next()获取实现类

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() {
        if (nextName != null) {
            return true;
        }
        if (configs == null) {
            try {
                // 1.获取文件名称,文件名为:resources/META-INF/services/全限定接口名
                String fullName = PREFIX + service.getName();
                if (loader == null)
                    configs = ClassLoader.getSystemResources(fullName);
                else
                    // 2.根据文件名称获取当前路径及jar包中的配置的SPI文件路径
                    configs = loader.getResources(fullName);
            } catch (IOException x) {
                fail(service, "Error locating configuration files", x);
            }
        }
        while ((pending == null) || !pending.hasNext()) {
            if (!configs.hasMoreElements()) {
                return false;
            }
            // 解析SPI配置文件,解析的过程就是按行读取文件内容
            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 {
            // 1.加载实现类
            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 {
            // 2.创建实现类对象
            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();
    }

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

}

从源码可以看出,创建实现类的过程如下:

1.读取配置文件解析出实现类名称

2.通过反射创建实现类对象

4.框架中的使用

SPI机制在很多框架中都有使用,例如JDBC加载驱动、SpringBoot自动配置等

4.1 加载MySQL驱动类

1)我们知道使用JDBC操作数据库时需要先通过DriverManager.getConnection获取连接,例如:

private Connection getConnection() {
    Connection connection;
    try {
        connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&serverTimezone=UTC", "root", "123456");
    } catch (SQLException e) {
        LOGGER.error("get connection failed", e);
        return null;
    }
    return connection;
}

2)DriverManager中静态代码块会通过SPI加载MySQL驱动,java.sql.DriverManager源码如下:

public class DriverManager {
    static {
        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;
        }

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                // 1.通过SPI机制在类路径及jar包中META-INF/services查找java.sql.Driver文件
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try{
                    while(driversIterator.hasNext()) {
                        // 2.加载类
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                    // Do nothing
                }
                return null;
            }
        });

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

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

    // 其他代码省略...
}

3)ServiceLoader.load(Driver.class)查找META-INF/services/java.sql.Driver文件

MySQL驱动包中SPI配置文件如下图所示:

driversIterator.next()会调用迭代器中的nextService(),从而根据实现类的全限定类名调用Class.forName(cn, false, loader)加载com.mysql.cj.jdbc.Driver驱动类

4)驱动类com.mysql.cj.jdbc.Driver源码如下:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            // 注册驱动到DriverManager的成员变量registeredDrivers中
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

可以看到静态代码块中会调用DriverManager.registerDriver注册驱动

5)注册成功后,调用getConnection()时就能通过对应的驱动建立连接,DriverManager#getConnection源码如下:

private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
    // 省略其他代码...

    for (DriverInfo aDriver : registeredDrivers) {
        if (isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                println("    trying " + aDriver.driver.getClass().getName());
                // 通过注册的驱动对象建立连接
                Connection con = aDriver.driver.connect(url, info);
                if (con != null) {
                    println("getConnection returning " + aDriver.driver.getClass().getName());
                    return (con);
                }
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }
        } else {
            println("    skipping: " + aDriver.getClass().getName());
        }
    }

    // 省略其他代码...
}

4.2 加载表达式处理引擎

在项目开发中,我们可能会使用到Hutool工具包中的ExpressionEngine执行表达式计算,比如说需要执行SpEL表达式,可以调用ExpressionUtil#eval,源码如下所示:

public static Object eval(String expression, Map<String, Object> context) {
    return eval(expression, context, ListUtil.empty());
}

public static Object eval(String expression, Map<String, Object> context, Collection<Class<?>> allowClassSet) {
    return getEngine().eval(expression, context, allowClassSet);
}

public static ExpressionEngine getEngine() {
    return ExpressionFactory.get();
}

其中获取具体引擎实现类是通过SPI机制实现的,ExpressionFactory.get()会调用ExpressionFactory#doCreate

private static ExpressionEngine doCreate() {
    final ExpressionEngine engine = ServiceLoaderUtil.loadFirstAvailable(ExpressionEngine.class);
    if(null != engine){
        return engine;
    }
    throw new ExpressionException("No expression jar found ! Please add one of it to your project !");
}

进入到ServiceLoaderUtil.loadFirstAvailable方法,返回第一个能够加载成功的实现类

public static <T> T loadFirstAvailable(Class<T> clazz) {
    final Iterator<T> iterator = load(clazz).iterator();
    while (iterator.hasNext()) {
        try {
            // 内部会调用class.forName加载类
            return iterator.next();
        } catch (ServiceConfigurationError ignore) {
            // ignore
        }
    }
    return null;
}

public static <T> ServiceLoader<T> load(Class<T> clazz) {
    return load(clazz, null);
}

public static <T> ServiceLoader<T> load(Class<T> clazz, ClassLoader loader) {
    return ServiceLoader.load(clazz, ObjectUtil.defaultIfNull(loader, ClassLoaderUtil::getClassLoader));
}

配置文件如下所示:

在每个具体实现类的构造函数中都会创建对应的引擎解析类,这些解析类依赖其他的jar包,如果加载了这些jar包就能顺利创建出对应的实现类,例如SpELEngine实例化时会创建SpelExpressionParser,这个类来自于spring-expression

public class SpELEngine implements ExpressionEngine {
    private final ExpressionParser parser;

    public SpELEngine(){
        parser = new SpelExpressionParser();
    }

    @Override
    public Object eval(String expression, Map<String, Object> context, Collection<Class<?>> allowClassSet) {
        final EvaluationContext evaluationContext = new StandardEvaluationContext();
        context.forEach(evaluationContext::setVariable);
        return parser.parseExpression(expression).getValue(evaluationContext);
    }
}

即如果类加载了spring-expression的jar包,那么就能创建SpELEngine引擎处理类

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lm_ylj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值