Java SPI 机制与底层库编写
Java SPI 机制
SPI 概念
SPI(Service Provider Interface)是 Java 内置的一种服务发现机制,核心思想是面向接口编程与策略模式的结合。API(Application Programming Interface)是供调用者使用的接口,而 SPI 是供服务提供者实现的接口。二者分离使得框架可以在运行时动态加载具体的实现类,而无需在代码中硬编码。
+-------------------+ +--------------------+
| 调用方代码 | 使用 | API 接口定义 |
| (Consumer) |--------->| (Service API) |
+-------------------+ +--------------------+
^
| 实现
+--------+--------+
| |
+---------+----+ +--------+---------+
| Provider A | | Provider B |
| (Impl by 甲) | | (Impl by 乙) |
+--------------+ +------------------+SPI 的工作方式可以概括为:接口定义由服务提供方提供,实现类由第三方提供,JDK 通过 ServiceLoader 统一加载。
ServiceLoader 使用流程
使用 Java SPI 需要遵循四个步骤:
第一步:定义服务接口
// 支付服务接口
package com.example.spi;
public interface PaymentService {
boolean pay(String orderId, BigDecimal amount);
String getChannel();
}第二步:编写实现类
package com.example.alipay;
import com.example.spi.PaymentService;
import java.math.BigDecimal;
public class AlipayService implements PaymentService {
@Override
public boolean pay(String orderId, BigDecimal amount) {
System.out.println("支付宝支付: order=" + orderId + ", amount=" + amount);
return true;
}
@Override
public String getChannel() {
return "alipay";
}
}package com.example.wechat;
import com.example.spi.PaymentService;
import java.math.BigDecimal;
public class WechatPayService implements PaymentService {
@Override
public boolean pay(String orderId, BigDecimal amount) {
System.out.println("微信支付: order=" + orderId + ", amount=" + amount);
return true;
}
@Override
public String getChannel() {
return "wechat";
}
}第三步:配置 META-INF/services/ 文件
在 resources/META-INF/services/ 目录下创建以接口全限定名命名的文件:
META-INF/services/com.example.spi.PaymentService文件内容为实现类的全限定名,每行一个:
com.example.alipay.AlipayService
com.example.wechat.WechatPayService第四步:加载使用
import com.example.spi.PaymentService;
import java.util.ServiceLoader;
public class PaymentClient {
public static void main(String[] args) {
// 加载所有 SPI 实现
ServiceLoader<PaymentService> loader = ServiceLoader.load(PaymentService.class);
// 遍历使用
for (PaymentService service : loader) {
System.out.println("发现支付渠道: " + service.getChannel());
service.pay("ORDER001", new BigDecimal("99.99"));
}
}
}ServiceLoader 源码分析
ServiceLoader 是 JDK 提供的 SPI 加载核心类(位于 java.util 包),其实现采用延迟加载和缓存机制。
核心成员变量
public final class ServiceLoader<S> implements Iterable<S> {
// 服务接口 Class
private final Class<S> service;
// 类加载器
private final ClassLoader loader;
// 访问控制上下文
private final AccessControlContext acc;
// 已缓存的实现(按加载顺序)
private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
// 延迟加载迭代器
private LazyIterator lookupIterator;
}LazyIterator 延迟加载
ServiceLoader 不会在构造时加载所有实现,而是通过内部类 LazyIterator 实现按需加载:
private class LazyIterator implements Iterator<S> {
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs;
Iterator<String> pending;
String nextName;
private boolean hasNextService() {
if (configs == null) {
// 查找 META-INF/services/ 下的配置文件
String fullName = "META-INF/services/" + service.getName();
configs = loader.getResources(fullName);
}
while (!pending.hasNext()) {
// 解析配置文件中的实现类名
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
private S nextService() {
// 反射加载并实例化
Class<?> c = Class.forName(nextName, false, loader);
S result = service.cast(c.newInstance());
// 放入缓存
providers.put(nextName, result);
return result;
}
}关键设计要点
- 延迟加载:
hasNextService()和nextService()在迭代时才真正读取配置文件和加载类。 - 缓存机制:已加载的实现放入
providers缓存(LinkedHashMap),保证插入顺序和可复用的迭代结果。 - 类加载器:默认使用当前线程的上下文类加载器(
Thread.currentThread().getContextClassLoader()),避免类加载双亲委派机制导致 SPI 实现类不可见的问题。 reload()方法:清空缓存并重新创建LazyIterator,适用于动态刷新场景。
SPI 典型应用
JDBC Driver
JDBC 4.0 开始使用 SPI 自动加载数据库驱动:
// DriverManager 初始化时通过 SPI 加载所有驱动
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
// META-INF/services/java.sql.Driver 文件中包含:
// com.mysql.cj.jdbc.Driver
// org.postgresql.Driver// 传统方式(JDBC 4.0 之前)
Class.forName("com.mysql.cj.jdbc.Driver");
// SPI 方式(JDBC 4.0 之后)
// 无需 Class.forName,DriverManager 自动发现已注册的驱动
Connection conn = DriverManager.getConnection(url, user, password);日志门面 SLF4J
SLF4J 通过 SPI 在运行时绑定具体的日志实现:
// SLF4J 使用 ServiceLoader 加载 ILoggerFactory 实现
// META-INF/services/org.slf4j.spi.SLF4JServiceProvider
// logback-classic 提供: ch.qos.logback.classic.spi.LogbackServiceProvider
// log4j-slf4j-impl 提供: org.apache.logging.slf4j.SLF4JServiceProvider
public class LoggerFactory {
static ILoggerFactory getILoggerFactory() {
// 通过 SPI 加载,实现运行时绑定
ServiceLoader<SLF4JServiceProvider> providers =
ServiceLoader.load(SLF4JServiceProvider.class);
// ...
}
}Bean Validation
Hibernate Validator 等实现通过 SPI 暴露 ValidationProvider:
// META-INF/services/javax.validation.spi.ValidationProvider
// org.hibernate.validator.HibernateValidator
ServiceLoader<ValidationProvider> loader =
ServiceLoader.load(ValidationProvider.class);SPI 缺点
无法按需加载
ServiceLoader 只支持全量加载,不支持根据名称或条件获取特定实现。一旦遍历,所有配置的实现类都会被实例化,即使某些实现实际上并不需要。
// 无法做到只加载 AlipayService
ServiceLoader<PaymentService> loader = ServiceLoader.load(PaymentService.class);
for (PaymentService s : loader) {
// 所有实现都被遍历,无法跳过
}异常吞没
ServiceLoader 在加载实现时,如果某个类加载或实例化失败,会抛出 ServiceConfigurationError 并直接终止整个加载过程,而不是跳过失败的实现继续加载剩余部分。调试时难以定位具体是哪个实现类出了问题。
并发问题
ServiceLoader 的 reload() 方法与迭代操作存在并发安全问题。如果在迭代过程中其他线程调用了 reload(),可能导致 ConcurrentModificationException 或加载结果不一致。同时 LazyIterator 内部状态不是线程安全的。
缺少扩展机制
JDK SPI 不支持:
- 为接口指定默认实现
- 为扩展点添加参数注入
- 实现类之间的依赖关系
- 根据运行时参数动态选择实现
这些局限性催生了 Dubbo SPI 等自定义 SPI 框架的出现。
自定义 SPI 框架
为了弥补 JDK SPI 的不足,Dubbo 设计了一套功能完善的 SPI 扩展机制。下面从注解设计、加载器实现、自适应扩展、自动激活四个方面剖析其设计思路。
SPI 注解设计
@SPI 注解
@SPI 标注在接口上,表示该接口是一个可扩展点,可以指定默认实现名称:
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SPI {
/**
* 默认扩展实现名称
* 例如 @SPI("dubbo") 表示默认使用名为 "dubbo" 的实现
*/
String value() default "";
}使用示例:
@SPI("console")
public interface Logger {
void log(String message);
}@Adaptive 注解
@Adaptive 标注在实现类或接口方法上,表示该实现是自适应扩展点:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Adaptive {
/**
* 从 URL 参数中提取的 key
* 例如 @Adaptive("protocol") 会从 URL 中获取 protocol 参数值
*/
String[] value() default {};
}@Activate 注解
@Activate 标注在实现类上,表示该扩展点按条件自动激活:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Activate {
/**
* 分组过滤,例如 "provider" 或 "consumer"
*/
String[] group() default {};
/**
* 排序值,值越小越优先
*/
int order() default 0;
/**
* URL 参数条件,仅当 URL 包含指定参数时才激活
*/
String[] value() default {};
}扩展点加载器实现
扩展点加载器是 SPI 框架的核心,需要实现缓存、懒加载、默认实现等能力:
public class ExtensionLoader<T> {
// 扩展点接口
private final Class<T> type;
// 扩展点加载器缓存(Class -> ExtensionLoader)
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS
= new ConcurrentHashMap<>();
// 扩展实现实例缓存(Class -> name -> instance)
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES
= new ConcurrentHashMap<>();
// 当前加载器管理的已加载实现缓存
private final ConcurrentMap<String, Holder<Object>> cachedInstances
= new ConcurrentHashMap<>();
// 实现类名与实例映射(已实例化)
private final ConcurrentMap<String, T> extensionInstances
= new ConcurrentHashMap<>();
// 默认实现名称(从 @SPI 注解读取)
private String defaultName;
// 配置文件中解析到的所有实现类名
private Map<String, Class<?>> extensionClasses;
// 获取 ExtensionLoader(静态入口)
@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type must be interface");
}
if (!type.isAnnotationPresent(SPI.class)) {
throw new IllegalArgumentException("Extension type must be annotated by @SPI");
}
// 从缓存获取,避免重复创建
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
private ExtensionLoader(Class<T> type) {
this.type = type;
// 读取 @SPI 注解中的默认实现名称
SPI spi = type.getAnnotation(SPI.class);
if (spi != null && !spi.value().isEmpty()) {
this.defaultName = spi.value();
}
}
// 获取默认实现(懒加载)
public T getDefaultExtension() {
if (defaultName == null || defaultName.isEmpty()) {
return null;
}
return getExtension(defaultName);
}
// 根据名称获取实现(懒加载 + 缓存)
public T getExtension(String name) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("Extension name == null");
}
// 先检查缓存
T instance = extensionInstances.get(name);
if (instance == null) {
// 双重检查锁
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<>());
holder = cachedInstances.get(name);
}
Object inst = holder.get();
if (inst == null) {
synchronized (holder) {
inst = holder.get();
if (inst == null) {
inst = createExtension(name);
holder.set(inst);
}
}
}
instance = (T) inst;
}
return instance;
}
// 获取所有已注册的实现
public List<T> getActivateExtensions(URL url, String... groups) {
// 详见 @Activate 实现
return Collections.emptyList();
}
// 创建扩展实例(反射 + 注入)
@SuppressWarnings("unchecked")
private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw new IllegalStateException("No such extension: " + name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 此处可扩展依赖注入逻辑
return instance;
} catch (Exception e) {
throw new IllegalStateException("Extension create error: " + name, e);
}
}
// 获取配置文件中的所有实现类
private Map<String, Class<?>> getExtensionClasses() {
if (extensionClasses == null) {
extensionClasses = loadExtensionClasses();
}
return extensionClasses;
}
// 从配置目录加载实现类信息
private Map<String, Class<?>> loadExtensionClasses() {
// 扫描目录:
// META-INF/services/
// META-INF/dubbo/
// META-INF/dubbo/internal/
// 配置文件格式: name=全限定类名
// 示例: dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
return new HashMap<>();
}
}配置文件示例(与 JDK SPI 不同,Dubbo SPI 使用 key=value 格式):
# META-INF/dubbo/com.example.spi.Logger
console=com.example.impl.ConsoleLogger
file=com.example.impl.FileLogger
kafka=com.example.impl.KafkaLogger自适应扩展
@Adaptive 机制允许在运行时根据参数动态决定使用的扩展实现:
@SPI("thrift")
public interface Protocol {
@Adaptive("protocol")
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
@Adaptive("protocol")
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
}自定义 SPI 框架在加载 @Adaptive 接口时,会动态生成一个代理类。核心逻辑如下:
public class AdaptiveExtensionLoader {
/**
* 获取自适应扩展实例
* 如果接口有 @Adaptive 标注的实现类,直接返回该类实例
* 否则动态生成代理代码并编译
*/
public static <T> T getAdaptiveExtension(Class<T> type) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
// 检查是否有 @Adaptive 标注的实现类
for (Map.Entry<String, Class<?>> entry : loader.getExtensionClasses().entrySet()) {
Class<?> clazz = entry.getValue();
if (clazz.isAnnotationPresent(Adaptive.class)) {
try {
return (T) clazz.newInstance();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
// 否则动态生成自适应代理类
// 代理逻辑大致如下:
String code = generateAdaptiveProxyCode(type);
// 编译并加载
Class<?> proxyClass = compileAndLoad(type, code);
try {
return (T) proxyClass.newInstance();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
// 模拟生成代理类代码
private static String generateAdaptiveProxyCode(Class<?> type) {
// 生成的代理类大致结构如下:
return """
package com.example.spi.adaptive;
import com.example.spi.ExtensionLoader;
public class Protocol$Adaptive implements Protocol {
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// 从 URL 获取 "protocol" 参数值
String extName = url.getParameter("protocol");
if (extName == null || extName.isEmpty()) {
extName = "dubbo"; // @SPI 默认值
}
// 根据名称获取具体实现并委托
Protocol extension = ExtensionLoader.getExtensionLoader(Protocol.class)
.getExtension(extName);
return extension.export(invoker);
}
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
String extName = url.getParameter("protocol");
if (extName == null || extName.isEmpty()) {
extName = "dubbo";
}
Protocol extension = ExtensionLoader.getExtensionLoader(Protocol.class)
.getExtension(extName);
return extension.refer(type, url);
}
}
""";
}
}自适应扩展的关键价值在于:框架代码不需要在编译期确定使用哪个实现,而是完全由运行时参数(URL)决定。这为微服务架构中的协议适配、序列化选择、负载均衡策略等场景提供了极大灵活性。
自动激活扩展
@Activate 机制用于在特定条件下自动激活一组扩展点,典型的应用场景是 Filter 链:
@Activate(group = "provider", order = 100)
public class ExceptionFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) {
try {
return invoker.invoke(invocation);
} catch (RuntimeException e) {
// 统一异常处理
return AsyncRpcResult.newDefaultAsyncResult(null, e, invocation);
}
}
}
@Activate(group = "consumer", order = 200)
public class TimeoutFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) {
long start = System.currentTimeMillis();
Result result = invoker.invoke(invocation);
long elapsed = System.currentTimeMillis() - start;
if (elapsed > 5000) {
System.err.println("Timeout warning: " + elapsed + "ms");
}
return result;
}
}扩展点加载器根据 @Activate 注解的 group、value、order 属性过滤和排序:
public List<T> getActivateExtensions(URL url, String group) {
List<T> activated = new ArrayList<>();
for (Map.Entry<String, Class<?>> entry : extensionClasses.entrySet()) {
Activate activate = entry.getValue().getAnnotation(Activate.class);
if (activate == null) {
continue;
}
// group 过滤
if (group != null && activate.group().length > 0
&& !Arrays.asList(activate.group()).contains(group)) {
continue;
}
// value 条件过滤(URL 参数匹配)
if (activate.value().length > 0) {
boolean matched = false;
for (String key : activate.value()) {
if (url.hasParameter(key)) {
matched = true;
break;
}
}
if (!matched) {
continue;
}
}
T extension = getExtension(entry.getKey());
activated.add(extension);
}
// 按 order 排序(值越小越优先)
activated.sort(Comparator.comparingInt(
ext -> {
Activate act = ext.getClass().getAnnotation(Activate.class);
return act != null ? act.order() : 0;
}
));
return activated;
}Dubbo SPI vs Java SPI 对比表
| 对比维度 | Java SPI | Dubbo SPI |
|---|---|---|
| 配置文件格式 | 每行一个全限定类名 | name=全限定类名 格式,支持别名 |
| 按需加载 | 不支持,遍历即实例化所有 | 支持,getExtension(name) 按名称加载 |
| 默认实现 | 不支持 | 通过 @SPI("defaultName") 指定 |
| 自适应扩展 | 不支持 | 通过 @Adaptive 动态代理实现 |
| 自动激活 | 不支持 | 通过 @Activate 按 group/value/order 过滤排序 |
| IoC/DI | 不支持 | 支持扩展点之间的依赖注入 |
| 异常处理 | 加载失败直接抛错,无容错 | 更完善的异常处理机制 |
| 缓存 | 简单缓存 | 多级缓存(Holder 模式 + 双重检查锁) |
| 性能 | 每次遍历重新解析配置 | 解析一次,缓存 Class 定义 |
| AOP 支持 | 不支持 | 支持 Wrapper 类实现自动 AOP |
底层库设计原则
API SPI 分离原则
底层库设计的核心原则是 API 与 SPI 分离,即对外只暴露接口定义,将实现发现机制隐藏到 SPI 层中。
maven-module/
├── api/ # 只包含接口和模型定义,零依赖或极简依赖
├── spi/ # SPI 配置文件 + 扩展点加载逻辑
├── impl/ # 默认实现,可选依赖
└── bom/ # 依赖管理(Bill of Materials)API 模块只暴露接口,不包含任何实现:
// api 模块:com.example.logging-api
package com.example.logging;
@SPI("jul")
public interface LoggerFactory {
Logger getLogger(String name);
}
public interface Logger {
void info(String message);
void error(String message, Throwable t);
}SPI 模块提供服务发现能力:
// spi 模块:com.example.logging-spi
package com.example.logging.spi;
import com.example.logging.Logger;
import com.example.logging.LoggerFactory;
import java.util.ServiceLoader;
public class LoggerLoader {
private static volatile LoggerFactory factory;
public static Logger getLogger(String name) {
if (factory == null) {
synchronized (LoggerLoader.class) {
if (factory == null) {
// 通过 SPI 发现实现
ServiceLoader<LoggerFactory> loader =
ServiceLoader.load(LoggerFactory.class);
for (LoggerFactory f : loader) {
factory = f;
break;
}
if (factory == null) {
// 兜底:使用 JDK JUL
factory = new JULLoggerFactory();
}
}
}
}
return factory.getLogger(name);
}
}Impl 模块提供具体实现,只被 SPI 发现,不直接暴露给 API 使用者:
// impl 模块:com.example.logging-impl
package com.example.logging.impl;
import com.example.logging.Logger;
import com.example.logging.LoggerFactory;
public class LogbackLoggerFactory implements LoggerFactory {
@Override
public Logger getLogger(String name) {
return new LogbackLogger(ch.qos.logback.classic.LoggerFactory
.getLogger(name));
}
}# META-INF/services/com.example.logging.LoggerFactory
com.example.logging.impl.LogbackLoggerFactory模块化
底层库的模块拆分应遵循以下原则:
api/spi/impl 三层结构
<!-- 父 POM -->
<groupId>com.example</groupId>
<artifactId>logging-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>logging-api</module>
<module>logging-spi</module>
<module>logging-impl-logback</module>
<module>logging-impl-log4j2</module>
<module>logging-bom</module>
</modules>API 模块依赖最小化
<!-- logging-api -->
<artifactId>logging-api</artifactId>
<dependencies>
<!-- API 模块绝不能依赖具体实现 -->
<!-- 只允许依赖 JDK 和极少数基础注解库 -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
<scope>provided</scope>
</dependency>
</dependencies>SPI 模块桥接层
<!-- logging-spi -->
<artifactId>logging-spi</artifactId>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>logging-api</artifactId>
<version>1.0.0</version>
</dependency>
<!-- 不直接依赖任何实现,由最终用户自行选择 -->
</dependencies>Impl 模块按需引入
<!-- logging-impl-logback -->
<artifactId>logging-impl-logback</artifactId>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>logging-api</artifactId>
<version>1.0.0</version>
<scope>provided</scope>
</dependency>
<!-- 桥接 SPI 模块也设为 provided -->
<dependency>
<groupId>com.example</groupId>
<artifactId>logging-spi</artifactId>
<version>1.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.14</version>
<scope>compile</scope>
</dependency>
</dependencies>依赖最小化
底层库必须严格控制依赖传递,避免污染使用方的依赖树。
compile scope:只放必须的依赖,API 模块应尽量做到零 compile 依赖。
provided scope:编译时需要但运行时由容器或 JDK 提供的依赖:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
<scope>provided</scope>
</dependency>optional scope:可选功能需要的依赖,使用者按需引入:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
<optional>true</optional>
</dependency>依赖分析工具使用建议:
# Maven 依赖树分析
mvn dependency:tree -Dverbose
# 查看未使用且未声明的依赖
mvn dependency:analyze向后兼容
语义化版本
采用语义化版本(Semantic Versioning)规范:
主版本.次版本.修订号
↑ ↑ ↑
| | +-- 修订号:向下兼容的问题修复
| +---------- 次版本:向下兼容的功能新增
+----------------- 主版本:不兼容的 API 变更版本号与 API 变更的对应关系:
| 变更类型 | 版本号变更 | 示例 |
|---|---|---|
| 修复 Bug,不改变 API | 修订号 +1 | 1.0.0 -> 1.0.1 |
| 新增 API,不破坏旧 API | 次版本 +1 | 1.0.0 -> 1.1.0 |
| 修改/删除 API | 主版本 +1 | 1.0.0 -> 2.0.0 |
@Deprecated 迁移策略
接口废弃时应遵循先标记再删除的流程:
/**
* 字符串工具类
* @since 1.0.0
*/
public class StringUtils {
/**
* 将字符串转为驼峰命名
* @deprecated 从 2.0.0 起废弃,建议使用 {@link #toCamelCase(String, boolean)}
* 该方法将在 3.0.0 版本移除
*/
@Deprecated
public static String toCamelCase(String str) {
return toCamelCase(str, false);
}
/**
* 将字符串转为驼峰命名
* @param str 输入字符串
* @param firstUpper 首字母是否大写
* @return 驼峰命名字符串
* @since 2.0.0
*/
public static String toCamelCase(String str, boolean firstUpper) {
// 新实现
return "";
}
}适配器模式
当 API 发生不兼容变更时,适配器模式可以帮助使用方平稳过渡:
// 老版本接口(v1)
public interface DataSourceV1 {
Connection getConnection() throws SQLException;
}
// 新版本接口(v2)
public interface DataSourceV2 {
Connection getConnection(String username, String password) throws SQLException;
Connection getConnection() throws SQLException;
}
// 适配器:将 V2 适配为 V1
public class DataSourceV2ToV1Adapter implements DataSourceV1 {
private final DataSourceV2 delegate;
public DataSourceV2ToV1Adapter(DataSourceV2 delegate) {
this.delegate = delegate;
}
@Override
public Connection getConnection() throws SQLException {
return delegate.getConnection();
}
}工具类库设计
通用工具类设计
工具类设计应遵循以下封装原则:
断言工具类
package com.example.common.lang;
/**
* 参数校验工具,失败时抛 IllegalArgumentException
*/
public final class Assert {
public static void notNull(Object object, String message) {
if (object == null) {
throw new IllegalArgumentException(message);
}
}
public static void hasText(String text, String message) {
if (text == null || text.trim().isEmpty()) {
throw new IllegalArgumentException(message);
}
}
public static void isTrue(boolean expression, String message) {
if (!expression) {
throw new IllegalArgumentException(message);
}
}
public static void notEmpty(Collection<?> collection, String message) {
if (collection == null || collection.isEmpty()) {
throw new IllegalArgumentException(message);
}
}
private Assert() {}
}反射工具类
package com.example.common.lang;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public final class ReflectUtils {
/**
* 获取指定类及其父类的所有字段(包含私有字段)
*/
public static Field[] getAllFields(Class<?> clazz) {
List<Field> fields = new ArrayList<>();
Class<?> current = clazz;
while (current != null && current != Object.class) {
Collections.addAll(fields, current.getDeclaredFields());
current = current.getSuperclass();
}
return fields.toArray(new Field[0]);
}
/**
* 安全调用方法(处理反射异常)
*/
public static Object invokeMethod(Object target, String methodName, Object... args) {
Class<?>[] paramTypes = Arrays.stream(args)
.map(Object::getClass)
.toArray(Class[]::new);
try {
Method method = target.getClass().getDeclaredMethod(methodName, paramTypes);
method.setAccessible(true);
return method.invoke(target, args);
} catch (Exception e) {
throw new RuntimeException("Failed to invoke method: " + methodName, e);
}
}
private ReflectUtils() {}
}集合工具类原则
- 返回空集合而不是 null,避免 NPE
- 不要修改传入的参数集合,而是创建新集合操作
- 对于大集合操作,提供分页或流式处理的版本
字符串工具类原则
- 所有方法对 null 安全(返回空串或默认值)
- 避免在循环中拼接字符串,使用 StringBuilder
- 正则表达式预编译 Pattern 并缓存
IO 工具类原则
- 提供 try-with-resources 的封装方法
- 操作完成后必须在 finally 中关闭资源
- 使用缓冲流包装原始流
日期工具类原则
- 使用
java.time包(JSR 310),避免使用java.util.Date和SimpleDateFormat - 所有日期操作指定时区,不依赖系统默认时区
- 提供格式常量,避免重复创建
DateTimeFormatter
package com.example.common.lang;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
public final class DateUtils {
public static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final DateTimeFormatter FORMATTER =
DateTimeFormatter.ofPattern(STANDARD_FORMAT).withZone(ZoneOffset.UTC);
/**
* 将时间戳转为标准格式字符串(UTC)
*/
public static String formatTimestamp(long epochMillis) {
return FORMATTER.format(Instant.ofEpochMilli(epochMillis));
}
/**
* 解析标准格式字符串为 LocalDateTime(UTC)
*/
public static LocalDateTime parseUtc(String dateStr) {
return LocalDateTime.parse(dateStr, FORMATTER);
}
/**
* 计算两个时间之间的工作日天数(排除周末)
*/
public static long businessDaysBetween(LocalDate start, LocalDate end) {
long days = Duration.between(start.atStartOfDay(), end.atStartOfDay()).toDays();
long businessDays = 0;
for (int i = 0; i < days; i++) {
LocalDate d = start.plusDays(i);
if (d.getDayOfWeek() != DayOfWeek.SATURDAY
&& d.getDayOfWeek() != DayOfWeek.SUNDAY) {
businessDays++;
}
}
return businessDays;
}
private DateUtils() {}
}异常体系设计
一个完善的底层库需要设计统一的异常体系:
package com.example.common.exception;
/**
* 错误码接口,枚举实现
*/
public interface ErrorCode {
int getCode();
String getMessage();
}package com.example.common.exception;
/**
* 业务异常码枚举
*/
public enum BizErrorCode implements ErrorCode {
// 通用错误 (1000-1999)
PARAM_INVALID(1000, "参数校验失败"),
RESOURCE_NOT_FOUND(1001, "资源不存在"),
RESOURCE_CONFLICT(1002, "资源冲突"),
OPERATION_TIMEOUT(1003, "操作超时"),
// 业务错误 (2000-2999)
ORDER_NOT_FOUND(2000, "订单不存在"),
ORDER_STATUS_INVALID(2001, "订单状态非法"),
PAYMENT_FAILED(2002, "支付失败"),
// 系统错误 (5000-5999)
SYSTEM_ERROR(5000, "系统内部错误"),
REMOTE_SERVICE_ERROR(5001, "远程服务调用异常"),
DATABASE_ERROR(5002, "数据库异常");
private final int code;
private final String message;
BizErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
@Override
public int getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}package com.example.common.exception;
/**
* 业务异常基类,所有业务异常继承此类
*/
public class BizException extends RuntimeException {
private final int code;
private final String errorMessage;
public BizException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
this.errorMessage = errorCode.getMessage();
}
public BizException(ErrorCode errorCode, String detail) {
super(errorCode.getMessage() + ": " + detail);
this.code = errorCode.getCode();
this.errorMessage = errorCode.getMessage() + ": " + detail;
}
public BizException(ErrorCode errorCode, Throwable cause) {
super(errorCode.getMessage(), cause);
this.code = errorCode.getCode();
this.errorMessage = errorCode.getMessage();
}
public int getCode() {
return code;
}
public String getErrorMessage() {
return errorMessage;
}
}package com.example.common.exception;
/**
* 参数校验异常
*/
public class ParamValidationException extends BizException {
private final String field;
private final Object rejectedValue;
public ParamValidationException(String field, Object rejectedValue, String message) {
super(BizErrorCode.PARAM_INVALID, message);
this.field = field;
this.rejectedValue = rejectedValue;
}
public String getField() {
return field;
}
public Object getRejectedValue() {
return rejectedValue;
}
}package com.example.common.exception;
/**
* 全局统一异常处理(Spring Boot 场景)
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ParamValidationException.class)
public ResponseEntity<ErrorResponse> handleParamInvalid(ParamValidationException e) {
ErrorResponse error = new ErrorResponse(e.getCode(), e.getMessage(), e.getField());
return ResponseEntity.badRequest().body(error);
}
@ExceptionHandler(BizException.class)
public ResponseEntity<ErrorResponse> handleBizException(BizException e) {
ErrorResponse error = new ErrorResponse(e.getCode(), e.getErrorMessage(), null);
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleUnknown(Exception e) {
log.error("unexpected error", e);
ErrorResponse error = new ErrorResponse(5000, "系统内部错误", null);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
@lombok.Data
@lombok.AllArgsConstructor
public static class ErrorResponse {
private int code;
private String message;
private String field;
}
}扩展点设计
底层库通过插件式架构提供扩展能力,核心模式包括 Filter 链和 Interceptor。
Filter 链模式
Filter 链通过责任链模式实现请求的处理流水线:
public interface Filter {
/**
* 执行过滤逻辑
* @param request 请求上下文
* @param chain 过滤器链,用于调用下一个过滤器
* @return 响应结果
*/
Response invoke(Request request, FilterChain chain);
}
public interface FilterChain {
Response invoke(Request request);
}public class DefaultFilterChain implements FilterChain {
private final Filter filter;
private final FilterChain next;
public DefaultFilterChain(Filter filter, FilterChain next) {
this.filter = filter;
this.next = next;
}
@Override
public Response invoke(Request request) {
// 调用当前 Filter,传递下一个节点
return filter.invoke(request, next);
}
}
public class FilterPipeline {
private final List<Filter> filters = new ArrayList<>();
public FilterPipeline addFilter(Filter filter) {
filters.add(filter);
return this;
}
public FilterChain build() {
// 从最后一个 Filter 开始逆序构建链
FilterChain chain = request -> {
// 末端节点,执行实际业务逻辑
return Response.success();
};
for (int i = filters.size() - 1; i >= 0; i--) {
Filter filter = filters.get(i);
chain = new DefaultFilterChain(filter, chain);
}
return chain;
}
}结合 SPI 实现 Filter 的自动发现和排序:
public class FilterManager {
private static final List<Filter> FILTERS = new ArrayList<>();
static {
// 通过 SPI 加载所有 Filter
ServiceLoader<Filter> loader = ServiceLoader.load(Filter.class);
for (Filter filter : loader) {
FILTERS.add(filter);
}
// 按 @Activate 注解排序
FILTERS.sort(Comparator.comparingInt(
f -> {
Activate act = f.getClass().getAnnotation(Activate.class);
return act != null ? act.order() : Integer.MAX_VALUE;
}
));
}
public static FilterChain createChain() {
FilterPipeline pipeline = new FilterPipeline();
FILTERS.forEach(pipeline::addFilter);
return pipeline.build();
}
}Interceptor 模式
Interceptor 与 Filter 类似,但更强调在方法调用前后插入横切逻辑:
public interface Interceptor {
/**
* 前置处理
* @return true 继续执行,false 拦截
*/
default boolean before(Object target, Method method, Object[] args) {
return true;
}
/**
* 后置处理
*/
default void after(Object target, Method method, Object[] args, Object result) {}
/**
* 异常处理
*/
default void exception(Object target, Method method, Object[] args, Throwable e) {}
/**
* 最终处理(类似 finally)
*/
default void finally_(Object target, Method method, Object[] args) {}
}public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public Object proceed(Object target, Method method, Object[] args) {
try {
// 执行所有前置拦截器
for (Interceptor interceptor : interceptors) {
if (!interceptor.before(target, method, args)) {
return null; // 被拦截
}
}
// 执行目标方法
Object result = method.invoke(target, args);
// 执行所有后置拦截器
for (Interceptor interceptor : interceptors) {
interceptor.after(target, method, args, result);
}
return result;
} catch (Throwable e) {
// 执行异常拦截器
for (Interceptor interceptor : interceptors) {
interceptor.exception(target, method, args, e);
}
throw new RuntimeException(e);
} finally {
// 执行最终拦截器
for (Interceptor interceptor : interceptors) {
interceptor.finally_(target, method, args);
}
}
}
}底层库测试与发布
单元测试
SPI 多实现测试
使用参数化测试验证多个 SPI 实现:
@ExtendWith(MockitoExtension.class)
class PaymentServiceTest {
@Test
void testAllSpiImplementations() {
ServiceLoader<PaymentService> loader =
ServiceLoader.load(PaymentService.class);
List<PaymentService> implementations = new ArrayList<>();
loader.forEach(implementations::add);
assertThat(implementations).hasSize(2);
for (PaymentService service : implementations) {
boolean result = service.pay("ORDER001", new BigDecimal("100.00"));
assertTrue(result);
assertNotNull(service.getChannel());
}
}
}参数化测试
class StringUtilsTest {
@ParameterizedTest
@CsvSource({
"helloWorld, helloWorld, false",
"HelloWorld, helloWorld, true",
"user_name, userName, false",
"USER_NAME, userName, true"
})
void toCamelCase(String input, String expected, boolean firstUpper) {
assertThat(StringUtils.toCamelCase(input, firstUpper))
.isEqualTo(expected);
}
@ParameterizedTest
@MethodSource("blankStringProvider")
void toCamelCase_blankInput_returnsEmpty(String input) {
assertThat(StringUtils.toCamelCase(input, false)).isEmpty();
}
static Stream<String> blankStringProvider() {
return Stream.of(null, "", " ");
}
}多线程测试
SPI 扩展点加载器通常是全局单例,需要验证并发安全性:
class ExtensionLoaderConcurrencyTest {
@Test
void testConcurrentGetExtension() throws Exception {
int threadCount = 20;
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
CountDownLatch latch = new CountDownLatch(1);
AtomicInteger successCount = new AtomicInteger(0);
for (int i = 0; i < threadCount; i++) {
executor.submit(() -> {
try {
latch.await(); // 同时开始
ExtensionLoader<PaymentService> loader =
ExtensionLoader.getExtensionLoader(PaymentService.class);
PaymentService service = loader.getExtension("alipay");
assertNotNull(service);
successCount.incrementAndGet();
} catch (Exception e) {
// 记录异常
}
});
}
latch.countDown(); // 并发触发
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
assertThat(successCount.get()).isEqualTo(threadCount);
}
}SPI 加载测试
创建测试专用的 SPI 配置文件:
# src/test/resources/META-INF/services/com.example.spi.PaymentService
com.example.test.MockAlipayService
com.example.test.MockWechatPayServiceclass SpiLoadingTest {
@BeforeAll
static void setup() {
// 清理已有缓存,确保测试加载测试配置
ServiceLoader<?> loader = ServiceLoader.load(PaymentService.class);
loader.reload();
}
@Test
void testSpiConfigurationLoaded() {
ServiceLoader<PaymentService> loader =
ServiceLoader.load(PaymentService.class);
Map<String, PaymentService> services = new HashMap<>();
for (PaymentService service : loader) {
services.put(service.getChannel(), service);
}
// 验证测试配置中的两个 mock 实现都被加载
assertThat(services).containsOnlyKeys("mock_alipay", "mock_wechat");
// 验证可以按渠道获取正确的实现
assertThat(services.get("mock_alipay").pay("ORDER001", BigDecimal.TEN))
.isTrue();
}
@Test
void testCustomClassLoader() {
ClassLoader customLoader = new URLClassLoader(
new URL[]{/* 自定义路径 */},
this.getClass().getClassLoader()
);
ServiceLoader<PaymentService> loader =
ServiceLoader.load(PaymentService.class, customLoader);
assertThat(loader).isNotEmpty();
}
}兼容性测试
SPI 接口变更是底层库中最敏感的操作,需要通过兼容性测试验证影响范围:
class CompatibilityTest {
/**
* 验证 SPI 接口方法签名兼容性
* 新增方法时,确保提供了默认实现(default method)
*/
@Test
void testSpiInterfaceCompatibility() {
// 反射获取接口所有方法
Method[] methods = PaymentService.class.getMethods();
for (Method method : methods) {
if (method.isDefault()) {
continue; // default 方法不影响现有实现
}
// 抽象方法必须有对应实现
assertThat(method.getModifiers())
.withFailMessage("非 default 方法 %s 不能删除或修改", method)
.isNotEqualTo(0);
}
}
/**
* 验证已有实现都能正常加载和调用
*/
@Test
void testAllImplementationsLoadable() {
ServiceLoader<PaymentService> loader =
ServiceLoader.load(PaymentService.class);
for (PaymentService service : loader) {
// 验证核心方法可用
assertDoesNotThrow(() -> service.getChannel());
assertDoesNotThrow(
() -> service.pay("TEST_ORDER", new BigDecimal("1.00"))
);
}
}
/**
* 验证 @Deprecated 方法的兼容性
*/
@Test
void testDeprecatedMethodsStillWork() {
ServiceLoader<PaymentService> loader =
ServiceLoader.load(PaymentService.class);
for (PaymentService service : loader) {
// 即使标记了 @Deprecated,旧方法也必须正常工作
// 直到主版本升级才移除
Class<?> clazz = service.getClass();
for (Method method : clazz.getMethods()) {
if (method.isAnnotationPresent(Deprecated.class)) {
// 验证该方法仍然可用
assertDoesNotThrow(() -> {
if (method.getParameterCount() == 0) {
method.invoke(service);
}
});
}
}
}
}
/**
* 模拟 SPI 接口添加新方法的影响测试
*/
@Test
void testNewMethodWithDefaultImplementation() {
// 验证所有现有实现类都不需要修改
ServiceLoader<PaymentService> loader =
ServiceLoader.load(PaymentService.class);
for (PaymentService service : loader) {
// 如果接口新增了 default 方法,现有实现无需修改
// 这里测试的是接口层面,不是具体实现
boolean hasGetTransactionId = Arrays.stream(service.getClass().getMethods())
.anyMatch(m -> m.getName().equals("getTransactionId"));
// 实现类可以不实现 default 方法
if (!hasGetTransactionId) {
// 通过接口调用 default 方法
assertDoesNotThrow(() -> {
PaymentService.class
.getMethod("getTransactionId")
.invoke(service);
});
}
}
}
}兼容性测试的核心关注点:
| 变更类型 | 兼容性影响 | 测试策略 |
|---|---|---|
| 新增接口方法(default) | 兼容 | 验证现有实现无需修改 |
| 新增接口方法(abstract) | 不兼容 | 主版本升级,所有实现必须修改 |
| 修改方法签名 | 不兼容 | 主版本升级 |
| 标记 @Deprecated | 兼容 | 验证旧方法仍可用 |
| 删除 @Deprecated 方法 | 不兼容 | 主版本升级时移除 |
| 新增 SPI 配置项 | 兼容 | 验证旧配置仍能被加载 |
| 删除 SPI 配置项 | 不兼容 | 主版本变更需文档说明 |