温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Java SPI机制及其应用场景是什么

发布时间:2023-04-26 11:55:00 来源:亿速云 阅读:139 作者:iii 栏目:开发技术

今天小编给大家分享一下Java SPI机制及其应用场景是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

一、什么是SPI

SPI全称Service Provider Interface,是Java提供的一种服务发现机制。实现服务接口和服务实现的解耦。

Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,实现不修改任何代码的情况下切换不同的实现。

Java SPI机制及其应用场景是什么

二、使用场景

很多开源第三方jar包都有基于SPI的实现,在jar包META-INF/services中都有相关配置文件。

如下几个常见的场景:

1)JDBC加载不同类型的数据库驱动

2)Slf4j日志框架

3)Dubbo框架

三、使用步骤示例

假设有个上传附件的场景,可以上传到不同的云储存(如阿里云OSS,亚马逊S3),那么基于Java SPI机制的实现,我们应该做如下步骤:

步骤1、创建4个工程

SPI的核心就是实现服务接口和服务实现的解耦,所以我们不能将接口和实现放在一个工程里面。

  • spi-file-upload,在这定义附件上传接口IFileUpload

  • spi-file-upload-oss,实现附件上传到oss,FileUploadOss实现接口IFileUpload

  • spi-file-upload-s3,实现附件上传到s3,FileUploadS3实现接口IFileUpload

  • spi-file-upload-test,通过ServiceLoader加载接口实现,进行测试

Java SPI机制及其应用场景是什么

步骤2、 在工程spi-file-upload创建接口IFileUpload

接口代码示例

package com.hj.test.file.oss;
/**
 * 文件上传接口
 */
public interface IFileUpload {
    void upload(String fileName);
}

步骤3、分别创建接口实现类FileUploadOss、FileUploadS3

1)FileUploadOss

在工程的 spi-file-upload-oss 的 resources目录下创建目录META-INF/services,并在该目录中创建以接口IFileUpload全路径命名的文件(com.hj.test.file.IFileUpload),文件内容是接口实现类 com.hj.test.file.oss.FileUploadOss

package com.hj.test.file.oss;
import com.hj.test.file.IFileUpload;
public class FileUploadOss implements IFileUpload {
    @Override
    public void upload(String fileName) {
        System.out.println("上传到阿里云OSS..." + fileName);
    }
}

Java SPI机制及其应用场景是什么

2)FileUploadS3

在工程的 spi-file-upload-s3 的 resources目录下创建目录META-INF/services,并在该目录中创建以接口IFileUpload全路径命名的文件(com.hj.test.file.IFileUpload),文件内容是接口实现类 com.hj.test.file.s3.FileUploadS3

package com.hj.test.file.s3;
import com.hj.test.file.IFileUpload;
public class FileUploadS3 implements IFileUpload {
    @Override
    public void upload(String fileName) {
        System.out.println("上传到亚马逊s3..." + fileName);
    }
}

步骤4、在工程spi-file-upload-test中创建测试调用类

1)在pom.xml中引入3个依赖工程

<dependency>
    <groupId>com.hj</groupId>
    <artifactId>spi-file-upload</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>com.hj</groupId>
    <artifactId>spi-file-upload-oss</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>com.hj</groupId>
    <artifactId>spi-file-upload-s3</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

2)测试实现

package com.hj.test.file.test;
import com.hj.test.file.IFileUpload;
import java.util.Iterator;
import java.util.ServiceLoader;
public class FileTest{
    public static void main(String[] args) {
        ServiceLoader<IFileUpload> loader = ServiceLoader.load(IFileUpload.class);
        for(Iterator<IFileUpload> it = loader.iterator(); it.hasNext();){
            IFileUpload file = it.next();
            file.upload("测试文件上传");
        }
    }
}

控制台输出

上传到阿里云OSS...测试文件上传
上传到亚马逊s3...测试文件上传

如果哪天不想要传到s3,只需要把jar包依赖去掉就可以,无需改代码

四、原理解析

1、SPI的核心就是ServiceLoader.load()方法

总结如下:

  1. 调用ServiceLoader.load(),创建一个ServiceLoader实例对象

  2. 创建LazyIterator实例对象lookupIterator

  3. 通过lookupIterator.hasNextService()方法读取固定目录META-INF/services/下面service全限定名文件,放在Enumeration对象configs

  4. 解析configs得到迭代器对象Iterator<String> pending

  5. 通过lookupIterator.nextService()方法初始化读取到的实现类,通过Class.forName()初始化

从上面的步骤可以总结以下几点

  • 实现类工程必须创建定目录META-INF/services/,并创建service全限定名文件,文件内容是实现类全限定名

  • 实现类必须有一个无参构造函数

2、ServiceLoader核心代码介绍

public final class ServiceLoader<S>
    implements Iterable<S>
{
    private static final String PREFIX = "META-INF/services/";
    // The class or interface representing the service being loaded
    private final Class<S> service;
    // The class loader used to locate, load, and instantiate providers
    private final ClassLoader loader;
    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;
    // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;
public static <S> ServiceLoader<S> load(Class<S> service,
                                        ClassLoader loader)
{
    return new ServiceLoader<>(service, loader);
}
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();
}

通过方法iterator()生成迭代器,内部调用LazyIterator实例对象

public Iterator<S> iterator() {
    return new Iterator<S>() {
        Iterator<Map.Entry<String,S>> knownProviders
            = providers.entrySet().iterator();
        public boolean hasNext() {
            if (knownProviders.hasNext())
                return true;
            return lookupIterator.hasNext();
        }
        public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            return lookupIterator.next();
        }
        public void remove() {
            throw new UnsupportedOperationException();
        }
    };
}

内部类LazyIterator,读取配置文件META-INF/services/

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

以上就是“Java SPI机制及其应用场景是什么”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注亿速云行业资讯频道。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI