胖胖的枫叶
主页
博客
产品设计
企业架构
全栈开发
效率工具
数据分析
项目管理
方法论
面试
  • openJdk-docs
  • spring-projects-docs
  • mysql-docs
  • redis-commands
  • redis-projects
  • apache-rocketmq
  • docker-docs
  • mybatis-docs
  • netty-docs
  • journaldev
  • geeksforgeeks
  • 后端进阶
  • 并发编程网
  • 英语肌肉记忆锻炼软件
  • 墨菲安全
  • Redisson-docs
  • jmh-Visual
  • 美团技术
  • MavenSearch
主页
博客
产品设计
企业架构
全栈开发
效率工具
数据分析
项目管理
方法论
面试
  • openJdk-docs
  • spring-projects-docs
  • mysql-docs
  • redis-commands
  • redis-projects
  • apache-rocketmq
  • docker-docs
  • mybatis-docs
  • netty-docs
  • journaldev
  • geeksforgeeks
  • 后端进阶
  • 并发编程网
  • 英语肌肉记忆锻炼软件
  • 墨菲安全
  • Redisson-docs
  • jmh-Visual
  • 美团技术
  • MavenSearch
  • 标签索引
  • 2024年

    • 配置Mac环境
    • 业务知识会计管理
    • 业务知识会计基础
    • 业务知识什么是财务
  • 2023年

    • 项目 Boi
  • 2022年

    • 企业架构故障管理
    • 企业架构开发债务
  • 2021年

    • Python3.8 Matplotlib员工数据分析
    • Python3.8 Matplotlib IP折线图
    • Python3.8 词云 IP地址
    • Redis RediSearch
    • Rust第一个CLI程序
    • Rust所有权
    • Rust函数与控制流
    • Rust变量与数据类型
    • Rust入门
    • 企业架构分布式系统
    • 编程式权限设计
    • Java JVM优化
    • SpringBoot MyBatis 批量
    • SpringBoot 测试Mock
    • SpringBoot Redis布隆过滤器
    • CentOS7 Jenkins 部署
    • SpringBoot WebClient
    • Docker Drone 部署
    • SpringBoot MyBatis
    • SpringBoot Redisson
    • SpringBoot MyBatis 雪花算法
    • Java Netty
    • Redis 扫描
    • CentOS7 Jenkins本地部署分级
    • Mac 安装 Neo4j Jupyter
    • Mac OpenJDK11 JavaFX 环境
    • Mac 安装 Jenv
    • SpringBoot Redis 延时队列
    • SpringBoot MDC日志
    • SpringBoot 定时任务
    • CentOS7 Nginx GoAccess
    • SpringBoot MyBatis 分析
    • SpringBoot Lucene
    • 企业架构分布式锁
    • 学习技巧减少学习排斥心理
    • SpringBoot 动态数据源
    • Docker Compose SpringBoot MySQL Redis
    • SpringBoot 阻塞队列
    • Docker Compose Redis 哨兵
    • Docker Compose Redis 主从
    • 网络通信
  • 2020年

    • SpringBoot 延时队列
    • MySQL基础(四)
    • Java 雪花算法
    • Redis Geo
    • 网络通信 Tcpdump
    • Spring SPI
    • Java Zookeeper
    • SpringBoot JMH
    • 网络通信 Wireshark
    • Docker Compose Redis MySQL
    • CentOS7 Docker 部署
    • Netty 源码环境搭建
    • MySQL基础(三)
    • CentOS7 Selenium运行环境
    • CentOS7 Nginx HTTPS
    • Java JMH
    • SpringBoot 修改Tomcat版本
    • Java Eureka 钉钉通知
    • SpringBoot 错误钉钉通知
    • Java JVM
    • Git 合并提交
    • CentOS7 OpenResty 部署
  • 2019年

    • Redis CLI
    • CentOS7 Nginx 日志
    • 编程式代码风格
    • IDEA 插件
    • Skywalking 源码环境搭建
    • SpringBoot Redis 超时错误
    • 编程式 gRPC
    • Java Arthas
    • Docker Compose Redis 缓存击穿
    • Docker ElasticSearch5.6.8 部署
    • Docker Mysql5.7 部署
    • Spring Redis 字符串
    • Docker Zookeeper 部署
    • Docker Redis 部署
    • SpringBoot Dubbo
    • CentOS7 CMake 部署
    • 应用程序性能指标
    • Java Code 递归
    • CentOS7 ELK 部署
    • CentOS7 Sonarqube 部署
    • Java Selenium
    • Java JJWT JUnit4
    • Spring 源码环境搭建
    • Java JUnit4
    • Java Web JSON Token
    • 编程式 FastDFS
    • Java XPath
    • Redis基础(二)
    • Redis基础(一)
    • Java MyBatis JUnit4
    • Java MyBatis H2 JUnit4
    • MyBatis 源码环境搭建
    • Git 配置
    • Java 核心
    • Java Dubbo
    • Java JavaCollecionsFramework
    • Java Maven
    • Java MyBatis
    • Java Spring
    • Java SpringMVC
    • MySQL
    • Redis
  • 2018年

    • Java HashMap
    • Java HashSet
    • Java Code 交换值
    • Spring Upgrade SpringBoot
    • Mac 编程环境
    • Java Log4j
    • 网络通信 Modbus
    • MySQL基础(二)
    • MySQL基础(一)
    • Java Stack
    • Java Vector
    • CentOS7 RabbitMQ 部署
    • CentOS7 Redis 部署
    • CentOS7 MongoDB 部署
    • CentOS7 基础命令
    • Java Eureka Zookeeper
    • CentOS7 MySQL 部署
    • Git 分支
    • CentOS7 Java环境配置
    • Java LinkedList
    • Java ArrayList
    • Spring Annotation Aop

Spring SPI

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。

  • 本文对比JDK、Spring对SPI机制的实现。

  • 在实际的应用场景。

    • jdbc驱动,不同的数据有不同的驱动。

    com.mysql.cj.jdbc.Driver

    • spring 也使用spi机制,用于扩展实现。

    org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory

    • dubbo也通过spi机制实现自定义扩展。
    • 日志门面接口实现类加载,SLF4J加载不同提供商的日志是现类。

SPI

演示代码

JDK 中 提供了一个 SPI 的功能,核心类是 java.util.ServiceLoader。其作用就是,可以通过类名获取在"META-INF/services/"下的多个配置实现文件。

ServiceLoader

  • META-INF/services下的文件(以Interface全路径命名)中添加具体实现类的全路径名。
  • 使用程序使用ServiceLoader动态加载实现类(根据目录META-INF/services下的配置文件找到实现类的全限定名并调用classloader来加载实现类到JVM)
  • SPI的实现类必须具有无参数的构造方法

演示代码

目录
└── src
    ├── main
    │   ├── java
    │   │   └── cn
    │   │       └── z201
    │   │           └── spi
    │   │               ├── SpeakChinese.java 
    │   │               ├── SpeakEnglish.java
    │   │               ├── SpeakGerman.java
    │   │               ├── SpeakSPIService.java // 定义接口
    │   │               └── package-info.java
    │   └── resources
    │       └── META-INF
    │           └── services
    │               └── cn.z201.spi.SpeakSPIService // 配置文件,文件名以接口包和类名称组成。

接口
public interface SpeakSPIService {
    Object echo();
}
实现类
public class SpeakGerman implements SpeakSPIService{

    @Override
    public Object echo() {
        return "german : hallo";
    }
}

public class SpeakEnglish implements SpeakSPIService{
    @Override
    public Object echo() {
        return "english : hello";
    }
}

public class SpeakChinese implements SpeakSPIService {

    @Override
    public Object echo() {
        return "chinese : 你好";
    }
}
配置文件
  • SpeakSPIService就是接口的名称
cn.z201.spi.SpeakChinese
cn.z201.spi.SpeakGerman
cn.z201.spi.SpeakEnglish
测试
class SpeakSPIServiceTest {

    @Test
    public void setUp(){
        ServiceLoader<SpeakSPIService> serviceLoader = ServiceLoader.load(SpeakSPIService.class, Thread.currentThread().getContextClassLoader());
        Iterator<SpeakSPIService> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            SpeakSPIService speakSPIService = iterator.next();
            System.out.println("speakSPIService " + speakSPIService.echo());
        }
    }

}
  • Console
speakSPIService chinese : 你好
speakSPIService german : hallo
speakSPIService english : hello
# 实际工作中这么用肯定太难用了,这里小改动一下。
改进
  • 使用注解标注类型,通过注解信息获取对应的实现类。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface LanguageType {

    String language();
}

@LanguageType(language = "chinese")
public class SpeakChinese implements SpeakSPIService {

    @Override
    public Object echo() {
        return "chinese : 你好";
    }
}
@LanguageType(language = "english")
public class SpeakEnglish implements SpeakSPIService{
    @Override
    public Object echo() {
        return "english : hello";
    }
}
@LanguageType(language = "german")
public class SpeakGerman implements SpeakSPIService{

    @Override
    public Object echo() {
        return "german : hallo";
    }
}

// 工具类,根据类注解反射获取数据
public class LanguageTypeTool {

    public static SpeakSPIService language(String language){
        ServiceLoader<SpeakSPIService> serviceLoader = ServiceLoader.load(SpeakSPIService.class, Thread.currentThread().getContextClassLoader());
        Iterator<SpeakSPIService> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            SpeakSPIService speakSPIService = iterator.next();
            Class clazz = speakSPIService.getClass();
            LanguageType languageType = (LanguageType) clazz.getDeclaredAnnotation(LanguageType.class);
            if (null != languageType) {
                if (Objects.equals(language,languageType.language())) {
                    return speakSPIService;
                }
            }
        }
        return null;
    }
}
  • 测试
    @Test
    public void setUp() {
        ServiceLoader<SpeakSPIService> serviceLoader = ServiceLoader.load(SpeakSPIService.class, Thread.currentThread().getContextClassLoader());
        Iterator<SpeakSPIService> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            SpeakSPIService speakSPIService = iterator.next();
            System.out.println(speakSPIService.echo());
        }
        SpeakSPIService speakSPIService = LanguageTypeTool.language("chinese");
        if (null != speakSPIService) {
            System.out.println(speakSPIService.echo());
        }
        speakSPIService = LanguageTypeTool.language("english");
        if (null != speakSPIService) {
            System.out.println(speakSPIService.echo());
        }
    }
  • Console
chinese : 你好
german : hallo
english : hello
chinese : 你好
english : hello

ServiceLoaderFactoryBean

通过Java提供的SPI的方式来加载相关实现类并注册到Spring容器中。

public class ServiceLoaderFactoryBean extends AbstractServiceLoaderBasedFactoryBean implements BeanClassLoaderAware {

   @Override
   protected Object getObjectToExpose(ServiceLoader<?> serviceLoader) {
      return serviceLoader;
   }

  // 这里引用的就是jdk中的ServiceLoader
   @Override
   public Class<?> getObjectType() {
      return ServiceLoader.class;
   }

}
演示代码
  • 项目引用JDK-SPI的代码
      <dependency>
            <groupId>cn.z201.spi</groupId>
            <artifactId>Jdk-SPI</artifactId>
            <version>1.0.0-SNAPSHOT</version>
       </dependency>
配置类
/**
 * @author z201.coding@gmail.com
 **/
@Configuration
public class SpeakSPIServiceConfig {

    // 指定bean的id ,通过依赖查找或者依赖注入可以找到。
    @Bean("initBeanFactoryServiceLoaderFactoryBean")
    public ServiceLoaderFactoryBean serviceLoaderFactoryBean() {
        ServiceLoaderFactoryBean serviceLoaderFactoryBean = new ServiceLoaderFactoryBean();
        serviceLoaderFactoryBean.setServiceType(SpeakSPIService.class);
        return serviceLoaderFactoryBean;
    }

}
测试
    @Test
    public void setSpringUp() {
        // 创建BeanFactory 容器
        AnnotationConfigApplicationContext config = new AnnotationConfigApplicationContext();
        config.register(SpeakSPIServiceConfig.class);
        // 启动上下文
        config.refresh();
        ServiceLoader<SpeakSPIService> serviceLoader = config.getBean("initBeanFactoryServiceLoaderFactoryBean", ServiceLoader.class);
        displayServiceLoader(serviceLoader);
        // 关闭上下文
        config.close();
    }

    private void displayServiceLoader(ServiceLoader<SpeakSPIService> serviceLoader){
        Iterator<SpeakSPIService> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            SpeakSPIService speakSPIService = iterator.next();
            System.out.println("speakSPIService " + speakSPIService.echo());
        }
    }
  • Console
speakSPIService chinese : 你好
speakSPIService german : hallo
speakSPIService english : hello
# 既然用到了spring 当然不会这么简单就结束。
改进
  • 接口实现类注册成bean。
 @Test
    public void setSpringRegisterBeanUp() {
        // 创建BeanFactory 容器
        AnnotationConfigApplicationContext config = new AnnotationConfigApplicationContext();
        config.register(SpeakSPIServiceConfig.class);
        // 启动上下文
        config.refresh();
        registerBean(config);
        lookupCollectionType(config);
        // 关闭上下文
        config.close();
    }
   
    
    private void registerBean(AnnotationConfigApplicationContext applicationContext){
        ServiceLoader<SpeakSPIService> serviceLoader = applicationContext.getBean("initBeanFactoryServiceLoaderFactoryBean", ServiceLoader.class);
        Iterator<SpeakSPIService> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            SpeakSPIService speakSPIService = iterator.next();
            Class clazz = speakSPIService.getClass();
            LanguageType languageType = (LanguageType) clazz.getDeclaredAnnotation(LanguageType.class);
            if (null != languageType) {
                applicationContext.registerBean(languageType.language(),clazz);
            }
        }
    }

    /**
     * 单一类型集合查找
     *
     * @param beanFactory
     */
    private void lookupCollectionType(BeanFactory beanFactory) {
        if (beanFactory instanceof ListableBeanFactory) {
            ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
            Map<String, SpeakSPIService> beansMap = listableBeanFactory.getBeansOfType(SpeakSPIService.class);
            // getBeansOfType 匹配所有类型的 bean,无论是单例、原型还是 FactoryBean , bean name 作为key value 作为对象
            beansMap.forEach((key, value) -> {
                System.out.println("单一类型集合查找  " + key + " " + value.echo());
            });
        }
    }
  • Console
单一类型集合查找  chinese chinese : 你好
单一类型集合查找  german german : hallo
单一类型集合查找  english english : hello

END

最近更新: 2025/12/27 18:51
Contributors: 庆峰
Prev
网络通信 Tcpdump
Next
Java Zookeeper