java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java SPI在数据库驱动、SpringBoot自动装配应用

Java SPI在数据库驱动、SpringBoot自动装配中的应用方式

作者:心流时间

这篇文章主要介绍了Java SPI在数据库驱动、SpringBoot自动装配中的应用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

1. 初识SPI

1.1 SPI的作用

1.2 SPI的工作原理

对于 Java 原生 SPI,只需要满足下面几个条件:

1.3 SPI的三个组件:Service、Service Provider、ServiceLoader

SPI其实是一种思想:约定大于配置

简单来说就是:jdk提供一个公共API,具体的实现由具体的应用程序自己完成,并约定一套规则来让java的类加载机制发现实现类所在位置

通过这个约定,就不需要把服务放在代码中了,通过模块被装配的时候就可以发现服务类了

1.4 SPI使用场景

很多开源第三方jar包都有基于SPI的实现,在jar包META-INF/services中都有相关配置文件。

如下几个常见的场景:

看看 Dubbo 的扩展实现,就知道 SPI 机制用的多么广泛:

1.5 具体的SPI 源码分析(SPI的核心就是ServiceLoader.load()方法)

从上面的步骤可以总结以下两点:

1.6 SPI 的优缺点

优点:

缺点:

2. API、SPI、JNDI释义

是Java平台中的一种标准服务,它提供了一组API,允许Java应用程序查找和访问命名以及目录服务。

这个接口的设计目的是统一不同命名服务和目录服务的访问方式,使得开发人员无需关注具体的底层实现细节,就能在分布式环境中查找、绑定和管理资源。

通过JNDI,开发者可以:

JNDI常用于以下场景:

3. SPI应用举例1:加载数据库驱动

依赖引用:

<dependency>
    <groupId>com.oracle.database.jdbc</groupId>
    <artifactId>ojdbc8</artifactId>
    <version>21.9.0.0</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.23</version>
</dependency>

SPI是JDK内置的一种动态扩展点的实现。

简单来说,就是我们可以定义一个标准的接口,然后第三方的库里面可以实现这个接口。

那么,程序在运行的时候,会根据配置信息动态加载第三方实现的类,从而完成功能的动态扩展机制。

在Java里面,SPI机制有一个非常典型的实现案例,就是数据库驱动java.jdbc.Driver,JDK里面定义了数据库驱动类Driver,它是一个接口,JDK并没有提供实现。

具体的实现是由第三方数据库厂商来完成的。在程序运行的时候,会根据我们声明的驱动类型,来动态加载对应的扩展实现,从而完成数据库的连接。

除此之外,在很多开源框架里面都借鉴了Java SPI的思想,提供了自己的SPI框架,比如Dubbo定义了ExtensionLoader,实现功能的扩展。Spring提供了SpringFactoriesLoader,实现外部功能的集成。

当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。通过这个约定,就不需要把服务放在代码中了,通过模块被装配的时候就可以发现服务类了。

3.1 Jdbc连接数据库示例代码

有了SPI机制之后,Class.forName(“com.mysql.jdbc.Driver”);这条语句就不需要了,

java.util.ServiceLoader会负责到jar包的META-INF/services/java.sql.Driver中获取具体驱动实现类

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JdbcDriverManagerDemo {

    static final String MYSQL_JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
    private static final String MYSQL_URL = "jdbc:mysql://localhost:3306/lelele?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC";
    private static final String USER = "root";
    private static final String PASSWORD = "admin123";


    public static void main(String[] args) {


        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            // 有了SPI机制之后,这条语句就不要了,
            // java.util.ServiceLoader会负责到jar包的META-INF/services/java.sql.Driver中获取具体驱动实现类

            // Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection(MYSQL_URL, USER, PASSWORD);

            ps = conn.prepareStatement("select * from xin_stu_t_bak");
            rs = ps.executeQuery();

            while (rs.next()) {
                System.out.println(rs.getInt("id"));
            }

            // 处理查询结果
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                System.out.println("ID: " + id + ", Name: " + name);
            }

        } catch (SQLException se) {
            // 处理SQL错误
            se.printStackTrace();
        } catch (Exception e) {
            // 处理其他异常
            e.printStackTrace();
        } finally {
            // 关闭资源,确保在任何情况下都能正确关闭
            try {
                if (rs != null)
                    rs.close();
                if (ps != null)
                    ps.close();
                if (conn != null)
                    conn.close();
            } catch (SQLException se2) {
                se2.printStackTrace();
            }
        }

        System.out.println("end!");
    }
}

3.2 为什么获取连接前不用获取驱动了,是怎么获取到驱动的?

源码分析参照 本文章1.5节 具体的SPI 源码分析(SPI的核心就是ServiceLoader.load()方法)

下面是获取连接对象代码调试中的一些断点截图

使用线程上下文类加载器(ContextClassLoader)加载

如果不做任何的设置,Java应用的线程的上下文类加载器默认就是AppClassLoader。在核心类库使用SPI接口时,传递的类加载器使用线程上下文类加载器,就可以成功的加载到SPI实现的类。线程上下文类加载器在很多SPI的实现中都会用到。通常我们可以通过Thread.currentThread().getClassLoader()和Thread.currentThread().getContextClassLoader()获取线程上下文类加载器。

4. SPI应用举例2:SpringBoot自动装配

4.1 SpringBoot自动装配借鉴了SPI思想

Spring Factories自动装配借用了SPI机制,区别如图:

Spring Boot的自动装配机制确实与SPI(Service Provider Interface)有关联,但并不是直接使用Java标准SPI来实现自动装配。Spring Boot借鉴了SPI的思想,在其内部设计了一套更加灵活和强大的自动配置体系。

在Spring Boot中,META-INF/spring.factories文件的使用方式类似于SPI的服务提供者配置文件(META-INF/services/下资源文件),它允许jar包声明自己提供的自动配置类。这些自动配置类通过条件注解(如@ConditionalOnClass、@ConditionalOnBean等)来检查应用环境并决定是否生效,从而实现了根据项目依赖和运行时环境进行自动装配的功能。

因此,虽然Spring Boot没有完全采用Java SPI的标准流程,但其自动装配过程中对第三方库和服务的发现和加载机制受到了SPI思想的启发,并在此基础上进行了扩展和创新。

另:

META-IF/spring.factories是在Maven引入的Jar包中,每一个Jar都有自己META-IF/spring.factories,所以SpringBoot是去每一个Jar包里面寻找META-IF/spring.factories,而不是我的项目中存在META-IF/spring.factories(当然也可以存在,但是我项目的META-IF/spring.factories肯定没有类似以下这些东西)

4.2 Spring Boot 的自动装配(Auto-configuration)原理

@SpringBootApplication注解作用图:

好处:简化了配置

Springboot的SPI机制是怎么实现的?

Spring Boot 的自动装配(Auto-configuration)原理(上面也是)

启动类与@SpringBootApplication注解:

@EnableAutoConfiguration:

spring.factories中的自动配置类:

条件化装配:

覆盖默认配置:

自定义自动配置:

另:META-INF下另一个文件MANIFEST.MF的作用

在JAR文件中,MANIFEST.MF 文件常见的用途包括:

Main-Class声明:对于可执行的JAR文件(也称为Runnable JAR或Self-executable JAR),需要在MANIFEST.MF文件中指定一个主类(Main-Class)。例如:

Main-Class: com.example.Main

这样,用户可以直接通过命令行 java -jar myapp.jar 来运行这个JAR程序。

Class-Path声明:用于定义当前JAR文件依赖的其他外部JAR文件的路径。例如:

Class-Path: lib/library.jar lib/anotherlibrary.jar

这样,当运行此JAR时,Java会自动加载指定路径下的库。

Sealed指示:可以用来表示JAR是否被密封(sealed),即所有包内的类都必须来自同一个代码源,以增强安全性。

签名信息:如果JAR文件被数字签名,那么相关的签名证书、签名者信息等也会记录在MANIFEST.MF中。

服务提供者信息:在实现Java SPI(Service Provider Interface)时,MANIFEST.MF也可以包含ServiceProvider-Impl条目,列出实现了某个接口的服务提供者的全限定类名。

OSGi Bundle信息:在OSGi框架中,每个Bundle都有自己的MANIFEST.MF文件,其中包含了Bundle的标识符、版本、导入导出的包、激活器等重要信息。

5. Spring Boot Starter

使用Spring Boot Starter的主要优点包括:

例如,spring-boot-starter-web 包含了开发Web应用程序所需的所有基本依赖,如Spring MVC、Tomcat服务器等;而 spring-boot-starter-data-jpa 则包含了使用JPA进行数据库持久化的相关依赖。

6. 只有Spring Boot Starter有spring.factories文件吗

并非只有Spring Boot Starter才有spring.factories文件。spring.factories 文件是Spring Boot框架为了实现自动配置(Auto-Configuration)和SPI(Service Provider Interface)机制而使用的一种约定。虽然它通常与Spring Boot Starter一起使用,但并非Spring Boot Starter特有的。

在任何Java项目中,只要符合Spring Boot的自动装配规则,都可以创建自己的META-INF/spring.factories文件来声明自定义的自动配置类或者其他扩展点。这意味着即使不是官方提供的Spring Boot Starter模块,开发者也可以编写自己的模块,并在其jar包中包含spring.factories文件来提供额外的自动配置服务。

所以,spring.factories文件不仅限于Spring Boot Starter,而是所有遵循Spring Boot规范并希望通过这种方式进行扩展的模块都可能包含的一个核心配置文件。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文