⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7395433541482823715 「流浪猿」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

什么是Java的SPI

Java SPI(‌Service Provider Interface)‌是一种服务提供界面,‌它是Java提供的一种服务发现和加载机制,‌允许开发者为接口定义多种实现,‌并在运行时动态地发现和加载这些实现。‌

Java SPI机制的核心在于它提供了一种方式,‌使得服务提供者可以根据SPI的约定,‌为某个接口提供具体的实现类。‌这些实现类被放置在特定的位置,‌如META-INF/services目录下,‌并通过配置文件指定。‌当需要使用这些服务时,‌Java运行时环境能够自动扫描这些目录,‌找到并加载相应的实现类,‌从而实现服务的动态发现和加载。‌

Java SPI的主要用途包括:‌

  • 服务提供者可以在不修改业务代码的情况下,‌为框架或库提供扩展点。‌
  • 允许在运行时动态地插入或更换组件实现,‌鼓励松耦合的设计原则。‌
  • 允许第三方扩展和替换核心库中的组件,‌丰富了Java生态,‌为开发者提供了极大的灵活性。‌

在Java中,‌SPI被广泛应用于各种框架和库的扩展,‌如Servlet容器初始化、‌类型转换、‌日志记录等场景。‌通过SPI机制,‌Java应用程序可以在不修改业务代码的情况下,‌轻松地集成和使用第三方提供的服务实现,‌从而提高了软件的可扩展性和可维护性

SPI和API的区别

SPI和API的主要区别在于它们的定义方式、‌调用方式、‌灵活性、‌依赖关系以及用途。‌

  • 定义方式:‌ API是由开发者主动编写并公开给其他开发者使用的,‌而SPI是由框架或库提供方定义的接口,‌供第三方开发者实现。‌
  • 调用方式:‌ API通过直接调用接口的方法来使用功能,‌而SPI是通过配置文件来指定具体的实现类,‌然后由框架或库自动加载和调用。‌
  • 灵活性:‌ API的实现类必须在编译时就确定,‌无法动态替换;‌而SPI的实现类可以在运行时根据配置文件的内容进行动态加载和替换。‌
  • 依赖关系:‌ API是被调用方依赖的,‌即应用程序需要引入API所在的库才能使用其功能;‌而SPI是调用方依赖的,‌即框架或库需要引入第三方实现类的库才能加载和调用。‌
  • 用途:‌ API通常用于描述库、‌框架、‌操作系统、‌服务等对外提供的编程接口,‌开发者通过API调用相应的功能来实现自己的应用程序。‌而SPI定义了一种插件式的架构,‌允许开发者定义接口,‌并通过服务提供者来提供不同的实现,‌主要目的是允许系统在运行时发现和加载具体的服务提供者,‌从而实现动态扩展和替换功能的能力。‌

综上所述,‌API是一种规范,‌描述了如何与一个组件进行交互;‌而SPI则是一种机制,‌用于动态地发现和加载实现了特定接口的组件。‌

实现过程

0.目录结构

sa-auth  父工程
-- sa-auth-bus 业务工程
-- sa-auth-plugin 定义SPI接口的工程
-- sa-auth-plugin-ldap 模拟第三方库的实现工程

1.idea创建名为sa-auth 的pom 项目,pom 如下

<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.16.RELEASE</version>
</parent>
<groupId>com.vijay</groupId>
<artifactId>cs-auth</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cs-auth</name>
<packaging>pom</packaging>
<description>cs-auth</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
<modules>
<module>cs-auth-plugin</module>
<module>cs-auth-bus</module>
<module>cs-auth-plugin-ldap</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>com.vijay</groupId>
<artifactId>cs-auth-plugin</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

2.然后创建sa-auth-plugin,pom 如下

<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.vijay</groupId>
<artifactId>cs-auth</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>cs-auth-plugin</artifactId>
<name>cs-auth-plugin</name>
<description>cs-auth-plugin</description>
</project>

3.sa-auth-bus,pom 如下

<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.vijay</groupId>
<artifactId>cs-auth</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>cs-auth-bus</artifactId>
<name>cs-auth-bus</name>
<description>cs-auth-bus</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.vijay</groupId>
<artifactId>cs-auth-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.1.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

4.sa-auth-plugin-ldap,pom如下

<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.vijay</groupId>
<artifactId>cs-auth</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>cs-auth-plugin-ldap</artifactId>
<name>cs-auth-plugin</name>
<description>cs-auth-plugin-ldap</description>
<dependencies>
<dependency>
<groupId>com.vijay</groupId>
<artifactId>cs-auth-plugin</artifactId>
</dependency>
</dependencies>
</project>

5.创建好的项目结构如下

6.打开sa-auth-plugin,定义SPI接口

package com.vijay.csauthplugin.service;  

/**
* 插件SPI接口
*
* @author vijay
*/
public interface AuthPluginService {

/**
* 登录认证
*
* @param userName 用户名
* @param password 密码
* @return 认证结果
*/
boolean login(String userName, String password);

/**
* AuthPluginService Name which for conveniently find AuthPluginService instance.
*
* @return AuthServiceName mark a AuthPluginService instance.
*/
String getAuthServiceName();
}

7.cs-auth-plugin-ldap 中实现SPI的接口并且打成jar包,模拟外部提供的插件jar包

1.实现引入的cs-auth-plug包的SPI接口 package com.vijay.csauthplugin.ldap;

package com.vijay.csauthplugin.ldap;
import com.vijay.csauthplugin.service.AuthPluginService;
/**
* @author vijay
*/
public class LdapProviderImpl implements AuthPluginService {
@Override
public boolean login(String userName, String password) {
return "vijay".equals(userName) && "123456".equals(password);
}

@Override
public String getAuthServiceName() {
return "LdapProvider";
}
}

2.resources目录下创建META-INF/services目录,并在目录下创建一个名为SPI接口类的全路径限定名com.vijay.csauthplugin.service.AuthPluginService的文件,文件中写入LdapProviderImpl 实现类的全路径限定名com.vijay.csauthplugin.ldap.LdapProviderImpl

3.cs-auth-plugin-ldap打包成jar包

8.打开cs-auth-plugin-bus

1.项目下创建plugin包,添加一个插件的默认实现DefaultProviderImpl

package com.vijay.bus.plugin;  

import com.vijay.csauthplugin.service.AuthPluginService;

/**
* 默认插件实现
*
* @author vijay
*/
public class DefaultProviderImpl implements AuthPluginService {
@Override
public boolean login(String userName, String password) {
return "vijay".equals(userName) && "123456".equals(password);
}

@Override
public String getAuthServiceName() {
return "DefaultProvider";
}
}

2.resources目录下创建META-INF/services目录并在目录下创建一个名为SPI接口类全路径限定名的文件com.vijay.csauthplugin.service.AuthPluginService,文件内容为DefaultProviderImpl全路径限定名com.vijay.bus.plugin.DefaultProviderImpl

3.自定义类加载器

package com.vijay.bus.plugin;  

import java.net.URL;
import java.net.URLClassLoader;

/**
* 自定义类加载器
*
* @author vijay
*/
public class PluginClassLoader extends URLClassLoader {

public PluginClassLoader(URL[] urls) {
super(urls);
}

/**
* @param url 路径
*/
public void addzURL(URL url) {
super.addURL(url);
}

}

4.定义一个加载外部jar包的类

package com.vijay.bus.plugin;  

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
* 加载指定目录jar包
* @author vijay
*/
public class ExternalJarLoader {

/**
* 加载外部jia包
*
* @param externalDirPath jar包目录
*/
public static void loadExternalJars(String externalDirPath) {
File dir = new File(externalDirPath);
if (!dir.exists() || !dir.isDirectory()) {
throw new IllegalArgumentException("Invalid directory path");
}
List<URL> urls = new ArrayList<>();
File[] listFiles = dir.listFiles();
if (Objects.nonNull(listFiles) && listFiles.length > 0) {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
try {
for (File file : listFiles) {
if (file.getName().endsWith(".jar")) {
urls.add(file.toURI().toURL());
}
}
PluginClassLoader customClassLoader = new PluginClassLoader(urls.toArray(new URL[0]));
Thread.currentThread().setContextClassLoader(customClassLoader);
} catch (Exception e) {
e.printStackTrace();
Thread.currentThread().setContextClassLoader(contextClassLoader);
}
}
}
}

5.启动类中添加类加载器

package com.vijay.bus;  

import com.vijay.bus.plugin.ExternalJarLoader;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* @author vijay
*/
@SpringBootApplication
public class CsAuthBusApplication {
public static void main(String[] args) {
String jarPath="/Users/vijay/Downloads/build/plugin";
ExternalJarLoader.loadExternalJars(jarPath);
SpringApplication.run(CsAuthBusApplication.class, args);
}
}

6.创建插件提供者类PluginProvider,提供实现类供springboot注入

package com.vijay.bus.plugin;  

import com.vijay.csauthplugin.service.AuthPluginService;

import java.util.ServiceLoader;

/**
* 插件提供者
*
* @author vijay
*/
public class PluginProvider {

/**
* 提供一个插件供注入(默认返回外部目录的插件,外部目录没有插件时返回默认插件)
*
* @return 具体的插件实现
*/
public static AuthPluginService getAuthPluginService() {
ServiceLoader<AuthPluginService> defaultLoad = ServiceLoader.load(AuthPluginService.class);
AuthPluginService plugin = null;
for (AuthPluginService authPluginService : defaultLoad) {
if (authPluginService instanceof DefaultProviderImpl) {
plugin = authPluginService;
} else {
return authPluginService;
}
}
return plugin;
}
}

7.项目下创建conf包,注入实现类到springboot

package com.vijay.bus.conf;  

import com.vijay.bus.plugin.PluginProvider;
import com.vijay.csauthplugin.service.AuthPluginService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


/**
* @author vijay
*/
@Configuration
public class PluginConfig {


@Bean
public AuthPluginService authPluginService() {
return PluginProvider.getAuthPluginService();
}
}

8.项目下创建controller包,定义controller接口,调用测试

package com.vijay.bus.controller;  

import com.vijay.csauthplugin.service.AuthPluginService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

/**
* @author vijay
*/
@RestController
public class TestController {
@Resource
private AuthPluginService authPluginService;
@GetMapping("test")
public Object test() {
return new HashMap() {{
put("name", authPluginService.getAuthServiceName());
put("login", authPluginService.login("vijay", "123456"));
}};
}
}

完整结构

9.请求接口,测试实现

此时返回为默认实现,把cs-auth-plugin-ldap项目模拟的第三方包放到外部jar包加载目录,重新启动项目后发起请求

实现已经是模拟的jar的实现

文章目录
  1. 1. 什么是Java的SPI
  2. 2. SPI和API的区别
  3. 3. 实现过程