聊聊 Java SPI

Java
309
0
0
2022-12-20

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驱动包结构如下:

img

从上图来看,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

img

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

img

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

img

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

img

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实现类!!!