java spi知识点 [Spring Boot 源解系列] 从 Java SPI 到 Spring Factories 扩展

今天学习了一个java spi的东西,挺好玩,分享给大家。估计好多人也都不记得了。

原文地址:https://www.lagou.com/lgeduarticle/99648.html

[Spring Boot 源解系列] 从 Java SPI 到 Spring Factories 扩展

Service Provider Interface,简写是 SPI。这是 Java 原生实现用于被第三方实现或余留的扩展 API。

 

 

 

上图为 SPI 实现的思路。我们可以看到,实际上是“调用方”只需要对接“标准的服务接口”,而实际上屏蔽了“服务提供方”的 A 和 B,这就给我们在实现上面提供了很大的灵活性。

相信你已经对 SPI 有个基本的了解了。那么我们来看看 按照 Java SPI 思路图来实现一个小栗子。

// 实现一个接口,作为标准服务接口
public interface StandardService {
    public void offerService();
}
复制代码

我们根据标准服务接口,编写两个不同逻辑不同的实现类

public class AStandardService implements StandardService {
    public void offerService() {
        System.out.println("我是服务 A,我可以为你提供 A 服务");
    }
}
复制代码

接着是服务 B

public class BStandardService implements StandardService {
    public void offerService() {
        System.out.println("我是服务 B,我可以为你提供 B 服务");
    }
}
复制代码

这时候我们的“服务”已经写好了。但是怎么让别人发现呢?其实 Java SPI 的规范会告诉你,你只需要在 classpath 下的 META-INF/services 文件夹里,放一个以“标准服务接口”的全限定名为名的文件,然后文件内写“服务提供方”的全限定名即可。如有多个,分行写。

而当外部程序装配这个模块的时候,就能通过该 jar 包 META-INF/services/ 里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。

根据 SPI 的约定,那我就直接在 META-INF/services/ 下创建一个名为 com.jc.demov20200215.service.StandardService,内容为 com.jc.demov20200215.service.impl.AStandardService

接下来就是调用方见证奇迹的时候了

@SpringBootApplication
public class Demov20200215Application {

    public static void main(String[] args) {
        ServiceLoader<StandardService> serviceLoader = ServiceLoader.load(StandardService.class);
        serviceLoader.forEach(StandardService::offerService);
    }

}
复制代码

Spring.factories In Spring Boot

Spring 中也有类似于 Java SPI 这样扩展的地方。

所以我们直接写一个关于如何使用 Spring Boot 的 Springfactories 机制进行扩展。

但是说明一点,Spring 余留这个扩展机制很大原因不是为了给我们去加载自定义 Bean,而是为了让我们去扩展 Spring 其内部的 Bean,自定义框架想要的效果。

例如说,我们学 Spring Framework 的时候,我们都知道在 web.xml 里面配置一个 ApplicationContextInitializer 的类。这个类的作用是框架启动时进行初始化数据/配置等。那么问题来了,假设我们有一些初始化参数想让 Spring 帮我们初始化,那我们怎么办呢?答案是:实现 ApplicationContextInitializer 接口,然后里面加入我们所初始化的参数。但是问题又来了,那我们怎么把我们的实现类告诉 Spring 呢?难不成是 @Autowired ?不,很显然不行的!

其实这里的原理跟 Java SPI 思考原理一样的。Spring 余留了 Spring factories 这种机制,就是它只关心你的东西放在哪里,不关心你怎么实现!

明白~开始操作! 首先我们要创建一个实现了 ApplicationContextInitializer 的类

public class FirstInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        Map<String, Object> map = new HashMap<>();
        map.put("key","value");
        MapPropertySource mapPropertySource = 
                new MapPropertySource("firstInitializer", map);
        environment.getPropertySources().addLast(mapPropertySource);
        System.out.println("firstInitializer finish");
    }
}
复制代码

我们写好了自定义的 Initializer,现在我们要告诉 Spring 它我们的 Initializer 放在哪里!还记得上面的 Java SPI 吗?它是放在了classpath 的 META-INF/services 文件夹下的。而 Spring 是放在 它是放在了classpath 下的 META-INF/spring.factories 文件。

创建好文件后,我们写入内容

org.springframework.context.ApplicationContextInitializer=com.jc.demov20200215.initializer.FirstInitializer
复制代码

(如果有多个 initializer 可以使用逗号分割)

那怎么测试是否 Spring 已经加载我们的配置?其实我们可以写一个 Controller 类或者 service 类(都可以),只要实现 ApplicationContextAware 接口,获取到 ApplcaitonContext 。再通过 ApplcaitonContext 获得环境即可。

public class TestService implements ApplicationContextAware {
    
    private ApplicationContext applicationContext;

    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.getApplicationContext().getEnvironment().getProperty("key");   
    }
}
复制代码

输入即可获得! 好以上已经讲完了本篇文章我学习到的知识点以及例子

conclusion 结论

从 SPI 到 Spring 我们可以得出以下结论:

Tips:扩展方式 = 基于接口编程 + 配置文件 + 策略模式

或许你想说:“骗人,说好的源码解析呢?还想知道 Sprint 是如何实现加载和解析这些扩展信息并加载进 Spring 的上下文呢!”

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值