
因为最近上班比较忙,第二部分直到现在才学习并且写出来。这一节我接着上节的内容,带领大家学习并且实战 SPI 机制在 Spring 和 SpringBoot中的实现方式。并且我还对 JDK 中的实现 ServiceLoader 进行了源码分析,带领大家深入底层源码学习。废话不多说,现在开始!
SPI 机制全部代码已上传,有需要可自取:SPI 代码存放地址
Spring的核心框架提供了很多接口和抽象类,如BeanPostProcessor, PropertySource, ApplicationContextInitializer等,这些都可以看作是Spring的SPI。开发者可以实现这些接口来扩展Spring的功能。这些接口允许开发者在Spring容器的生命周期的不同阶段介入,实现自己的逻辑
在Spring框架中,虽然没有直接叫做SPI的术语,但其核心思想仍然存在。Spring提供了多个扩展点,其中最具代表的就是 BeanPostProcessor。在本节中,我们将通过一个简单的MessageService接口及其实现来探讨如何利用Spring的BeanPostProcessor扩展点体现SPI的思想
实现步骤:
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 初始化前后进行自定义处理
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
@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。启动程序
@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());
}
}运行结果如下:
Hello Message--postProcessor By Spring SPI
Hi Message--postProcessor By Spring SPI
现在,每一个MessageService实现都被BeanPostProcessor处理了,添加了额外的消息“By Spring SPI”。这演示了Spring的SPI概念,即通过BeanPostProcessor来扩展或修改Spring容器中的bean
解释:
类比电视机的例子:
Spring应用程序,具体来说是DemoApplication类。这个核心应用程序需要从某个服务(即MessageService)获取并打印一条消息MessageService接口就是这个"USB插口"。它为电视机提供了一个标准化的接口,即getMessage()方法,但没有规定具体怎么实现HelloMessageService和HiMessageService。它们为"USB插口"(即MessageService接口)提供了不同的设备或实现。一个显示“Hello from HelloMessageService!”,另一个显示“Hi from HiMessageService!”。USB设备(即MessageService的实现)并尝试从中获取消息时,这个“魔法盒子”会介入,并为每条消息添加“[Processed by Spring SPI]”Java的配置方式,即MessageServiceConfig类。这个“使用说明书”指导Spring容器如何创建并管理MessageService的实例,并且还指导它如何使用“魔法盒子”(即MessageServicePostProcessor)来处理消息总的来说,与之前的例子相比,这个新示例提供了一个更加动态的场景,其中Spring的BeanPostProcessor扩展点允许我们拦截并修改bean的行为,就像一个能够干预并改变电视机显示内容的智能设备
spring.factories是SpringBoot 的一个特性,允许开发者自定义自动配置。通过spring.factories配置文件,开发者可以定义自己的自动配置类,这些类在Spring Boot启动时会被自动加载。这个文件可以在多个jar中存在,并且Spring Boot会加载所有可见的spring.factories文件。我们可以在这个文件中声明一系列的自动配置类,这样当满足某些条件时,这些配置类会自动被Spring Boot应用。
在这种情况下,SpringFactoriesLoader的使用,尤其是通过spring.factories文件来加载和实例化定义的类,可以看作是一种特定的SPI实现方式,但它特定于Spring Boot
实现步骤讲解:
org.springframework.boot.autoconfigure.EnableAutoConfigurationMETA-INF/spring.factories文件,这个文件中列出了该 JAR 包中实现的自动配置类spring.factories文件,并实例化这些文件中列出的实现类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实现类
com.example.springbootspidemo.service.MessageService=com.example.springbootspidemo.service.impl.HelloMessageServiceImpl,\
com.example.springbootspidemo.service.impl.HiMessageServiceImpl
注意:
com.example.demo.service.MessageService是接口的全路径,而com.example.demo.service.HelloMessageService,com.example.demo.service.HiMessageService是实现类的全路径。如果有多个实现类,它们应当用逗号分隔spring.factories文件中的条目键和值之间不能有换行,即key=value形式的结构必须在同一行开始。但是,如果有多个值需要列出(如多个实现类),并且这些值是逗号分隔的,那么可以使用反斜杠(\)来换行。spring.factories 的名称是约定俗成的。如果试图使用一个不同的文件名,那么 Spring Boot 的自动配置机制将不会识别它4. 编写启动类,使用 SpringFactoriesLoader 来加载服务
@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());
}
}
}运行结果如下:
Hello Message
Hi Message
这种方式利用了Spring的SpringFactoriesLoader,它允许开发者提供接口的多种实现,并通过spring.factories文件来注册它们。这与JDK的SPI思想非常相似,只是在实现细节上有所不同。这也是Spring Boot如何自动配置的基础,它会查找各种spring.factories文件,根据其中定义的类来初始化和配置bean