【小家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()方法

相关文章
|
12天前
|
XML Java 数据格式
SpringBoot入门(8) - 开发中还有哪些常用注解
SpringBoot入门(8) - 开发中还有哪些常用注解
30 0
|
8天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
28 2
|
30天前
|
Java Spring
在使用Spring的`@Value`注解注入属性值时,有一些特殊字符需要注意
【10月更文挑战第9天】在使用Spring的`@Value`注解注入属性值时,需注意一些特殊字符的正确处理方法,包括空格、引号、反斜杠、新行、制表符、逗号、大括号、$、百分号及其他特殊字符。通过适当包裹或转义,确保这些字符能被正确解析和注入。
|
19天前
|
XML JSON Java
SpringBoot必须掌握的常用注解!
SpringBoot必须掌握的常用注解!
42 4
SpringBoot必须掌握的常用注解!
|
21天前
|
存储 缓存 Java
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
68 2
|
21天前
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
34 1
|
1月前
|
Java 关系型数据库 MySQL
mysql5.7 jdbc驱动
遵循上述步骤,即可在Java项目中高效地集成MySQL 5.7 JDBC驱动,实现数据库的访问与管理。
149 1
|
1月前
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
|
15天前
|
存储 安全 Java
springboot当中ConfigurationProperties注解作用跟数据库存入有啥区别
`@ConfigurationProperties`注解和数据库存储配置信息各有优劣,适用于不同的应用场景。`@ConfigurationProperties`提供了类型安全和模块化的配置管理方式,适合静态和简单配置。而数据库存储配置信息提供了动态更新和集中管理的能力,适合需要频繁变化和集中管理的配置需求。在实际项目中,可以根据具体需求选择合适的配置管理方式,或者结合使用这两种方式,实现灵活高效的配置管理。
12 0
|
1月前
|
XML Java 数据库
Spring boot的最全注解
Spring boot的最全注解
下一篇
无影云桌面