SPI简介
这里先说下SPI的一个概念,SPI英文为Service Provider Interface单从字面可以理解为Service提供者接口,正如从SPI的名字去理解SPI就是Service提供者接口;我对SPI的定义:提供给服务提供厂商与扩展框架功能的开发者使用的接口。
在我们日常开发的时候都是对问题进行抽象成Api然后就提供各种Api的实现,这些Api的实现都是封装与我们的Jar中或框架中的虽然当我们想要提供一种Api新实现时可以不修改原来代码只需实现该Api就可以提供Api的新实现,但我们还是生成新Jar或框架(虽然可以通过在代码里扫描某个目录已加载Api的新实现,但这不是Java的机制,只是hack方法),而通过Java SPI机制我们就可以在不修改Jar包或框架的时候为Api提供新实现。
很多框架都使用了java的SPI机制,如java.sql.Driver的SPI实现(MySQL驱动、oracle驱动等)、common-logging的日志接口实现、dubbo的扩展实现等等框架;
SPI约定
1) 在 META-INF/services/ 目录中创建以接口全限定名命名的文件,该文件内容为Api具体实现类的全限定名;
2) 使用 ServiceLoader类 动态加载 META-INF 中的实现类;
3) 如SPI的实现类为Jar,则需要放在主程序classPath中;
4) Api具体实现类必须有一个不带参数的构造方法;

简单示例
通过一个简单例子来说明SPI是如何使用的。 首先通过一张图来看看,用SPI需要遵循哪些规范,因为spi毕竟是JDK的一种标准。
完整的Maven示例
1、目录结构
[spi-demo] ├── pom.xml ├── spi-demo-lib │ ├── pom.xml │ ├── src │ │ ├── main │ │ │ ├── java │ │ │ │ └── com │ │ │ │ └── jianbao │ │ │ │ └── IService.java │ │ │ └── resources │ │ └── test │ │ └── java │ └── target ├── spi-demo-main │ ├── pom.xml │ ├── src │ │ ├── main │ │ │ ├── java │ │ │ │ └── com │ │ │ │ └── jianbao │ │ │ │ └── main │ │ │ │ └── Main.java │ │ │ └── resources │ │ └── test │ │ └── java │ └── target ├── spi-demo-service1 │ ├── pom.xml │ ├── src │ │ ├── main │ │ │ ├── java │ │ │ │ └── com │ │ │ │ └── jianbao │ │ │ │ └── services │ │ │ │ └── impl │ │ │ │ ├── Service1.java │ │ │ │ └── Service2.java │ │ │ └── resources │ │ │ └── META-INF │ │ │ └── services │ │ │ └── com.jianbao.IService │ │ └── test │ │ └── java │ └── target └── spi-demo-service3 ├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ └── jianbao │ │ │ └── services │ │ │ └── impl │ │ │ └── Service3.java │ │ └── resources │ │ └── META-INF │ │ └── services │ │ └── com.jianbao.IService │ └── test │ └── java └── target
2、项目文件内容
(1)模块 spi-demo
pom.xml
<?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.jianbao</groupId>
<artifactId>spi-demo</artifactId>
<packaging>pom</packaging>
<version>1.0.0</version>
<modules>
<module>spi-demo-service1</module>
<module>spi-demo-service3</module>
<module>spi-demo-lib</module>
<module>spi-demo-main</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.jianbao</groupId>
<artifactId>spi-demo-lib</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
(2) 模块 /spi-demo/spi-demo-lib
pom.xml
<?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.jianbao</groupId>
<artifactId>spi-demo-lib</artifactId>
<version>1.0.0</version>
</project>
IService.java
package com.jianbao;
public interface IService {
void Foo();
}
(3) 模块 /spi-demo/spi-demo-service1
pom.xml
<?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>
<parent>
<groupId>com.jianbao</groupId>
<artifactId>spi-demo</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>spi-demo-service1</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>com.jianbao</groupId>
<artifactId>spi-demo-lib</artifactId>
</dependency>
</dependencies>
</project>
Service1.java
package com.jianbao.services.impl;
import com.jianbao.IService;
public class Service1 implements IService {
@Override
public void Foo() {
System.out.println("service1 on called");
}
}
Service2.java
package com.jianbao.services.impl;
import com.jianbao.IService;
public class Service2 implements IService {
@Override
public void Foo() {
System.out.println("service2 on called");
}
}
META-INF/services/com.jianbao.IService
com.jianbao.services.impl.Service1
com.jianbao.services.impl.Service2
(4) 模块 /spi-demo/spi-demo-service3
pom.xml
<?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>
<parent>
<groupId>com.jianbao</groupId>
<artifactId>spi-demo</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>spi-demo-service3</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>com.jianbao</groupId>
<artifactId>spi-demo-lib</artifactId>
</dependency>
</dependencies>
</project>
Service3.java
package com.jianbao.services.impl;
import com.jianbao.IService;
public class Service3 implements IService {
@Override
public void Foo() {
System.out.println("service3 on called");
}
}
META-INF/services/com.jianbao.IService
com.jianbao.services.impl.Service3
(5) 模块 /spi-demo/spi-demo-main
pom.xml
<?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>
<parent>
<groupId>com.jianbao</groupId>
<artifactId>spi-demo</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>spi-demo-main</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>com.jianbao</groupId>
<artifactId>spi-demo-lib</artifactId>
</dependency>
<dependency>
<groupId>com.jianbao</groupId>
<artifactId>spi-demo-service1</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.jianbao</groupId>
<artifactId>spi-demo-service3</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.jianbao.main.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Main.java
package com.jianbao.main;
import com.jianbao.IService;
import java.util.ServiceLoader;
public class Main {
public static void main(String[] args) throws Exception {
// 【推荐】输出 方式一
ServiceLoader<IService> services = ServiceLoader.load(IService.class);
for (IService service : services) {
service.Foo();
}
// 输出 方式二
// ServiceLoader<IService> serviceList = ServiceLoader.load(IService.class);
// Iterator<IService> serviceIterator = serviceList.iterator();
// //System.out.println("classPath:" + System.getProperty("java.class.path"));
// while (serviceIterator.hasNext()) {
// IService service = serviceIterator.next();
// service.Foo();
// }
}
}
3、执行代码:
(1) 切换到 项目根目录下,执行
mvn clean install
(2) 运行 main 方法所在的 jar 文件
java -jar ./spi-demo-main/target/spi-demo-main-1.0.0.jar
输出:
service1 on called
service2 on called
当把 pom.xml 中对 spi-demo-service1 模块的引用去掉后,重新编译,输出:
service3 on called
4、得出结论(特别注意的地方)
当有多个 jar 文件同时实现该接口,并同时被项目引用的话,只有第一个 被引用的 jar 文件起作用!
参考: