1 简单示例
1 2 3 public interface DataBaseDriver { void connect () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MySQLDriver implements DataBaseDriver { @Override public void connect () { System.out.println("MySQL connecting..." ); } } public class PostgreSQLDriver implements DataBaseDriver { @Override public void connect () { System.out.println("PostgreSQL connecting..." ); } }
$\text {SPI}$ 配置文件:META-INF/services/com.solisamicus.DataBaseDriver
1 2 com.solisamicus.MySQLDriver com.solisamicus.PostgreSQLDriver
1 2 3 4 ServiceLoader<DataBaseDriver> loader = ServiceLoader.load(DataBaseDriver.class); for (DataBaseDriver driver : loader) { driver.connect(); }
MySQL connecting… PostgreSQL connecting…
2 JDBC 驱动 $\text {java.sql.Driver}$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <dependencies > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.33</version > </dependency > <dependency > <groupId > org.postgresql</groupId > <artifactId > postgresql</artifactId > <version > 42.7.5</version > </dependency > </dependencies >
当使用下面代码时:
1 Connection conn = DriverManager.getConnection(url,username,password);
$\text {java.sql.DriverManager}$:
1 2 3 4 5 6 7 8 static { loadInitialDrivers(); println("JDBC DriverManager initialized" ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 private static void loadInitialDrivers () { String drivers; try { drivers = AccessController.doPrivileged(new PrivilegedAction <Str public String run () { return System.getProperty("jdbc.drivers" ); } }); } catch (Exception ex) { drivers = null ; } AccessController.doPrivileged(new PrivilegedAction <Void>() { public Void run () { ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Dri Iterator<Driver> driversIterator = loadedDrivers.iterator(); try { while (driversIterator.hasNext()) { driversIterator.next(); } } catch (Throwable t) { } return null ; } }); println("DriverManager.initialize: jdbc.drivers = " + drivers); if (drivers == null || drivers.equals("" )) { return ; } String[] driversList = drivers.split(":" ); println("number of Drivers:" + driversList.length); for (String aDriver : driversList) { try { println("DriverManager.Initialize: loading " + aDriver); Class.forName(aDriver, true , ClassLoader.getSystemClassLoader()); } catch (Exception ex) { println("DriverManager.Initialize: load failed: " + ex); } } }
3 日志框架 $\text {Common-Logging}$
提供了四层查找具体日志实现的方法(聚焦分析 SPI 的实现机制。):
系统属性查找
1 2 3 4 String factoryClass = getSystemProperty(FACTORY_PROPERTY, null );if (factoryClass != null ) { factory = newFactory(factoryClass, baseClassLoader, contextClassLoader); }
Java SPI 方式
1 2 3 4 5 6 7 8 9 10 11 12 13 if (factory == null ) { final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID); if (is != null ) { BufferedReader rd = new BufferedReader (new InputStreamReader (is, "UTF-8" )); String factoryClassName = rd.readLine(); rd.close(); if (factoryClassName != null && !"" .equals(factoryClassName)) { factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader); } } }
配置文件方式
1 2 3 4 5 6 7 8 if (factory == null ) { if (props != null ) { String factoryClass = props.getProperty(FACTORY_PROPERTY); if (factoryClass != null ) { factory = newFactory(factoryClass, baseClassLoader, contextClassLoader); } } }
默认实现
1 2 3 if (factory == null ) { factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader); }
4 Spring Framework $\text {org.springframework.core.io.support.SpringFactoriesLoader}$:该类负责从 spring.factories
文件加载并实例化工厂,相关文件可能存在于多个 JAR 文件的类路径中。spring.factories
文件是一个属性格式的配置文件,里面包含了接口或抽象类到具体实现类的映射关系,从而支持框架的扩展性和插件机制。
:star:实现了一个简单的支付服务 SPI 系 统,同时支持 Java-SPI 和 Spring-SPI 的加载方式。
4.1 工程结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 payment/ ├── pom.xml ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── solisamicus/ │ │ │ └── payment/ │ │ │ ├── api/ │ │ │ │ └── PaymentService.java │ │ │ ├── impl/ │ │ │ │ ├── AlipayService.java │ │ │ │ └── WechatPayService.java │ │ │ ├── config/ │ │ │ │ └── PaymentConfig.java │ │ │ ├── loader/ │ │ │ │ └── PaymentLoader.java │ │ │ ├── service/ │ │ │ │ └── PaymentManager.java │ │ │ └── controller/ │ │ │ └── PaymentController.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ ├── services/ │ │ │ │ └── com.solisamicus.payment.api.PaymentService │ │ │ └── spring.factories │ │ └── application.properties │ └── test/ └── README.md
4.2 Maven依赖配置 (pom.xml) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.solisamicus</groupId > <artifactId > payment</artifactId > <version > 1.0-SNAPSHOT</version > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.3.12.RELEASE</version > <relativePath /> </parent > <properties > <maven.compiler.source > 8</maven.compiler.source > <maven.compiler.target > 8</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > <configuration > <excludes > <exclude > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > </exclude > </excludes > </configuration > </plugin > </plugins > </build > </project >
3 核心代码实现 3.1 支付服务接口 PaymentService.java
1 2 3 4 5 6 7 8 package com.solisamicus.payment.api;public interface PaymentService { void pay (Double amount) ; String getType () ; }
3.2 支付服务实现类 3.2.1 支付宝实现 AlipayService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.solisamicus.payment.impl;import com.solisamicus.payment.api.PaymentService;import lombok.extern.slf4j.Slf4j;@Slf4j public class AlipayService implements PaymentService { @Override public void pay (Double amount) { log.info("Pay with Alipay: {}" , amount); } @Override public String getType () { return "alipay" ; } }
3.2.3 微信支付实现 WechatPayService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.solisamicus.payment.impl;import com.solisamicus.payment.api.PaymentService;import lombok.extern.slf4j.Slf4j;@Slf4j public class WechatPayService implements PaymentService { @Override public void pay (Double amount) { log.info("Pay with WeChat: {}" , amount); } @Override public String getType () { return "wechat" ; } }
3.3 支付加载器 $\text {org.springframework.core.io.support.SpringFactoriesLoader}$
PaymentLoader.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package com.solisamicus.payment.loader;import com.solisamicus.payment.api.PaymentService;import lombok.extern.slf4j.Slf4j;import org.springframework.core.io.support.SpringFactoriesLoader;import org.springframework.stereotype.Component;import java.util.ArrayList;import java.util.List;import java.util.ServiceLoader;@Slf4j @Component public class PaymentLoader { public List<PaymentService> loadJavaSPI () { List<PaymentService> services = new ArrayList <>(); ServiceLoader<PaymentService> loader = ServiceLoader.load(PaymentService.class); for (PaymentService service : loader) { services.add(service); log.info("Loaded payment service: {}" , service.getClass().getName()); } return services; } public List<PaymentService> loadSpring (ClassLoader classLoader) { List<PaymentService> services = new ArrayList <>(); List<String> names = SpringFactoriesLoader.loadFactoryNames(PaymentService.class, classLoader); for (String name : names) { try { Class<?> clazz = Class.forName(name); PaymentService service = (PaymentService) clazz.newInstance(); services.add(service); log.info("Loaded payment service: {}" , name); } catch (Exception e) { log.error("Failed to load payment service: " + name, e); } } return services; } }
3.4 支付管理服务 PaymentManager.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package com.solisamicus.payment.service;import com.solisamicus.payment.api.PaymentService;import com.solisamicus.payment.loader.PaymentLoader;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import javax.annotation.PostConstruct;import java.util.List;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;@Slf4j @Service public class PaymentManager { @Autowired private PaymentLoader paymentLoader; private final Map<String, PaymentService> serviceMap = new ConcurrentHashMap <>(); @PostConstruct public void init () { List<PaymentService> services = paymentLoader.loadJavaSPI(); for (PaymentService service : services) { serviceMap.put(service.getType(), service); log.info("Registered payment service: {}" , service.getType()); } } public void processPayment (Double amount, String type) { PaymentService service = serviceMap.get(type.toLowerCase()); if (service == null ) { throw new IllegalArgumentException ("Unsupported payment type: " + type); } service.pay(amount); } }
3.5 控制器 PaymentController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.solisamicus.payment.controller;import com.solisamicus.payment.service.PaymentManager;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.*;@Slf4j @RestController @RequestMapping("/payment") public class PaymentController { @Autowired private PaymentManager paymentManager; @PostMapping("/pay") public ResponseEntity<?> pay(@RequestParam Double amount, @RequestParam String type) { try { paymentManager.processPayment(amount, type); return ResponseEntity.ok("Payment processed successfully" ); } catch (Exception e) { log.error("Payment failed" , e); return ResponseEntity.badRequest().body("Payment failed: " + e.getMessage()); } } }
4 配置文件 4.1 Java SPI配置 META-INF/services/com.solisamicus.payment.api.PaymentService
1 2 com.solisamicus.payment.impl.AlipayService com.solisamicus.payment.impl.WechatPayService
4.2 Spring factories配置 META-INF/spring.factories
1 2 3 com.solisamicus.payment.api.PaymentService =\ com.solisamicus.payment.impl.AlipayService,\ com.solisamicus.payment.impl.WechatPayService
4.3 应用配置 application.properties
1 2 server.port =8080 logging.level.com.solisamicus.payment =DEBUG
5 启动类 PaymentApplication.java
1 2 3 4 5 6 7 8 9 10 11 package com.solisamicus.payment; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class PaymentApplication { public static void main(String[] args) { SpringApplication.run(PaymentApplication.class, args); } }
6 测试类 PaymentServiceTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package com.solisamicus.payment;import com.solisamicus.payment.loader.PaymentLoader;import com.solisamicus.payment.api.PaymentService;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.List;import static org.junit.jupiter.api.Assertions.assertFalse;@SpringBootTest class PaymentServiceTest { @Autowired private PaymentLoader paymentLoader; @Test void testLoadJavaSPI () { List<PaymentService> services = paymentLoader.loadJavaSPI(); assertFalse(services.isEmpty()); } @Test void testLoadSpring () { List<PaymentService> services = paymentLoader.loadSpring(getClass().getClassLoader()); assertFalse(services.isEmpty()); } }
测试结果(忽略 Spring Test 输出):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 2025-01-20 22:57:24.470 INFO 39472 --- [ main] c.s.payment.loader.PaymentLoader : Loaded payment service: com.solisamicus.payment.impl.AlipayService 2025-01-20 22:57:24.471 INFO 39472 --- [ main] c.s.payment.loader.PaymentLoader : Loaded payment service: com.solisamicus.payment.impl.WechatPayService 2025-01-20 22:57:24.472 INFO 39472 --- [ main] c.s.payment.service.PaymentManager : Registered payment service: alipay 2025-01-20 22:57:24.472 INFO 39472 --- [ main] c.s.payment.service.PaymentManager : Registered payment service: wechat 2025-01-20 22:57:24.770 INFO 39472 --- [ main] ... 2025-01-20 22:57:25.089 INFO 39472 --- [ main] c.s.payment.loader.PaymentLoader : Loaded payment service: com.solisamicus.payment.impl.AlipayService 2025-01-20 22:57:25.089 INFO 39472 --- [ main] c.s.payment.loader.PaymentLoader : Loaded payment service: com.solisamicus.payment.impl.WechatPayService 2025-01-20 22:57:25.100 INFO 39472 --- [ main] c.s.payment.loader.PaymentLoader : Loaded payment service: com.solisamicus.payment.impl.AlipayService 2025-01-20 22:57:25.100 INFO 39472 --- [ main] c.s.payment.loader.PaymentLoader : Loaded payment service: com.solisamicus.payment.impl.WechatPayService ...
7 使用方式
编译并运行项目:
1 2 $ mvn clean package $ java -jar target/payment-1.0-SNAPSHOT.jar
测试 $\text {API}$:
1 2 3 4 5 # 使用支付宝支付 curl -X POST "http://localhost:8080/payment/pay?amount=100&type=alipay" # 使用微信支付 curl -X POST "http://localhost:8080/payment/pay?amount=100&type=wechat"
运行结果:
日志输出:
Spring 的 SpringFactoriesLoader
和 Java-SPI 都是用于动态加载接口实现的机制,有以下几个主要区别:
配置文件位置和格式
1 2 3 org.springframework.cache.CacheManager =\ org.springframework.cache.concurrent.ConcurrentMapCacheManager\ org.springframework.cache.ehcache.EhCacheCacheManager`
功能和用途
Java SPI: 主要用于 Java 标准库和第三方库之间的解耦,让程序能够加载实现类。Java SPI 是 Java 平台的一部分,通常用于 Java SE 项目中,广泛用于服务提供者的发现机制。
Spring SpringFactoriesLoader
: 是 Spring 框架内部的扩展机制,专门用于 Spring 的自动配置和插件化架构。它不仅支持接口的动态加载,还能加载配置类、自动配置类等,因此是 Spring 应用扩展机制的重要组成部分。
加载方式
Java SPI: 使用 ServiceLoader
类来加载服务提供者(实现类)。
Spring SpringFactoriesLoader
: 在 Spring 中通过 ClassLoader
加载 spring.factories
文件,并通过反射动态实例化实现类。