spring框架四个基础核心四(Spring factories)

439 阅读6分钟

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的基本使用,案例结构如下

image.png

这是一个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

image.png

比如说,spring-boot-autoconfigure

image.png

具体spring.factories文件内容都比较多,我就不复制粘贴了,我建议你一定要自己打开看一看。另外我写了一个简单的参考案例:follow-me-springboot-thirtybean

本文相关源码,仓库地址:gitee.com/yanghouhua/…