前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊聊 Java SPI

聊聊 Java SPI

作者头像
程序猿杜小头
发布2022-12-01 21:38:11
8320
发布2022-12-01 21:38:11
举报
文章被收录于专栏:程序猿杜小头程序猿杜小头

The built-in service discovery mechanism in Java

Running with Java 11.0.10

SPI (Service Provider Interface) 是自 Java 1.6 引入的一种基于接口或抽象类的服务发现机制。得益于 Java SPI 机制,开发人员只需为第三方预留出 SPI 拓展接口,这样可以在不修改代码的前提下,通过增删第三方依赖来实现系统的灵活拓展。

要想成功地玩转 Java SPI,下面四个组件是缺一不可的:

① Service Provider Interface

服务供应商接口,即SPI拓展接口;只能是接口抽象类

② Service Provider

服务供应商,即针对SPI拓展接口提供SPI实现类的第三方;SPI实现类必须定义一个无参构造方法,否则报错:Unable to get public no-arg constructor

③ SPI Configuration File

首先,SPI配置文件必须贮存于classpath:/META-INF/services/目录下;其次,其必须采用UTF-8编码;此外,其必须以SPI拓展接口的完全限定名来命名;最后,SPI配置文件的内容应当为第三方SPI实现类的完全限定名。

④ ServiceLoader

ServiceLoader是JDK内置的SPI利器,主要负责读取SPI配置文件并将第三方SPI实现类加载到JVM中。

1. SPI应用案例解读

既然是亲儿子,SPI机制在JDK内部还是有若干应用场景的,其中大家最为熟悉的应该就是JDBC API了。众所周知,官方只是制定了一套数据库交互规范,秉持'让专业的人干专业的事'这一原则,官方并没有提供具体的实现,转而将实现逻辑交由各数据库厂商负责。在JDBC 4.0前后,分别颖现出两种编程范式,如下所示:

Before JDBC 4.0
Connection connection = null;
Statement statement = null;
try {
    // 加载驱动
    Class.forName("org.postgresql.Driver");
    connection = DriverManager.getConnection("jdbc:postgresql://HOST:PORT/DB", "USERNAME", "PASSWORD");
    statement = connection.createStatement();
    statement.execute("delete from tbl_user where user_id = 1");
} catch (SQLException e) {
    log.error("user deletion failure", e);
} finally {
    JdbcUtils.closeStatement(statement);
    JdbcUtils.closeConnection(connection);
}
After JDBC 4.0
Connection connection = null;
Statement statement = null;
try {
    connection = DriverManager.getConnection("jdbc:postgresql://HOST:PORT/DB", "USERNAME", "PASSWORD");
    statement = connection.createStatement();
    statement.execute("delete from tbl_user where user_id = 1");
} catch (SQLException e) {
    log.error("user deletion failure", e);
} finally {
    JdbcUtils.closeStatement(statement);
    JdbcUtils.closeConnection(connection);
}

显然,在JDBC 4.0之前,我们需要通过Class.forName()来手动加载指定厂商的数据库驱动;若后期更换数据库驱动,必须修改forName()方法中的驱动参数。而在JDBC 4.0之后,因为不再需要手动加载数据库驱动,顾而也就不涉及代码的修改了,这就是Java SPI带给我们的能力!

java.sql.Driver是JDK为第三方数据库厂商预留的SPI拓展接口,主要用于构建Connection。

public interface Driver {
    Connection connect(String url, Properties info);
    boolean acceptsURL(String url);
}

这里以PostgreSQL为例!org.postgresql:postgresql驱动包结构如下:

从上图来看,META-INF/services目录下的java.sql.Driver文件应该就是SPI配置文件了,其内容如下:

org.postgresql.Driver

显然,PostgreSQL作为数据库厂商,org.postgresql.Driver毫无保留地实现了java.sql.Driver接口,在其源码中有一静态初始化代码块,用于向java.sql.DriverManager注册自身实例。

public class Driver implements java.sql.Driver {
    private static Driver registeredDriver;
    // 静态初始化代码块
    static {
        try {
            if (Objects.nonNull(registeredDriver)) {
                throw new IllegalStateException("Driver is already registered.");
            }
            registeredDriver = new Driver();
            // 第三方厂商向DriverManager注册驱动
            DriverManager.registerDriver(registeredDriver);
        } catch (SQLException e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    @Override
    public Connection connect(String url, Properties info) {
        // 略
    }
    @Override
    public boolean acceptsURL(String url) {
        // 略
    }
}

接下来,自然要看看java.sql.DriverManager的源码了,其主要用来维护驱动实例;registerDriver()静态方法会将驱动实例添加到CopyOnWriteArrayList中,而deregisterDriver()静态方法又会将驱动实例从CopyOnWriteArrayList中移除;在getDriver()getConnection()这俩静态方法中,存在一段相同的逻辑,即通过ServiceLoader.load(Driver.class)来加载PostgreSQL针对java.sql.Driver接口提供的SPI实现类。关于类的加载,一般通过Class.forName()方法来实现,其会触发静态初始化代码块的执行,那也就是说org.postgresql.Driver中的静态初始化代码块是在这里被触发执行的。但为什么ServiceLoader.load(Driver.class)执行完之后,还要有一个空的迭代逻辑呢?

public class DriverManager {
    // 已注册JDBC驱动列表
    private static final CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    // 锁
    private static final Object lockForInitDrivers = new Object();
    // 第三方厂商驱动是否已经加载完毕,若已加载,则后期不再重复加载
    private static volatile boolean driversInitialized;
    // 私有构造方法
    private DriverManager(){}
    // 获取Driver实例
    public static Driver getDriver(String url) {
        if (!driversInitialized) {
            synchronized (lockForInitDrivers) {
                if (!driversInitialized) {
                    // ServiceLoader.load()并不会立即去加载第三方厂商驱动,其只是返回一个ServiceLoader实例而已
                    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                    // 生成Iterator实例
                    Iterator<Driver> driversIterator = loadedDrivers.iterator();
                    // 延迟加载,即在迭代时才真正去加载第三方厂商驱动
                    while (driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                    driversInitialized = true;
                }
            }
        }
        Driver driver = null;
        for (DriverInfo driverInfo : registeredDrivers) {
            if (driverInfo.driver.acceptsURL(url)) {
                driver = driverInfo.driver;
                break;
            }
        }
        return driver;
    }

    // 获取Connection实例
    public static Connection getConnection(String url, String user, String password) {
        if (!driversInitialized) {
            synchronized (lockForInitDrivers) {
                if (!driversInitialized) {
                    // ServiceLoader.load()并不会立即去加载第三方厂商驱动,其只是返回一个ServiceLoader实例而已
                    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                    // 生成Iterator实例
                    Iterator<Driver> driversIterator = loadedDrivers.iterator();
                    // 延迟加载,即在迭代时才真正去加载第三方厂商驱动
                    while (driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                    driversInitialized = true;
                }
            }
        }
        Properties info = MapUtils.toProperties(ImmutableMap.of("user", user, "password", password));
        Connection connection = null;
        for (DriverInfo driverInfo : registeredDrivers) {
            // 第三方厂商会实现java.sql.Driver接口,实现其connect()方法
            connection = driverInfo.driver.connect(url, info);
            if (connection != null) {
                break;
            }
        }
        return connection;
    }

    // 注册JDBC驱动
    public static void registerDriver(Driver driver) {
        registeredDrivers.addIfAbsent(new DriverInfo(driver));
    }
    // 注销JDBC驱动
    public static void deregisterDriver(Driver driver) {
        DriverInfo driverInfo = new DriverInfo(driver);
        synchronized (lockForInitDrivers) {
            if (registeredDrivers.contains(driverInfo)) {
                registeredDrivers.remove(driverInfo);
            }
        }
    }
}

带着刚才的疑问,进入ServiceLoader的源码一探究竟吧。盯着load()方法看了许久,哥陷入深深的沉思:这玩意儿就是单纯地返回一个ServiceLoader实例而已,并没有Class.forName()的身影啊,莫非上述空的迭代逻辑才是真正用来执行类加载的吗?iterator()方法生成了一个LazyClassPathLookupIterator迭代器,也许玄机就在这个迭代器中。

public final class ServiceLoader<S> implements Iterable<S> {
    private final Class<S> service;
    private final String serviceName;
    private final ClassLoader loader;
    private Iterator<ServiceLoader.Provider<S>> lookupIterator1;
    private final List<S> instantiatedProviders = new ArrayList<>();

    public static interface Provider<S> extends Supplier<S> {
        Class<? extends S> type();
        @Override S get();
    }
    private ServiceLoader(Class<?> caller, Class<S> svc, ClassLoader cl) {
        this.service = svc;
        this.serviceName = svc.getName();
        this.loader = cl;
    }

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
    }
    @Override
    public Iterator<S> iterator() {
        if (lookupIterator1 == null) {
            Iterator<Provider<S>> iterator = new LazyClassPathLookupIterator<>();
            lookupIterator1 = new Iterator<Provider<S>>() {
                @Override
                public boolean hasNext() {
                    return iterator.hasNext();
                }
                @Override
                public Provider<S> next() {
                    if (iterator.hasNext()) {
                        return iterator.next();
                    } else {
                        throw new NoSuchElementException();
                    }
                }
            };
        }
        return new Iterator<S>() {
            int index;
            @Override
            public boolean hasNext() {
                if (index < instantiatedProviders.size()) {
                    return true;
                }
                return lookupIterator1.hasNext();
            }
            @Override
            public S next() {
                S next;
                if (index < instantiatedProviders.size()) {
                    next = instantiatedProviders.get(index);
                } else {
                    next = lookupIterator1.next().get();
                    instantiatedProviders.add(next);
                }
                index++;
                return next;
            }
        };
    }
}

继续跟进,在查阅LazyClassPathLookupIterator的源码后,脑海中的疑问也随之烟消云散了。nextProviderClass()无疑是核心逻辑所在,它首先一次性读取SPI配置文件,然后在每一次迭代时通过Class.forName()方法来加载SPI实现类。

private final class LazyClassPathLookupIterator<T> implements Iterator<Provider<T>> {
    static final String PREFIX = "META-INF/services/";

    Enumeration<URL> configs;
    Iterator<String> pending;
    Provider<T> nextProvider;
    ServiceConfigurationError nextError;

    LazyClassPathLookupIterator() {}

    private Class<?> nextProviderClass() {
        if (configs == null) {
            String fullName = PREFIX + service.getName();
            configs = loader.getResources(fullName);
        }
        while ((pending == null) || !pending.hasNext()) {
            if (!configs.hasMoreElements()) {
                return null;
            }
            pending = parse(configs.nextElement());
        }
        String cn = pending.next();
        try {
            return Class.forName(cn, false, loader);
        } catch (ClassNotFoundException x) {
            return null;
        }
    }

    private boolean hasNextService() {
        while (nextProvider == null && nextError == null) {
            try {
                Class<?> clazz = nextProviderClass();
                if (clazz == null) {
                    return false;
                }

                if (service.isAssignableFrom(clazz)) {
                    Class<? extends S> type = (Class<? extends S>) clazz;
                    Constructor<? extends S> ctor = (Constructor<? extends S>)getConstructor(clazz);
                    ProviderImpl<S> p = new ProviderImpl<S>(service, type, ctor);
                    nextProvider = (ProviderImpl<T>) p;
                } else {
                    fail(service, clazz.getName() + " not a subtype");
                }
            } catch (ServiceConfigurationError e) {
                nextError = e;
            }
        }
        return true;
    }

    private Provider<T> nextService() {
        if (!hasNextService()) {
            throw new NoSuchElementException();
        }

        Provider<T> provider = nextProvider;
        if (provider != null) {
            nextProvider = null;
            return provider;
        } else {
            ServiceConfigurationError e = nextError;
            assert e != null;
            nextError = null;
            throw e;
        }
    }

    @Override
    public boolean hasNext() {
        return hasNextService();
    }
    @Override
    public Provider<T> next() {
        return nextService();
    }
}

实际上,ServiceLoader使用了延迟加载技术,即在需要时才会加载对象或数据;一般,当对象创建的成本非常高且对象的使用非常少时,延迟加载是必不可少的。

2. 入门实战

2.1 json-serializer

2.1.1 定义SPI拓展接口
package io.github.serializer;

public interface JsonSerializer {
    void serialize(Object obj);
}
2.1.2 定义静态工厂类

JsonSerializerManager是一个静态工厂类,它的构造方法是私有的;getJsonSerializer()静态方法可以根据特定厂商名称来获取相应的JsonSerializer实例。

package io.github.serializer;

public class JsonSerializerManager {
    private static final CopyOnWriteArrayList<JsonSerializerWrapper> REGISTERED_JSON_SERIALIZER = new CopyOnWriteArrayList<>();
    private static final AtomicBoolean IS_INITIALIZED = new AtomicBoolean(false);

    private JsonSerializerManager() {}

    public static void registerJsonSerializer(JsonSerializerWrapper jsonSerializer) {
        REGISTERED_JSON_SERIALIZER.addIfAbsent(jsonSerializer);
    }
    public static void deregisterJsonSerializer(JsonSerializerWrapper jsonSerializer) {
        REGISTERED_JSON_SERIALIZER.remove(jsonSerializer);
    }

    public static JsonSerializer getJsonSerializer(String manufactureName) {
        if (!IS_INITIALIZED.get()) {
            ServiceLoader<JsonSerializer> jsonSerializerServiceLoader = ServiceLoader.load(JsonSerializer.class);
            Iterator<JsonSerializer> jsonSerializerIterator  = jsonSerializerServiceLoader.iterator();
            while (jsonSerializerIterator.hasNext()) {
                jsonSerializerIterator.next();
            }
        }

        return REGISTERED_JSON_SERIALIZER
                .stream()
                .collect(Collectors.toMap(
                        JsonSerializerWrapper::getManufactureName,
                        JsonSerializerWrapper::getJsonSerializer)
                )
                .get(manufactureName);
    }
}

JsonSerializerWrapper主要用于包装JsonSerializer,重点关注equals()hashCode()中的逻辑。

package io.github.serializer;

@Getter
@Setter
public class JsonSerializerWrapper {
    private String manufactureName;
    private JsonSerializer jsonSerializer;

    @Override
    public boolean equals(Object other) {
        return (other instanceof JsonSerializerWrapper)
                && this.jsonSerializer == ((JsonSerializerWrapper) other).jsonSerializer;
    }
    @Override
    public int hashCode() {
        return jsonSerializer.hashCode();
    }
}

2.2 alibaba-json-serializer

2.2.1 引入json-serializer依赖
<dependency>
    <groupId>io.github</groupId>
    <artifactId>json-serializer</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
2.2.2 实现SPI拓展接口
package com.alibaba.json.serializer.privider;

public class AlibabaFastjsonSerializer implements JsonSerializer {
    static {
        JsonSerializerWrapper wrapper = new JsonSerializerWrapper();
        wrapper.setManufactureName("alibaba");
        wrapper.setJsonSerializer(new AlibabaFastjsonSerializer());
        JsonSerializerManager.registerJsonSerializer(wrapper);
    }

    @Override
    public void serialize(Object obj) {
        System.out.println("Alibaba提供的json序列化方案:fastjson");
    }
}
2.2.3 编写SPI配置文件
com.alibaba.json.serializer.privider.AlibabaFastjsonSerializer

2.3 fasterxml-json-serializer

2.3.1 引入json-serializer依赖
<dependency>
    <groupId>io.github</groupId>
    <artifactId>json-serializer</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
2.3.2 实现SPI拓展接口
package org.fasterxml.json.serializer.privider;

public class FasterxmlJacksonSerializer implements JsonSerializer {
    static {
        JsonSerializerWrapper wrapper = new JsonSerializerWrapper();
        wrapper.setManufactureName("fasterxml");
        wrapper.setJsonSerializer(new FasterxmlJacksonSerializer());
        JsonSerializerManager.registerJsonSerializer(wrapper);
    }

    @Override
    public void serialize(Object obj) {
        System.out.println("Fasterxml提供的json序列化方案:jackson");
    }
}
2.3.3 编写SPI配置文件
org.fasterxml.json.serializer.privider.FasterxmlJacksonSerializer

2.4 json-serializer-app

2.4.1 引入第三方SPI实现类的依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>json-serializer</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>org.fasterxml</groupId>
    <artifactId>json-serializer</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
2.4.2 运行
public class JsonSerializerApp {
    public static void main(String[] args) {
        JsonSerializer jsonSerializer = JsonSerializerManager.getJsonSerializer("alibaba");
        jsonSerializer.serialize(new Object());
        jsonSerializer = JsonSerializerManager.getJsonSerializer("fasterxml");
        jsonSerializer.serialize(new Object());
    }
}

运行结果如下所示:

Alibaba提供的json序列化方案:fastjson
Fasterxml提供的json序列化方案:jackson

3. 总结

SPI的确很简单,但如何更优雅地设计与应用呢?个人觉得JDK中java.sql.DriverManager是一个很好的参照。此外,Java SPI机制有一个较为明显的缺点:无法按需加载指定第三方SPI实现类!!!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-07-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序猿杜小头 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • The built-in service discovery mechanism in Java
    • 1. SPI应用案例解读
      • 2. 入门实战
        • 2.1 json-serializer
        • 2.2 alibaba-json-serializer
        • 2.3 fasterxml-json-serializer
        • 2.4 json-serializer-app
      • 3. 总结
      相关产品与服务
      数据库
      云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档