Java SPI 机制

目录

1.SPI的核心思想

[2.SPI 的实现步骤](#2.SPI 的实现步骤)

[3.案例:验证码生成器的 SPI 实现](#3.案例:验证码生成器的 SPI 实现)

[3.1 定义服务接口(ICode)](#3.1 定义服务接口(ICode))

[3.2 编写服务实现类](#3.2 编写服务实现类)

中文验证码实现(ChineseCodeImpl)

数字验证码实现(NumberCodeImpl)

[3.3 配置 SPI 文件](#3.3 配置 SPI 文件)

[3.4 使用ServiceLoader加载服务](#3.4 使用ServiceLoader加载服务)

[3.5 测试 SPI](#3.5 测试 SPI)

[3.6 运行结果:(在运行期间更改了SPI 配置文件的注释)](#3.6 运行结果:(在运行期间更改了SPI 配置文件的注释))

[3.7 核心代码解析](#3.7 核心代码解析)


SPI(Service Provider Interface) 是 Java 提供的一种 "服务发现" 机制,它允许我们在不修改代码的情况下,动态加载接口的实现类。本文将通过一个验证码生成的案例来体现 SPI 的原理与实践。

1.SPI的核心思想

SPI 是一种**服务发现机制,**让接口与实现完全解耦,核心思想是:(接口 + 实现 + 配置文件)

  • 定义一个接口(服务接口)。
  • 由第三方(服务提供者)实现该接口。
  • 程序运行时,通过ServiceLoader自动发现并加载所有实现类。

2.SPI 的实现步骤

  1. 定义服务接口:声明通用的服务规范。

  2. 编写服务实现:第三方实现服务接口。

  3. 配置 SPI 文件 :在META-INF/services/下创建以接口为名的文件,内容为实现类名字。

  4. 使用ServiceLoader加载服务:运行时自动发现并加载所有实现类。

3.案例:验证码生成器的 SPI 实现

项目结构:

3.1 定义服务接口(ICode

java 复制代码
package com.interfacess;

/**
 * 验证码生成器接口(服务接口)
 */
public interface ICode {
	
	public  String  makeCode();

}

3.2 编写服务实现类

创建两个实现类:ChineseCodeImpl(生成中文验证码)和NumberCodeImpl(生成数字验证码)。

中文验证码实现(ChineseCodeImpl

java 复制代码
package com.interfaces.impl;

import java.util.Random;
import com.interfacess.ICode;

/**
 * 中文验证码实现类(服务提供者)
 */
public class ChineseCodeImpl implements ICode {
	
    private String[] chars = {"赵", "钱", "孙", "李", "王", "五", "马", "六", "天", "地"};

    @Override
    public String makeCode() {
    	
        StringBuilder code = new StringBuilder();
        Random random = new Random();
        
        // 生成4位不重复的中文验证码
        for (int i = 0; i < 4; ) {
        	
            String ch = chars[random.nextInt(chars.length)];
            
            if (!code.toString().contains(ch)) {
                code.append(ch);
                i++; // 只有不重复时,才推进循环
            }
        }
        return code.toString();
    }
}

数字验证码实现(NumberCodeImpl

java 复制代码
package com.interfaces.impl;

import java.util.Random;
import com.interfacess.ICode;

/**
 * 数字验证码实现类(服务提供者)
 */
public class NumberCodeImpl implements ICode {
    private String[] nums = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"};

    @Override
    public String makeCode() {
    	
        StringBuilder code = new StringBuilder();
        
        Random random = new Random();
        
        for (int i = 0; i < 4; ) {
        	
            String num = nums[random.nextInt(nums.length)];
            
            if (!code.toString().contains(num)) {
                code.append(num);
                i++; 
            }
        }
        return code.toString();
    }
}

3.3 配置 SPI 文件

resources/META-INF/services/目录下,创建名为com.interfacess.ICode的文件(文件名必须与接口全限定名一致),内容为实现类的全限定名:

  • 文件路径必须是META-INF/services/接口全限定名
  • 每行一个实现类的全限定名,#开头的是注释。

3.4 使用ServiceLoader加载服务

创建工厂类CodeServiceFactory,通过ServiceLoader加载所有ICode实现类:

java 复制代码
package com.service;

import java.util.Iterator;
import java.util.ServiceLoader;
import com.interfacess.ICode;

/**
 * 验证码服务工厂:通过SPI加载实现类
 */
public class CodeServiceFactory {

	public static  String  createCode(Class  targetClass)
	{
		// 服务发现,是通过一个接口的策略文件来动态加载的,使用ServiceLoader加载所有ICode实现类
		ServiceLoader  s = ServiceLoader.load(targetClass);
		
		// 遍历所有实现类
		Iterator  its = s.iterator();
		
		ICode code  = null;
		
		while(its.hasNext())
		{
			// 把接口策略文件里的类的路径转换成ICode对象,获取实现类实例
			code =(ICode)its.next();
		}
		
		String  checkCode  = code.makeCode();   // 生成验证码
		
		return  checkCode;
	}
}

3.5 测试 SPI

java 复制代码
package com.javaspi;

import com.interfacess.ICode;
import com.service.CodeServiceFactory;

public class Test
{
    public static void main( String[] args )
    {
      
    	 while(true)
    	 {
    		 // 通过SPI加载ICode实现类,生成验证码
    		String checkCode =  CodeServiceFactory.createCode(ICode.class);
    		
    		System.out.println("获取的验证码为:"+checkCode);
    		
    		try {
				Thread.sleep(5000);		// 每5秒生成一次
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
    	 }
    }
}

3.6 运行结果:(在运行期间更改了SPI 配置文件的注释)

3.7 核心代码解析

java 复制代码
ServiceLoader  s = ServiceLoader.load(targetClass);
  • 作用:根据接口targetClass,自动扫描META-INF/services/下的配置文件,加载所有实现类。
  • 返回值:ServiceLoader对象,可通过iterator()遍历所有实现类。

解耦的关键

  • 接口ICode与实现类ChineseCodeImpl/NumberCodeImpl完全分离。
  • 切换实现类只需修改 SPI 配置文件,无需修改代码。

SPI 配置文件的 "魔法"

  • ServiceLoader会自动查找META-INF/services/下与接口全限定名一致的文件。
  • 文件内容中的每个实现类都会被反射实例化,无需硬编码new XxxImpl()。
相关推荐
皮皮林5511 小时前
Java性能调优黑科技!1行代码实现毫秒级耗时追踪,效率飙升300%!
java
冰_河2 小时前
QPS从300到3100:我靠一行代码让接口性能暴涨10倍,系统性能原地起飞!!
java·后端·性能优化
桦说编程4 小时前
从 ForkJoinPool 的 Compensate 看并发框架的线程补偿思想
java·后端·源码阅读
躺平大鹅6 小时前
Java面向对象入门(类与对象,新手秒懂)
java
初次攀爬者7 小时前
RocketMQ在Spring Boot上的基础使用
java·spring boot·rocketmq
花花无缺7 小时前
搞懂@Autowired 与@Resuorce
java·spring boot·后端
Derek_Smart8 小时前
从一次 OOM 事故说起:打造生产级的 JVM 健康检查组件
java·jvm·spring boot
NE_STOP9 小时前
MyBatis-mybatis入门与增删改查
java
孟陬13 小时前
国外技术周刊 #1:Paul Graham 重新分享最受欢迎的文章《创作者的品味》、本周被划线最多 YouTube《如何在 19 分钟内学会 AI》、为何我不
java·前端·后端