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()。
相关推荐
资生算法程序员_畅想家_剑魔2 小时前
Java常见技术分享-16-多线程安全-并发编程的核心问题
java·开发语言
Macbethad2 小时前
区块链技术在分布式系统中的应用实践技术报告
开发语言·c#
海南java第二人2 小时前
Java无锁并发编程:volatile+CAS原子类深度解析
java·cas·volatile
毕设源码-邱学长2 小时前
【开题答辩全过程】以 人才培养方案调查系统为例,包含答辩的问题和答案
java·eclipse
零雲2 小时前
Java面试:@Component和@Bean的区别是什么
java·开发语言·面试
Jerry404_NotFound3 小时前
工厂方法模式
java·开发语言·jvm·工厂方法模式
一起养小猫3 小时前
【探索实战】Kurator统一流量治理深度实践:基于Istio的跨集群服务网格
java·云原生·istio
微风欲寻竹影3 小时前
深入理解Java中的String
java·开发语言
Coder_Boy_3 小时前
基于SpringAI的智能平台基座开发-(二)
java·人工智能·springboot·aiops·langchain4j