Java 的SPI机制-框架组件开发利器

一、什么是SPI机制

SPI是Service Provider Interface 的简称,即服务提供者接口的意思,SPI说白了就是一种扩展机制,实现了框架和组件的解耦。

  • 框架设计者:我们在设计框架的时候留一个接口做为扩展点,然后加载固定路径配置文件中配置的实现类。这样框架不用把扩展的组件写死,由配置文件动态指定。
  • 扩展组件者:实现扩展点接口,然后把实现类名写到配置文件中,由框架自己去加载。

二、Java 中的SPI机制使用

案例:比如我们框架要有一个写日志的功能,然后日志具体的写在数据库还是log文件中由具体项目组去实现

第一步:定义一个写日日志的服务接口做扩展点

// 定义一个写日志的服务
public interface LogService{
    // 写日志的功能
    public void writeLog(String msg);  
}

第二步:定义一个工厂类,用来实例化日志服务,静态代码块是关键。

public class LogFactory{
    
    private static LogService logService;
    public LogService getLogService(){
        return logService;
    }    
    static {
   // 关键地方 查找classPath下的META-INFO/services目录下名字为【SPIService全限名.service】 的文件,读取文件中写的类名,通过反射实例化到ServiceLoader里。和spring的依赖注入一样。
        ServiceLoader<LogService> load = ServiceLoader.load(LogService.class);
        Iterator<LogService> it = load.iterator();
        while(it.hasNext()){
            LogService service = it.next();
            LogFactory.logService = service;
        }       
    }
}

第三步:开发扩展点,打成jar包,由项目组自己去实现

//第一步:实现LogService打成Jar包
public class FileLogService implement LogService{         
     // 自定义实现,输出到控制台
    public void writeLog(String msg){
		System.out.print(msg);
	}     
}
// 第二步:在classPath下的META-INFO/services下创建一个文件名为接口全限名的文件。文件内容写实现类名。
// 如文件名为【com.cn.LogService】 文件内容:com.cn.FileLogService

在Java中有很多这样的案例,如JDBC驱动的加载。JDBC驱动类jar包里有一个java.sql.Driver 的文件,里面写的是驱动类实现的类名。在DriverManager 类里有一个静态代码块,通过ServiceLoader类去加载实现类,大家可以翻翻源码看。

三、Spring 中的SPI机制使用

1. Spring中的SPI机制解决什么问题?

我们知道SpringBoot 默认只扫描启动类同层级和向下目录里的类,注册到Spring容器中,当我们向要被Spring管理的Bean不在Spring项目扫描路径下怎么半呢?

  • 方法一:在Spring Boot 项目中配置ComponentScan注解的扫描路径。
  • 方法二:通过在Spring Boot 项目中添加@EnableAutoConfiguration注解,并自定义@EnableXXXXConfiguration的注解,通过注解中的方法注入Bean。
  • 方法三:手动硬编码进行Bean的注入

由以上可以发现外部jar包里的类和项目耦合很紧,当外部jar包类换名字,换路径等等都需要项目进行修改,有没有更好的方法呢?接下来继续看Spring的SPI机制解决这个问题。

2. Spring Factories机制原理

  • 核心类SpringFactoriesLoader
    Spring Factories机制通过META-INF/spring.factories文件获取相关的实现类的配置信息,而SpringFactoriesLoader的功能就是读取META-INF/spring.factories,并加载配置中的类。SpringFactoriesLoader主要有两个方法:loadFactories和loadFactoryNames。
  • 核心方法一:loadFactoryNames
    用于按接口获取Spring Factories文件中的实现类的全称,其方法定义如下所示,其中参数factoryType指定了需要获取哪个接口的实现类,classLoader用于读取配置文件资源。 public static List loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader)
  • 核心方法二:lloadFactories
    用于按接口获取Spring Factories文件中的实现类的实例,其方法定义如下所示,其中参数factoryType指定了需要获取哪个接口的实现类,classLoader用于读取配置文件资源和加载类。 public static List loadFactories(Class factoryType, @Nullable ClassLoader classLoader)

看完这个大家可能都明白了,它和Java中的SPI 机制套路一样,就是加载META-INF/spring.factories中配置的类,并进行实例化。

3. 在Spring中如何使用

  • Spring boot中默认使用了很多factories机制,主要包含:
    • ApplicationContextInitializer:用于在spring容器刷新之前初始化Spring
    • ConfigurableApplicationContext的回调接口。
    • ApplicationListener:用于处理容器初始化各个阶段的事件。
    • AutoConfigurationImportListener:导入配置类的时候,获取类的详细信息。
    • AutoConfigurationImportFilter:用于按条件过滤导入的配置类。
    • EnableAutoConfiguration:指定自动加载的配置类列表。
      我们可以自定义一个类,然后通过配置来指定自定义的Bean何时初始化被Spring管理

案例:

  • 第一步:定义一个类 cn.com.SpringBeanDemo
@Configruation
public class SpringBeanDemo{
// 这里可以写自己的内容,比如初始化方法、加载配置信息,注入其他Bean对象

}
  • 第二步:编写META-INF/spring.factories文件
# 注意写法规则,每个类要换一行,后面用 \ 做换行标记,如有多个实现,用逗号隔开。 注释用# 号做标记,关于spring.factories文件在Spring很多jar包里都有,可以去做参照。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.com.SpringBeanDemo,\
cn.com.SpringBeanDemo2

这样当注解EnableAutoConfiguration 启动生效的时候会把自定义的SpringBeanDemo 进行实例化,交由Spring容器管理。

案例二:

案例一中我们是通过Spring的EnableAutoConfiguration 注解,把SpringBeanDemo 注入到Spring容器中,那我们如何自定义一个类来控制 SpringBeanDemo 的注入呢?


@Service
public class MyAutoConfiguration{
    @PostConstruct
    public void printService(){
        List<String> serviceNames = SpringFactoriesLoader.loadFactoryNames(SpringBeanDemo.class,null);
        for (String serviceName:serviceNames){
            System.out.println(serviceName);
        }
        List<SpringBeanDemo> services = SpringFactoriesLoader.loadFactories(SpringBeanDemo.class,null);
        for (DemoService demoService:services){
            demoService.printName();
        }
    }
}

通过以上方式我们可以自定义加载配置类的时机,我们可以在SpringFactoriesLoader.loadFactories 加载类的时候做很多我们自己项目的需求。

以上就是SPI的用法啦,大家快快去实践下,看项目上哪些地方可以封装成组件。

  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值