共计 9222 个字符,预计需要花费 24 分钟才能阅读完成。
[TOC]
1.SPI 是什么?
SPI
,即是 Service Provider Interface
,是一种服务提供(接口实现)发现机制,能够通过 ClassPath 门路下的META-INF/Service
文件查找文件,加载外面定义的类。
个别能够用来启用框架拓展和替换组件,比方在最常见的数据库连贯 JDBC 中,java.sql.Driver
, 不同的数据库产商能够对接口做不一样的实现,然而 JDK 怎么晓得他人有哪些实现呢?这就须要 SPI
, 能够查找到接口的实现,对其进行操作。
用两个字解释:解耦。
2. 如何应用 SPI 来提供自定义服务?
咱们来写一个简略的例子:
整个我的项目构造:
-
SPI-Project
:maven
我的项目DBInterface
:maven
我的项目,parent 是SPI-Project
,定义了一个接口com.aphysia.sqlserver.DBConnectionService
, 本人不做实现。MysqlConnection
:prarent 是SPI-Project
, 实现了接口DBConnectionService
,也就是MysqlConnectionServiceImpl
SqlServerConnection
:prarent 也是SPI-Project
,实现了DBConnectionService
, 也就是SqlServerConnectionServiceImpl
WebProject
: 测试项目,模仿 web 我的项目外面应用数据库驱动。
不论是 MySqlConnection
还是 SqlServerConnection
两个 module 中,都是去实现了 DBInterface
的接口,并且在 resource/META-INF/services
下都须要申明所实现的类,文件名就是实现的接口全限定名com.aphysia.sql.DBConnectionService
,文件外面就是具体的实现类的全限定名,比方:com.aphysia.mysql.MysqlConnectionServiceImpl
SPI-Project 的 pom 文件:
<?xml version="1.0" encoding="UTF-8"?> | |
<project xmlns="http://maven.apache.org/POM/4.0.0" | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
<modelVersion>4.0.0</modelVersion> | |
<groupId>com.aphysia</groupId> | |
<artifactId>SPI-Project</artifactId> | |
<packaging>pom</packaging> | |
<version>1.0-SNAPSHOT</version> | |
<modules> | |
<module>DbInterface</module> | |
<module>MySqlConection</module> | |
<module>SqlServerConnection</module> | |
<module>WebProject</module> | |
</modules> | |
</project> |
2.1 DBInterface 定义接口
DBInterface 是 SPIProject 的一个 module,次要是定义一个标准(接口),不做任何实现。
pom 文件如下:
<?xml version="1.0" encoding="UTF-8"?> | |
<project xmlns="http://maven.apache.org/POM/4.0.0" | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
<parent> | |
<artifactId>SPI-Project</artifactId> | |
<groupId>com.aphysia</groupId> | |
<version>1.0-SNAPSHOT</version> | |
</parent> | |
<modelVersion>4.0.0</modelVersion> | |
<artifactId>DbInterface</artifactId> | |
</project> |
定义的接口(模仿了 java 提供的数据库驱动的情景,定义了驱动标准):DBConnectionService.java
package com.aphysia.sql; | |
public interface DBConnectionService {void connect(); | |
} |
2.2 模仿 Mysql 实现驱动
接口的第一种实现,相当于模仿第三方 Mysql
对接口做了本人的拓展:
pom 文件:
<?xml version="1.0" encoding="UTF-8"?> | |
<project xmlns="http://maven.apache.org/POM/4.0.0" | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
<parent> | |
<artifactId>SPI-Project</artifactId> | |
<groupId>com.aphysia</groupId> | |
<version>1.0-SNAPSHOT</version> | |
</parent> | |
<modelVersion>4.0.0</modelVersion> | |
<artifactId>MySqlConection</artifactId> | |
<dependencies> | |
<dependency> | |
<groupId>com.aphysia</groupId> | |
<artifactId>DbInterface</artifactId> | |
<version>1.0-SNAPSHOT</version> | |
</dependency> | |
</dependencies> | |
</project> |
实现了后面定义的接口:
MysqlConnectionServiceImpl
package com.aphysia.mysql; | |
import com.aphysia.sqlserver.DBConnectionService; | |
public class MysqlConnectionServiceImpl implements DBConnectionService {public void connect() {System.out.println("mysql 正在连接..."); | |
} | |
} |
申明实现,在 resource/META-INF.services/
下定义一个文件,名为com.aphysia.sql.DBConnection
, 外面内容是:
com.aphysia.mysql.MysqlConnectionServiceImpl
2.3 模仿 SqlServer 实现驱动
SqlServerConnection
也是一个 module,pom 文件如下:
<?xml version="1.0" encoding="UTF-8"?> | |
<project xmlns="http://maven.apache.org/POM/4.0.0" | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
<parent> | |
<artifactId>SPI-Project</artifactId> | |
<groupId>com.aphysia</groupId> | |
<version>1.0-SNAPSHOT</version> | |
</parent> | |
<modelVersion>4.0.0</modelVersion> | |
<artifactId>SqlServerConnection</artifactId> | |
<dependencies> | |
<dependency> | |
<groupId>com.aphysia</groupId> | |
<artifactId>DbInterface</artifactId> | |
<version>1.0-SNAPSHOT</version> | |
</dependency> | |
</dependencies> | |
</project> |
接口的第二种实现,相当于第三方 SqlServer
对接口做了本人的拓展:SqlServerConnectionServiceImpl
package com.aphysia.sqlserver; | |
public class SqlServerConnectionServiceImpl implements DBConnectionService {public void connect() {System.out.println("sqlServer 正在连接..."); | |
} | |
} |
申明实现,在 resource/META-INF.services/
下定义一个文件,名为com.aphysia.sql.DBConnection
, 外面内容是:
com.aphysia.sqlserver.SqlServerConnectionServiceImpl
2.4 模仿用户应用不同驱动
下面两种不同的接口实现,留神须要在 resource 下申明,文件名是基类的全限定名,外面内容是具体实现类的全限定名
而咱们本人应用我的项目的时候呢?必定是须要哪一个驱动就引入哪一个驱动的 jar 包。
比方咱们在 webProject 中导入两种实现:MysqlConnection
和SqlServerConnection
:
<dependencies> | |
<dependency> | |
<groupId>com.aphysia</groupId> | |
<artifactId>DbInterface</artifactId> | |
<version>1.0-SNAPSHOT</version> | |
</dependency> | |
<dependency> | |
<groupId>com.aphysia</groupId> | |
<artifactId>MySqlConection</artifactId> | |
<version>1.0-SNAPSHOT</version> | |
</dependency> | |
<dependency> | |
<groupId>com.aphysia</groupId> | |
<artifactId>SqlServerConnection</artifactId> | |
<version>1.0-SNAPSHOT</version> | |
</dependency> | |
</dependencies> |
测试代码如下:
import com.aphysia.sql.DBConnectionService; | |
import java.util.ServiceLoader; | |
public class Test {public static void main(String[] args) {ServiceLoader<DBConnectionService> serviceLoader= ServiceLoader.load(DBConnectionService.class); | |
for (DBConnectionService dbConnectionService : serviceLoader) {dbConnectionService.connect(); | |
} | |
} | |
} |
输入:
mysql 正在连接... | |
sqlServer 正在连接... |
如果咱们只在 pom 文件外面引入 mysql 的实现呢?答案很显著,只会输入上面一句:
mysql 正在连接...
也就是对于应用的人来说,不须要本人再做什么操作,只须要把包引入进来即可,简略易用。
具体残缺代码: https://github.com/Damaer/Dem…
3. ServiceLoader 实现原理
ServiceLoader
位于 java.util
包下, 其次要代码如下:
public final class ServiceLoader<S> | |
implements Iterable<S> | |
{ | |
private static final String PREFIX = "META-INF/services/"; | |
private final Class<S> service; | |
private final ClassLoader loader; | |
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 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();} | |
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(); | |
} | |
} | |
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(); | |
} | |
}; | |
} | |
public static <S> ServiceLoader<S> load(Class<S> service) {ClassLoader cl = Thread.currentThread().getContextClassLoader(); | |
return ServiceLoader.load(service, cl); | |
} | |
public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {ClassLoader cl = ClassLoader.getSystemClassLoader(); | |
ClassLoader prev = null; | |
while (cl != null) { | |
prev = cl; | |
cl = cl.getParent();} | |
return ServiceLoader.load(service, prev); | |
} | |
public String toString() {return "java.util.ServiceLoader[" + service.getName() + "]"; | |
} | |
} |
咱们调用 ServiceLoader.load()
获取接口的实现,实际上也是调用了 ServiceLoader(Class<S> svc, ClassLoader cl)
,外面都是调用reload()
,reload()
外面做了些什么操作呢?
先把 provider
清空,而后创立了 LazyIterator
对象,LazyIterator
是一个外部类,实现了 Iterator
接口,实际上就是一个懒加载的迭代器。什么时候加载呢?
在迭代器调用的时候,调用 hasNextService()
,去解析resource/META-INF/services
上面的实现,并实现实现类的实例化。这里的实例化是应用反射,也是通过全限定类名。class.forName()
。
解析的时候,每一行代表一个实现类,将曾经发现的接口进行缓存,放到 private LinkedHashMap<String,S> providers
中,同时对外提供遍历迭代的办法。
4. SPI 的利用
咱们在应用 mysql 驱动的时候,在 mysql-connector-java-version.jar
中,有一个文件是 Resource/service/java.sql.Driver
文件,外面记录的是:
com.mysql.jdbc.Driver | |
com.mysql.fabric.jdbc.FabricMySQLDriver |
也就是申明了 java.sql.Driver
的实现类是 com.mysql.jdbc.Driver
,不须要手动应用Class.forName()
手动加载。
同样的,slf4j
也是一样的机制去实现拓展性能。
这种思维,通过服务约定 –> 服务实现 –> 服务主动注册 –> 服务发现和应用,实现了提供者和应用方的解耦,真的很强 …
此文章仅代表本人(本菜鸟)学习积攒记录,或者学习笔记,如有侵权,请分割作者删除。人无完人,文章也一样,文笔稚嫩,在下不才,勿喷,如果有谬误之处,还望指出,感激不尽~
技术之路不在一时,山高水长,纵使迟缓,驰而不息。
公众号:秦怀杂货店