今天小编给大家分享一下Java SPI机制及其应用场景是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。
SPI全称Service Provider Interface,是Java提供的一种服务发现机制。实现服务接口和服务实现的解耦。
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加载接口实现,进行测试
步骤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); } }
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包依赖去掉就可以,无需改代码
总结如下:
调用ServiceLoader.load()
,创建一个ServiceLoader
实例对象
创建LazyIterator
实例对象lookupIterator
通过lookupIterator.hasNextService()
方法读取固定目录META-INF/services/
下面service全限定名文件,放在Enumeration
对象configs
中
解析configs得到迭代器对象Iterator<String> pending
通过lookupIterator.nextService()
方法初始化读取到的实现类,通过Class.forName()
初始化
从上面的步骤可以总结以下几点
实现类工程必须创建定目录META-INF/services/
,并创建service全限定名文件,文件内容是实现类全限定名
实现类必须有一个无参构造函数
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机制及其应用场景是什么”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。