首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Sa-Token 自定义插件 —— SPI 机制讲解(二)

Sa-Token 自定义插件 —— SPI 机制讲解(二)

作者头像
CodeSuc
发布2025-11-03 17:45:13
发布2025-11-03 17:45:13
1500
举报

前言

因为最近上班比较忙,第二部分直到现在才学习并且写出来。这一节我接着上节的内容,带领大家学习并且实战 SPI 机制在 Spring 和 SpringBoot中的实现方式。并且我还对 JDK 中的实现 ServiceLoader 进行了源码分析,带领大家深入底层源码学习。废话不多说,现在开始!

SPI 机制全部代码已上传,有需要可自取:SPI 代码存放地址

1. 传统 Spring 框架中的 SPI 的应用示例

Spring的核心框架提供了很多接口和抽象类,如BeanPostProcessor, PropertySource, ApplicationContextInitializer等,这些都可以看作是SpringSPI。开发者可以实现这些接口来扩展Spring的功能。这些接口允许开发者在Spring容器的生命周期的不同阶段介入,实现自己的逻辑

Spring框架中,虽然没有直接叫做SPI的术语,但其核心思想仍然存在。Spring提供了多个扩展点,其中最具代表的就是 BeanPostProcessor。在本节中,我们将通过一个简单的MessageService接口及其实现来探讨如何利用SpringBeanPostProcessor扩展点体现SPI的思想

实现步骤:

  1. 新建一个 Spring 项目 Spring-SPI-demo(省略)
  2. 提供两个简单的 Service 实现类,实现不同的逻辑
代码语言:javascript
复制
public interface MessageService {
    String getMessage();
}


public class HiMessageServiceImpl implements MessageService {
    @Override
    public String getMessage() {
        return "Hi Message";
    }
}

public class HelloMessageServiceImpl implements MessageService {
    @Override
    public String getMessage() {
        return "Hello Message";
    }
}

3. 定义 Processor,实现 BeanPostProcessor,用于在 Bean 初始化前后进行自定义处理

代码语言:javascript
复制
public class MessageServicePostProcessor implements BeanPostProcessor {

    /**
     * 只需要改前置操作
     *
     * @param bean the new bean instance
     * @param beanName the name of the bean
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof MessageService) {
            return new MessageService() {
                @Override
                public String getMessage() {
                    return ((MessageService) bean).getMessage() + "--postProcessor By Spring SPI";
                }
            };
        }
        return bean;
    }


    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

此处目的是在 Bean 初始化前,查找如果类型是 MessageService,则在消息后面加上一段话

4. 修改 Spring 配置,注册成 Bean

代码语言:javascript
复制
@Configuration
public class SpringConfig {
    @Bean
    public MessageService helloMessageService() {
        return new HelloMessageServiceImpl();
    }

    @Bean
    public MessageService hiMessageService() {
        return new HiMessageServiceImpl();
    }

    @Bean
    public MessageServicePostProcessor messageServicePostProcessor() {
        return new MessageServicePostProcessor();
    }
}

5. 编写启动类,让程序从上下文环境中读取到我们定义的 Bean。启动程序

代码语言:javascript
复制
@SpringBootApplication
public class SpringSpiDemoApplication {

    public static void main(String[] args) {
        // 创建 Spring 上下文,加载配置文件
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);

        // 从上下文中获取 Bean
        MessageService helloMessageService = context.getBean("helloMessageService", MessageService.class);
        MessageService hiMessageService = context.getBean("hiMessageService", MessageService.class);

        System.out.println(helloMessageService.getMessage());
        System.out.println(hiMessageService.getMessage());
    }

}

运行结果如下:

代码语言:javascript
复制
Hello Message--postProcessor By Spring SPI
Hi Message--postProcessor By Spring SPI

现在,每一个MessageService实现都被BeanPostProcessor处理了,添加了额外的消息“By Spring SPI”。这演示了SpringSPI概念,即通过BeanPostProcessor来扩展或修改Spring容器中的bean

解释:

类比电视机的例子:

  1. 电视机与USB插口: 在这个新的示例中,电视机仍然是核心的Spring应用程序,具体来说是DemoApplication类。这个核心应用程序需要从某个服务(即MessageService)获取并打印一条消息
  2. USB插口: 与之前一样,MessageService接口就是这个"USB插口"。它为电视机提供了一个标准化的接口,即getMessage()方法,但没有规定具体怎么实现
  3. 设备制造商与他们的产品: 在这里,我们有两种设备制造商或第三方提供者:HelloMessageServiceHiMessageService。它们为"USB插口"(即MessageService接口)提供了不同的设备或实现。一个显示“Hello from HelloMessageService!”,另一个显示“Hi from HiMessageService!”
  4. BeanPostProcessor: 这是一个特殊的“魔法盒子”,可以将其视为一个能够拦截并修改电视机显示内容的智能设备。当插入USB设备(即MessageService的实现)并尝试从中获取消息时,这个“魔法盒子”会介入,并为每条消息添加“[Processed by Spring SPI]”
  5. Spring上下文配置: 这依然是电视机的使用说明书,但现在是使用了基于Java的配置方式,即MessageServiceConfig类。这个“使用说明书”指导Spring容器如何创建并管理MessageService的实例,并且还指导它如何使用“魔法盒子”(即MessageServicePostProcessor)来处理消息

总的来说,与之前的例子相比,这个新示例提供了一个更加动态的场景,其中SpringBeanPostProcessor扩展点允许我们拦截并修改bean的行为,就像一个能够干预并改变电视机显示内容的智能设备

2. SpringBoot 中的 spring.factories

spring.factories是SpringBoot 的一个特性,允许开发者自定义自动配置。通过spring.factories配置文件,开发者可以定义自己的自动配置类,这些类在Spring Boot启动时会被自动加载。这个文件可以在多个jar中存在,并且Spring Boot会加载所有可见的spring.factories文件。我们可以在这个文件中声明一系列的自动配置类,这样当满足某些条件时,这些配置类会自动被Spring Boot应用。

在这种情况下,SpringFactoriesLoader的使用,尤其是通过spring.factories文件来加载和实例化定义的类,可以看作是一种特定的SPI实现方式,但它特定于Spring Boot

实现步骤讲解:

  1. 服务接口定义: Spring 定义了许多服务接口,如 org.springframework.boot.autoconfigure.EnableAutoConfiguration
  2. 服务提供者实现: 各种具体的模块和库会提供这些服务接口的实现,如各种自动配置类
  3. 服务描述文件: 在实现模块的 JAR 包中,会有一个META-INF/spring.factories文件,这个文件中列出了该 JAR 包中实现的自动配置类
  4. 服务加载: Spring Boot 在启动时加载spring.factories文件,并实例化这些文件中列出的实现类
  5. 新建一个 SpringBoot 项目 Spring-SPI-demo(省略)
  6. 提供两个简单的 Service 实现类,实现不同的逻辑
代码语言:javascript
复制
public interface MessageService {
    String getMessage();
}


public class HiMessageServiceImpl implements MessageService {
    @Override
    public String getMessage() {
        return "Hi Message";
    }
}

public class HelloMessageServiceImpl implements MessageService {
    @Override
    public String getMessage() {
        return "Hello Message";
    }
}

3. 注册服务

resources/META-INF下创建一个文件名为spring.factories。这个文件里,可以注册MessageService实现类

代码语言:javascript
复制
com.example.springbootspidemo.service.MessageService=com.example.springbootspidemo.service.impl.HelloMessageServiceImpl,\
  com.example.springbootspidemo.service.impl.HiMessageServiceImpl

注意:

  1. 注意这里com.example.demo.service.MessageService是接口的全路径,而com.example.demo.service.HelloMessageService,com.example.demo.service.HiMessageService是实现类的全路径。如果有多个实现类,它们应当用逗号分隔
  2. spring.factories文件中的条目键和值之间不能有换行,即key=value形式的结构必须在同一行开始。但是,如果有多个值需要列出(如多个实现类),并且这些值是逗号分隔的,那么可以使用反斜杠(\)来换行。spring.factories 的名称是约定俗成的。如果试图使用一个不同的文件名,那么 Spring Boot 的自动配置机制将不会识别它

4. 编写启动类,使用 SpringFactoriesLoader 来加载服务

代码语言:javascript
复制
@SpringBootApplication
public class SpringBootSpiDemoApplication {

    public static void main(String[] args) {
        List<MessageService> services = SpringFactoriesLoader.loadFactories(MessageService.class, null);
        for (MessageService service : services) {
            System.out.println(service.getMessage());
        }
    }

}

运行结果如下:

代码语言:javascript
复制
Hello Message
Hi Message

这种方式利用了SpringSpringFactoriesLoader,它允许开发者提供接口的多种实现,并通过spring.factories文件来注册它们。这与JDKSPI思想非常相似,只是在实现细节上有所不同。这也是Spring Boot如何自动配置的基础,它会查找各种spring.factories文件,根据其中定义的类来初始化和配置bean

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-06-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • 1. 传统 Spring 框架中的 SPI 的应用示例
    • 2. SpringBoot 中的 spring.factories
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档