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

相关文章
|
14天前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
140 73
|
9天前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
40 21
|
15天前
|
人工智能 前端开发 Java
Spring AI Alibaba + 通义千问,开发AI应用如此简单!!!
本文介绍了如何使用Spring AI Alibaba开发一个简单的AI对话应用。通过引入`spring-ai-alibaba-starter`依赖和配置API密钥,结合Spring Boot项目,只需几行代码即可实现与AI模型的交互。具体步骤包括创建Spring Boot项目、编写Controller处理对话请求以及前端页面展示对话内容。此外,文章还介绍了如何通过添加对话记忆功能,使AI能够理解上下文并进行连贯对话。最后,总结了Spring AI为Java开发者带来的便利,简化了AI应用的开发流程。
236 0
|
14天前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
14天前
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
22天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
76 14
|
1月前
|
XML Java 数据格式
Spring Core核心类库的功能与应用实践分析
【12月更文挑战第1天】大家好,今天我们来聊聊Spring Core这个强大的核心类库。Spring Core作为Spring框架的基础,提供了控制反转(IOC)和依赖注入(DI)等核心功能,以及企业级功能,如JNDI和定时任务等。通过本文,我们将从概述、功能点、背景、业务点、底层原理等多个方面深入剖析Spring Core,并通过多个Java示例展示其应用实践,同时指出对应实践的优缺点。
57 14
|
1月前
|
XML 前端开发 安全
Spring MVC:深入理解与应用实践
Spring MVC是Spring框架提供的一个用于构建Web应用程序的Model-View-Controller(MVC)实现。它通过分离业务逻辑、数据、显示来组织代码,使得Web应用程序的开发变得更加简洁和高效。本文将从概述、功能点、背景、业务点、底层原理等多个方面深入剖析Spring MVC,并通过多个Java示例展示其应用实践,同时指出对应实践的优缺点。
81 2
|
1月前
|
缓存 Java 数据库连接
深入探讨:Spring与MyBatis中的连接池与缓存机制
Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
67 4
|
2月前
|
前端开发 Java Spring
Spring MVC核心:深入理解@RequestMapping注解
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的核心,它将HTTP请求映射到控制器的处理方法上。本文将深入探讨`@RequestMapping`注解的各个方面,包括其注解的使用方法、如何与Spring MVC的其他组件协同工作,以及在实际开发中的应用案例。
49 4