Java中的SPI(Service Provider Interface)是一种服务发现机制,允许服务提供者动态地为服务接口提供实现。SPI机制通过在运行时查找并加载服务实现类,从而实现了松耦合的设计。以下是SPI的工作原理和机制:
SPI的工作原理
SPI机制的使用
步骤一:定义服务接口
package com.example;
public interface MyService {
    void execute();
}
步骤二:实现服务接口
package com.example.impl;
import com.example.MyService;
public class MyServiceImpl implements MyService {
    @Override
    public void execute() {
        System.out.println("Executing MyServiceImpl");
    }
}
com.example.impl.MyServiceImpl
步骤四:使用ServiceLoader加载服务
package com.example;
import java.util.ServiceLoader;
public class Main {
    public static void main(String[] args) {
        ServiceLoader<MyService> serviceLoader = ServiceLoader.load(MyService.class);
        for (MyService service : serviceLoader) {
            service.execute();
        }
    }
}
运行机制
- 加载配置文件:ServiceLoader会从类路径下的META-INF/services目录中查找名为com.example.MyService的文件。
- 解析配置文件:读取文件内容,获取实现类的全限定名。
- 加载实现类:使用反射机制实例化实现类。
- 提供服务:将实现类的实例返回给应用程序进行使用
SPI的优点
- 松耦合:实现类和接口解耦,方便替换实现。
- 扩展性好:新服务提供者只需要实现接口并在配置文件中声明即可,无需修改客户端代码。
- 灵活性高:可以在运行时动态加载和替换服务实现。
 SPI的注意事项
- 安全性:确保服务实现类的安全性,防止加载恶意实现。
- 性能:由于需要在运行时进行反射和解析,可能会有一定的性能开销。
- 类路径管理:确保配置文件和实现类正确放置在类路径下。
注:
- 放到META-INF/services下的原因:
 
- META-INF/services目录下创建的文件应以服务接口的全限定名命名,文件内容则是服务实现类的全限定名。具体来说:
- 文件名:应该是服务接口的全限定名。
- 文件内容:应该是服务提供者(实现类)的全限定名。
project-root/
 ├── src/
 │   ├── main/
 │   │   ├── java/
 │   │   │   ├── com/
 │   │   │   │   ├── example/
 │   │   │   │   │   ├── MyService.java
 │   │   │   │   │   └── impl/
 │   │   │   │   │       └── MyServiceImpl.java
 │   │   ├── resources/
 │   │   │   └── META-INF/
 │   │   │       └── services/
 │   │   │           └── com.example.MyService
 └── pom.xml (or build.gradle if using Gradle)
关键点
- 配置文件的命名:必须与服务接口的全限定名完全一致。
- 配置文件的内容:应该是服务实现类的全限定名,可以包含多个实现类,每行一个。
- 类路径管理:确保 META-INF/services 目录及其文件在运行时可见(通常会在项目的 src/main/resources 目录下创建该结构)。
- Java的SPI机制是半自动的,意思是: 
  - 自动:配置文件和实现类被正确放置在类路径中,ServiceLoader会自动找到这些配置文件并加载相应的实现类。
- 手动:你必须手动调用ServiceLoader来执行加载过程。
 
springboot中使用
project-root/
 ├── src/
 │   ├── main/
 │   │   ├── java/
 │   │   │   ├── com/
 │   │   │   │   ├── example/
 │   │   │   │   │   ├── MyService.java
 │   │   │   │   │   ├── MyServiceAutoConfiguration.java
 │   │   │   │   │   └── impl/
 │   │   │   │   │       └── MyServiceImpl.java
 │   │   ├── resources/
 │   │   │   └── META-INF/
 │   │   │       └── spring.factories
 └── pom.xml (or build.gradle if using Gradle)
前面一样,后面创建一个自动配置类,例如MyServiceAutoConfiguration:
package com.example;
import com.example.impl.MyServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyServiceAutoConfiguration {
    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}
创建spring.factories文件
 在src/main/resources/META-INF/spring.factories中创建文件,内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyServiceAutoConfiguration
应用主类为:
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(Main.class, args);
        MyService myService = context.getBean(MyService.class);
        myService.execute();
    }
}
解释
- spring.factories文件:这是Spring Boot用来配置自动配置类的方式。你将自动配置类(如MyServiceAutoConfiguration)的全限定名放在EnableAutoConfiguration条目下。
- 自动配置类:MyServiceAutoConfiguration类中定义了一个@Bean方法,用来创建并注册MyService实现(MyServiceImpl)到Spring应用上下文。
- Spring Boot应用:当Spring Boot启动时,它会自动扫描spring.factories文件,并加载配置的自动配置类,从而自动配置MyService实现。
通过这种方式,Spring Boot可以在启动时自动加载和配置你的服务实现,使得你的服务可以在应用的任何地方通过依赖注入(如通过@Autowired或ApplicationContext#getBean)来使用。
使用 spring.factories 文件和 Spring Boot 的自动配置机制可以被看作是一种 SPI(Service Provider Interface)实现方式,不过它与传统的 Java SPI 有所不同。让我们详细看看两者的区别和实现方式:
 传统 Java SPI
-  
- 定义服务接口。
- 实现服务接口。
- 在 META-INF/services 目录下创建文件,文件名为服务接口的全限定名,内容为实现类的全限定名。
- 使用 ServiceLoader 动态加载服务提供者。
 
- 定义服务接口。
- 实现服务接口。
- 创建自动配置类,将服务实现注册为 Spring Bean。
- 在 META-INF/spring.factories 文件中配置自动配置类。
- Spring Boot 自动加载和配置服务提供者。
比较
- 传统 Java SPI: 
  - 配置文件位置:META-INF/services
- 配置文件内容:服务实现类的全限定名
- 动态加载机制:ServiceLoader
- 适用范围:所有 Java 应用
 
- Spring Boot 自动配置机制: 
  - 配置文件位置:META-INF/spring.factories
- 配置文件内容:自动配置类的全限定名
- 动态加载机制:Spring Boot 自动配置
- 适用范围:Spring Boot 应用
 
在Spring Boot中,spring.factories文件不仅限于org.springframework.boot.autoconfigure.EnableAutoConfiguration配置条目,实际上可以包含许多不同类型的配置。以下是一些常用的配置条目:
- EnableAutoConfiguration:自动配置类
- ApplicationListener:应用程序事件
- EnvironmentPostProcessor:环境后处理器
EnableAutoConfiguration
 这是最常用的条目,用于指定自动配置类。这些类将在Spring Boot应用启动时自动加载并应用。
 ApplicationListener
 用于指定应用程序事件,这些在特定事件(如应用启动或上下文刷新)发生时被调用。
 EnvironmentPostProcessor
 用于在Spring环境初始化后执行一些定制化的处理。
使用implements CommandLineRunner
在Spring Boot中,虽然实现CommandLineRunner接口可以用于在应用启动后执行特定代码,但这与传统的SPI机制有所不同。CommandLineRunner是在Spring Boot应用启动完成并且所有的Spring Beans都已经初始化之后运行的一个接口。
如果你想在Spring Boot中实现类似SPI的启动方式,可以结合使用CommandLineRunner和Spring的自动配置机制(如spring.factories文件)来实现。在这种情况下,CommandLineRunner可以作为一种触发机制,用于在Spring Boot启动时执行一些自定义逻辑。
- 确认类路径:确保你的类路径正确,并且所有类文件和配置文件都在正确的位置。
- 配置类(WsDatasourceConfig):
- 确保你的配置类上有正确的注解:@Configuration。
package cn.example.datasource.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class WsDatasourceConfig {
    @Bean
    public WsDatasourceBootstrap wsDatasourceBootstrap() {
        return new WsDatasourceBootstrap();
    }
}
启动类(WsDatasourceBootstrap):
- 确保你的启动类实现了CommandLineRunner接口。
- 确保ConfigurableEnvironment被正确注入。
@Component
public class WsDatasourceBootstrap implements CommandLineRunner {
    private static final Logger LOGGER = LoggerFactory.getLogger(WsDatasourceBootstrap.class);
    @Autowired
    private ConfigurableEnvironment environment;
    @Override
    public void run(String... args) throws Exception {
        YmlUtils.environment = environment;
        LOGGER.info("已就绪");
    }
}
spring.factories文件:
- 确保你的spring.factories文件路径为src/main/resources/META-INF/spring.factories。
- 确保文件内容格式正确。
然后启动项目,日志会打印“已就绪”
ConfigurableEnvironment
 ConfigurableEnvironment是Spring Framework中的一个接口,它扩展了Environment接口,提供了对环境属性进行配置的能力。在Spring应用中,ConfigurableEnvironment通常用于访问和操作环境属性(如系统属性、环境变量、应用程序属性等),并且可以通过PropertySource添加或修改环境属性。
ConfigurableEnvironment 的用途
- 环境变量和系统属性访问:
- 可以访问系统属性、环境变量以及各种配置源(如application.properties或application.yml文件)的属性。
 添加或修改属性源:
- 可以动态地添加、修改或删除属性源,从而改变应用程序的行为。
- 配置文件(Profiles)管理:
- 设置激活的配置文件:可以在运行时激活或切换配置文件,以便在不同环境(如开发、测试、生产)之间切换。
- 设置默认配置文件:可以设置应用程序的默认配置文件。
- 动态调整配置:
- 在应用程序运行时,可以动态调整和更新配置属性,而无需重启应用。
- 管理属性源(Property Sources):
- 添加属性源:可以在运行时添加自定义的属性源,例如从数据库或外部服务加载的配置。
- 修改属性源顺序:可以动态调整属性源的优先级和顺序。
- 删除属性源:可以在运行时删除不需要的属性源。
environment.getProperty(key);获取系统值…