【小家Spring】探讨注解驱动Spring应用的机制,详解ServiceLoader、SpringFactoriesLoader的使用(以JDBC、spring.factories为例介绍SPI)(上)

简介: 【小家Spring】探讨注解驱动Spring应用的机制,详解ServiceLoader、SpringFactoriesLoader的使用(以JDBC、spring.factories为例介绍SPI)(上)

前言


在之前的一篇文章【小家Spring】Spring注解驱动开发—Servlet 3.0整合Spring MVC(不使用web.xml部署描述符,使用ServletContainerInitializer)


它介绍了基于注解驱动的Servlet容器的启动。今天刚好回头看到了自己写的这篇文章,自己心里就萌生了几个疑问:原理是啥?为何就能自动的这么样执行呢?通过配置文件就能加载类这肯定涉及到类加载机制吧?


带着这些疑问,就决定深究一番,然后做出如下记录,供读者们参考哈~~~


ServiceLoader:服务提供者加载器


SPI概念介绍


SPI:Service Provider Interfaces(服务提供者接口)。正如从SPI的名字去理解SPI就是Service提供者接口


SPI定位:给服务提供厂商与扩展框架功能的开发者使用的接口。


比如大名鼎鼎的JDBC驱动,Java只提供了java.sql.Driver这个SPI接口,具体的实现由各服务提供厂商(比如MySql、Oracle等)去提供。Mysql的驱动实现类为:com.mysql.jdbc.Driver,Oracle的驱动实现类为:oracle.jdbc.driver.OracleDriver,PostgreSQL 的为:org.postgresql.Driver…


ServiceLoader


首先我们简单的看看javadoc和源码字段说明

/**
* 一个简单的服务提供商加载设施
* 服务 是一个熟知的接口和类(通常为抽象类)集合。服务提供者 是服务的特定实现
* 服务提供者可以以扩展的形式安装在 **Java 平台的实现中**.也就是将 jar 文件放入任意常用的扩展目录中
* 也可通过将提供者加入应用程序类路径,或者通过其他某些特定于平台的方式使其可用(所以并不限定你的方式,不在类路径也无所谓哟)
*/
// @since 1.6 Java6以后才有的工具类
public final class ServiceLoader<S> implements Iterable<S> {
  // 这个路径非常的重要,最终就是去此路径读取
    private static final String PREFIX = "META-INF/services/";
    // 指向对象类型的 Class<S> 对象  最后通过 Class<S> 对象来构造服务实现类 S 的实例 s 
    private final Class<S> service;
  //类加载器 ClassLoader
    private final ClassLoader loader;
    private final AccessControlContext acc;
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    private LazyIterator lookupIterator;
  ...


ServiceLoader与ClassLoader


ServiceLoader与ClassLoader是Java中2个即相互区别又相互联系的加载器。(ServiceLoader是一种加载类的规范,底层还是依赖于ClassLoader的)


JVM利用ClassLoader将类载入内存,这是一个类生命周期的第一步。(一个java类的完整的生命周期会经历加载、连接、初始化、使用、和卸载五个阶段,当然也有在加载或者连接之后没有被初始化就直接被使用的情况。 Tips:初始化:给类中的静态变量赋予正确的初始值)


ServiceLoader: 上面JavaDoc里已经有了一些概念性的说明。可以说它的使用是非常松散的,没有太多的强制要求。 唯一强制要求的是,提供者类(实现类)必须具有不带参数的构造方法,以便它们可以在加载中被实例化(因此,子接口肯定不行(下面Demo会验证))


ServiceLoader位于java.util包,ClassLoader位于java.lang包。因此可议看出它俩的定位也是不一样的,ServiceLoader被认为是一种工具


我们可以简单的认为:ServiceLoader也像ClassLoader一样,能装载类文件,但是使用时有区别,具体区别如下:


  1. ServiceLoader装载的是一系列有某种共同特征的实现类,而ClassLoader是个万能加载器;
  2. ServiceLoader装载时需要特殊的配置,使用时也与ClassLoader有所区别;
  3. ServiceLoader还实现了Iterator接口。


ServiceLoader使用方式


毕竟光说不练假把式


我从这个问题的抛出,然后逐步讲解:


一般我们使用接口的实现类都是静态new一个实现类赋值给接口引用,如下:

HelloService service = new HelloImpl();


如果需要动态的获取一个接口的实现类呢?

全局扫描全部的Class,然后判断是否实现了某个接口?代价太大,相信没人愿意去这么做吧。

一种合适的方式就是使用配置文件,把实现类名配置在某个地方,然后读取这个配置文件,获取实现类名。而JDK为我们提供的工具ServiceLoader就是采用的这种方式

(该思想其实在Spring体系内,存在大量的使用,并且我觉得比JDK做得还好~~),当然JDK的好处是:它是规范,更容易广而周知,通用性更强


ServiceLoader它的使用方式可列为4个步骤:

1.创建一个接口文件

2.在resources资源目录下创建META-INF/services文件夹

3.在上面services文件夹中创建文件:以接口全类名命名

4.在该文件内,写好实现类的全类名们


使用Demo如下:

// SPI服务接口
public interface IService {
    String sayHello();
    String getScheme();
}
// 主要为了测试,看看是子接口是否会被加载
public interface MyIService extends IService {
}


准备实现类(两个),模拟服务提供商:


// 服务提供商的具体实现1:HDFS实现
public class HDFSService implements IService {
    @Override
    public String sayHello() {
        return "Hello HDFSService";
    }
    @Override
    public String getScheme() {
        return "hdfs";
    }
}
// 服务提供商的具体实现2:Local实现
public class LocalService implements IService {
    @Override
    public String sayHello() {
        return "Hello LocalService";
    }
    @Override
    public String getScheme() {
        return "local";
    }
}


准备一个服务的配置文件:META-INF/services/com.fsx.maintest.IService,然后在该文件里书写上实现类们,内容如下:

image.png


com.fsx.serviceloader.MyIService // 注意这个是接口
com.fsx.serviceloader.HDFSService
com.fsx.serviceloader.LocalService


main函数测试:


    public static void main(String[] args) {
        // 加载IService下所有的服务
        ServiceLoader<IService> serviceLoader = ServiceLoader.load(IService.class);
        for (IService service : serviceLoader) {
            System.out.println(service.getScheme() + "=" + service.sayHello());
        }
    }


报错:


java.util.ServiceConfigurationError: com.fsx.serviceloader.IService: Provider com.fsx.serviceloader.MyIService could not be instantiated


很显然写了一个接口,而接口是不能够实例化的。注意到上面说了唯一一个强制要求,就是必须能够实例化(有空的构造函数) 因此做修改如下(只写实现类):


com.fsx.serviceloader.HDFSService
com.fsx.serviceloader.LocalService


运行正常,输出如下:


hdfs=Hello HDFSService
local=Hello LocalService


可以看到ServiceLoader可以根据IService把定义的两个实现类找出来,返回一个ServiceLoader的实现,而ServiceLoader实现了Iterable接口,所以可以通过ServiceLoader来遍历所有在配置文件中定义的类的实例。


几个注意事项:

1、文件名称是服务接口类型的完全限定

2、文件内若有多个实现类,每行一个(末尾不要有空格和,等符号)

3、文件必须使用 UTF-8 编码


另外ServiceLoader拿实例提供者是有缓存的,策略如下:

1、服务加载器维护到目前为止已经加载的提供者缓存

2、每次调用 iterator 方法返回一个迭代器,它首先按照实例化顺序生成缓存的所有元素

3、然后以延迟方式查找和实例化所有剩余的提供者,并且依次将每个提供者添加到缓存

4、若清除缓存,可议调用ServiceLoader.reload()方法

相关文章
|
1月前
|
监控 Java API
Spring Boot 3.2 结合 Spring Cloud 微服务架构实操指南 现代分布式应用系统构建实战教程
Spring Boot 3.2 + Spring Cloud 2023.0 微服务架构实践摘要 本文基于Spring Boot 3.2.5和Spring Cloud 2023.0.1最新稳定版本,演示现代微服务架构的构建过程。主要内容包括: 技术栈选择:采用Spring Cloud Netflix Eureka 4.1.0作为服务注册中心,Resilience4j 2.1.0替代Hystrix实现熔断机制,配合OpenFeign和Gateway等组件。 核心实操步骤: 搭建Eureka注册中心服务 构建商品
309 3
|
3月前
|
人工智能 JSON 安全
Spring Boot实现无感刷新Token机制
本文深入解析在Spring Boot项目中实现JWT无感刷新Token的机制,涵盖双Token策略、Refresh Token安全性及具体示例代码,帮助开发者提升用户体验与系统安全性。
363 5
|
30天前
|
人工智能 监控 安全
如何快速上手【Spring AOP】?核心应用实战(上篇)
哈喽大家好吖~欢迎来到Spring AOP系列教程的上篇 - 应用篇。在本篇,我们将专注于Spring AOP的实际应用,通过具体的代码示例和场景分析,帮助大家掌握AOP的使用方法和技巧。而在后续的下篇中,我们将深入探讨Spring AOP的实现原理和底层机制。 AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的核心特性之一,它能够帮助我们解决横切关注点(如日志记录、性能统计、安全控制、事务管理等)的问题,提高代码的模块化程度和复用性。
|
1月前
|
安全 算法 Java
在Spring Boot中应用Jasypt以加密配置信息。
通过以上步骤,可以在Spring Boot应用中有效地利用Jasypt对配置信息进行加密,这样即使配置文件被泄露,其中的敏感信息也不会直接暴露给攻击者。这是一种在不牺牲操作复杂度的情况下提升应用安全性的简便方法。
588 10
|
2月前
|
安全 Java Nacos
0代码改动实现Spring应用数据库帐密自动轮转
Nacos作为国内被广泛使用的配置中心,已经成为应用侧的基础设施产品,近年来安全问题被更多关注,这是中国国内软件行业逐渐迈向成熟的标志,也是必经之路,Nacos提供配置加密存储-运行时轮转的核心安全能力,将在应用安全领域承担更多职责。
|
2月前
|
NoSQL Java Redis
Redis基本数据类型及Spring Data Redis应用
Redis 是开源高性能键值对数据库,支持 String、Hash、List、Set、Sorted Set 等数据结构,适用于缓存、消息队列、排行榜等场景。具备高性能、原子操作及丰富功能,是分布式系统核心组件。
343 2
|
2月前
|
Java Linux 网络安全
Linux云端服务器上部署Spring Boot应用的教程。
此流程涉及Linux命令行操作、系统服务管理及网络安全知识,需要管理员权限以进行配置和服务管理。务必在一个测试环境中验证所有步骤,确保一切配置正确无误后,再将应用部署到生产环境中。也可以使用如Ansible、Chef等配置管理工具来自动化部署过程,提升效率和可靠性。
284 13
|
2月前
|
JSON 前端开发 Java
Spring MVC 核心组件与请求处理机制详解
本文解析了 Spring MVC 的核心组件及请求流程,核心组件包括 DispatcherServlet(中央调度)、HandlerMapping(URL 匹配处理器)、HandlerAdapter(执行处理器)、Handler(业务方法)、ViewResolver(视图解析),其中仅 Handler 需开发者实现。 详细描述了请求执行的 7 步流程:请求到达 DispatcherServlet 后,经映射器、适配器找到并执行处理器,再通过视图解析器渲染视图(前后端分离下视图解析可省略)。 介绍了拦截器的使用(实现 HandlerInterceptor 接口 + 配置类)及与过滤器的区别
193 0
|
3月前
|
Java 测试技术 数据库
说一说 SpringBoot 整合 Junit5 常用注解
我是小假 期待与你的下一次相遇 ~
|
3月前
|
SQL Java 数据库
解决Java Spring Boot应用中MyBatis-Plus查询问题的策略。
保持技能更新是侦探的重要素质。定期回顾最佳实践和新技术。比如,定期查看MyBatis-Plus的更新和社区的最佳做法,这样才能不断提升查询效率和性能。
147 1