1.引言
spring框架有四个基础核心:
- IoC容器
- JavaConfig
- 事件监听
- Spring factories
2.什么是spring factories
当下java领域的很多项目,springboot都是作为基础框架的存在。在应用过程中,如果你此时还不熟悉spring factories究竟为何物?请打开你们项目的resources/META-INF/spring.factories文件,看看里面是否有类似这样的配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.MyAutoConfiguration
基本上都有,对吧。这即是今天分享的主角spring factories的作用,即应用场景了。 稍微从概念层次上来解释一下,什么是spring factories?
spring factories是一种模块化设计和插件化开发机制,它是在java spi机制基础上进行了扩展,解决了应用
中自动化配置和扩展的问题,该机制使得Spring框架更加灵活和可扩展,支持外部包的动态加载和配置
你看它是解决可扩展和灵活性问题的,其中有一个关键词:java spi。文章稍后会给你分享一个完整的案例。 说到这里,你大致应该可以理解你们日常开发中的这样一个场景了:
- 基础架构组,或者平台组的同事会给我们提供一些sdk,我们拿到这些sdk,只需要在pom.xml中添加依赖,就可以直接使用
- 使用springboot作为基础框架,需要某个组件,只需要在pom.xml,添加xxx-starter依赖,就可以直接使用
- 当需要更换sdk实现方式,xxx-starter组件的时候,只需要更换依赖即可,并不需要修改应用代码
有没有领会到以上描述的奥妙之处?即我们的应用代码,与基础组件sdk,xxx-starter之间是解耦的,后期代码维护起来极其方便!
那么这么好的解决方案,究竟是如何实现的呢?下面来看一个案例。
3. java spi机制案例
3.1.spi介绍
以上我们说的实现应用代码,与基础组件之间的解耦,其实我们是在描述一个事实
- 在模块化、组件化开发的实践中,需要遵循高内聚、低耦合的思想
- 其中实现解耦的一个基本思路是面向接口编程,即接口隔离的设计原则
- 模块与模块之间,组件之间依赖接口,而非具体的实现,即最大化的实现了解耦
这是我们在项目中经常推荐的做法,除了提倡面向接口编程这个事实,我们还需要考虑另外一个事实
- 定义好了接口,模块之间通过依赖接口,实现解耦
- 但是一个接口,可能会有多中实现方案,实现方案A、实现方案B...
- 应用程序在执行的时候,如何知道具体实现的是方案A,还是方案B呢
这就是我要描述的另外一个事实,我们还需要一个发现接口实现的机制。它也就是spi机制。
总结一下,spi的全称是service provider interface,它是java提供的规范,该规范
- 强调基于接口编程,实现模块之间的解耦
- 具体实现,首先要定义接口规范
- 然后每个服务提供者,参照规范约定提供接口的具体实现
- 使用者,通过ServiceLoader工具,即可获取服务提供者的具体实现
- 举个例子,想想jdbc编程实践中,不同数据库厂商提供的驱动实现,比如mysql、oracle
- 再举个例子,插件化编程的时代,各种插件的使用
以上关于spi的介绍,如果你是第一次关注的话,相信此时一定是蒙圈的,不过不要紧,继续看完后面的案例, 再回过头来看 你就能明白了。
3.2.入门案例
3.2.1.代码接口
通过一个基础案例,演示java spi的基本使用,案例结构如下
这是一个maven项目,由三个类文件,以及一个配置文件组成
- Animal:接口规范,spi强调基于接口编程
- Bird:Animal接口的实现
- SpiTest:使用者,通过ServiceLoader工具,获取Animal接口的具体实现
- META-INF/services/cn.edu.spi.Animal:spi规范的约定,即接口与接口实现的描述文件
3.2.2.代码
Animal接口
/**
* spi 案例:接口
*
* @author ThinkPad
* @version 1.0
*/
public interface Animal {
/**
* 动物喊话
* @param name
*/
void hello(String name);
}
Bird实现类
/**
* spi案例:接口实现 鸟
*
* @author ThinkPad
* @version 1.0
*/
public class Bird implements Animal{
/**
* 动物喊话
*
* @param name
*/
@Override
public void hello(String name) {
System.out.println("I am small small small Bird! name is:" +name);
}
}
META-INF/services/cn.edu.spi.Animal配置文件
cn.edu.spi.impl.Bird
SpiTest类
/**
* 测试类
*
* @author ThinkPad
* @version 1.0
*/
public class SpiTest {
public static void main(String[] args) {
ServiceLoader<Animal> animals = ServiceLoader.load(Animal.class);
Iterator<Animal> iter = animals.iterator();
while (iter.hasNext()){
Animal animal = iter.next();
animal.hello("Bob");
}
}
}
执行结果
I am small small small Bird! name is:Bob
Process finished with exit code 0
3.3.java spi综合案例
有了spi基础案例,接下来我们做一个更加接近项目实践的案例。我们来准备以下一些事情
- 构建sdk接口规范工程:follow-me-spi-animal,它有两个实现
- 构建sdk接口规范实现工程cat:follow-me-spi-cat,提供具体实现方案1
- 构建sdk接口规范实现工厂dog:follow-me-spi-dog,提供具体实现方案2
- 构建使用者工程:follow-me-springboot-api,实现在cat、dog之间随意切换实现方式,而不需要修改应用代码
3.3.1.工程:follow-me-spi-animal
3.3.1.1.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>
<!--spi 接口gav信息-->
<groupId>cn.edu.anan</groupId>
<artifactId>follow-me-spi-animal</artifactId>
<version>0.0.1</version>
</project>
3.3.1.2.接口
package cn.edu.anan;
/**
* spi接口
*
* @author ThinkPad
* @version 1.0
*/
public interface Animal {
/**
* 接口方法
*/
void hello();
}
3.3.2.工程:follow-me-spi-cat
3.3.2.1.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>
<!--项目 gav信息-->
<groupId>cn.edu.anan</groupId>
<artifactId>follow-me-spi-cat</artifactId>
<version>0.0.1</version>
<!--公共变量定义-->
<properties>
<java.version>1.8</java.version>
<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>
<dependencies>
<!--spi interface依赖-->
<dependency>
<groupId>cn.edu.anan</groupId>
<artifactId>follow-me-spi-animal</artifactId>
<version>0.0.1</version>
</dependency>
</dependencies>
<!--项目构建插件-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.3.2.2.接口实现
package cn.edu.anan;
/**
* pi Animal接口实现2
*
* @author ThinkPad
* @version 1.0
*/
public class Cat implements Animal{
/**
* 接口方法
*/
@Override
public void hello() {
System.out.println("I am Cat.喵喵...");
}
}
3.3.2.3.spi配置
META-INF/services/cn.edu.anan.Animal
cn.edu.anan.Cat
3.3.3.工程:follow-me-spi-dog
3.3.3.1.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>
<!--项目 gav信息-->
<groupId>cn.edu.anan</groupId>
<artifactId>follow-me-spi-dog</artifactId>
<version>0.0.1</version>
<!--公共变量定义-->
<properties>
<java.version>1.8</java.version>
<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>
<dependencies>
<!--spi interface依赖-->
<dependency>
<groupId>cn.edu.anan</groupId>
<artifactId>follow-me-spi-animal</artifactId>
<version>0.0.1</version>
</dependency>
</dependencies>
<!--项目构建插件-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.3.3.2.接口实现
package cn.edu.anan;
/**
* spi Animal接口实现1
*
* @author ThinkPad
* @version 1.0
*/
public class Dog implements Animal{
/**
* 接口方法
*/
@Override
public void hello() {
System.out.println("I am Dog.汪汪...");
}
}
3.3.3.3.spi配置
META-INF/services/cn.edu.anan.Animal
cn.edu.anan.Dog
3.3.4.工程:follow-me-springboot-api
3.3.4.1.依赖follow-me-spi-cat
导入依赖
<!--follow-me-spi-cat依赖-->
<dependency>
<groupId>cn.edu.anan</groupId>
<artifactId>follow-me-spi-cat</artifactId>
<version>0.0.1</version>
</dependency>
应用配置
/**
* spi 配置类
*
* @author ThinkPad
* @version 1.0
*/
@Configuration
public class SpiConfiguration {
@Bean
public Animal animal(){
Animal animal = null;
ServiceLoader<Animal> animals = ServiceLoader.load(Animal.class);
Iterator<Animal> iter = animals.iterator();
while (iter.hasNext()){
animal = iter.next();
break;
}
return animal;
}
}
controller
/**
* spi controller
*
* @author ThinkPad
* @version 1.0
*/
@RestController
@RequestMapping("spi")
@Slf4j
public class SpiController {
@Autowired
private Animal animal;
@RequestMapping("test")
public String test(){
animal.hello();
return animal.getClass().getName();
}
}
执行结果
启动应用,访问端点:http://127.0.0.1:8080/spi/test
响应:cn.edu.anan.Cat
控制台输出:I am Cat.喵喵...
3.3.4.2.依赖follow-me-spi-dog
将依赖换成dog
<!--follow-me-spi-dog依赖-->
<dependency>
<groupId>cn.edu.anan</groupId>
<artifactId>follow-me-spi-dog</artifactId>
<version>0.0.1</version>
</dependency>
<!--follow-me-spi-cat依赖-->
<!--<dependency>
<groupId>cn.edu.anan</groupId>
<artifactId>follow-me-spi-cat</artifactId>
<version>0.0.1</version>
</dependency>-->
执行结果
启动应用,访问端点:http://127.0.0.1:8080/spi/test
响应:cn.edu.anan.Dog
控制台输出:I am Dog.汪汪...
4.springboot中的spi机制解析
在springboot应用中,经常会用到spi机制,我们称之为spring factories机制,它的实现原理与java spi机制一致,它提供了
- SpringFactoriesLoader工具:作用等价于ServiceLoader,用于发现加载接口实现,即服务提供者实现方案
- META-INF/spring.factories:它约定将spi服务实现,配置到META-INF/spring.factories文件中,以kev/value的形式存在
举几个例子,比如说spring-boot
比如说,spring-boot-autoconfigure
具体spring.factories文件内容都比较多,我就不复制粘贴了,我建议你一定要自己打开看一看。另外我写了一个简单的参考案例:follow-me-springboot-thirtybean
本文相关源码,仓库地址:gitee.com/yanghouhua/…