在 Spring Boot 项目开发中,经常会遇到现有接口与业务所需接口不兼容、第三方组件 API 无法直接对接业务逻辑的场景,适配器模式能完美解决这类接口适配问题。同时 Spring Boot 的依赖注入和 Bean 管理机制,可便捷地实现适配器的注册、管理与调用,让接口适配的代码更优雅、易维护。
当你的业务满足以下至少 2 条时,就可以考虑使用适配器模式:
- 现有接口与目标接口定义不一致,无法直接调用;
- 需要对接多个第三方组件,各组件 API 风格不同但业务逻辑相似;
- 希望在不修改原有代码的前提下,复用已有接口实现;
- 后续可能新增更多外部组件 / 接口,需要统一对接规范;
- 需要对原有接口进行包装,屏蔽底层实现细节。
1. 适配器模式定义
- 适配器模式(Adapter Pattern)是一种结构型设计模式,它的核心是将一个类的接口转换成客户希望的另一个接口,使原本由于接口不兼容而无法一起工作的那些类可以协同工作。
- 核心思想:通过引入适配器类,作为两个不兼容接口之间的桥梁,实现接口的转换和兼容,让原有代码无需修改即可适配新的业务需求。
- 适配器模式主要分为类适配器(通过继承实现)和对象适配器(通过组合实现),在 Java 中因单继承特性,对象适配器使用更为广泛,本文也将以对象适配器为核心实现。
2. 定义适配器接口
- 为了方便切换任何一个oss,将公共方法抽取为接口,由某个oss的实现类去编写具体逻辑
java
public interface StorageAdapter {
/**
* 创建bucket
*/
void createBucket(String bucket);
/**
* 上传文件
*/
void uploadFile(MultipartFile multipartFile, String bucket, String objectName);
/**
* 获取文件在oss中的url
*/
String getUrl(String bucket, String objectName);
}
3. 实现适配器类
- Minio适配器类:通过继承或者组合方式,将被适配者类(minioUtils)的接口与适配器接口转换起来,使得客户端可以按照适配器接口进行操作。
java
@Slf4j
public class MinioStorageAdapter implements StorageAdapter {
@Resource
private MinioUtil minioUtil;
@Value("${minio.url}")
private String url;
@Override
@SneakyThrows
public void createBucket(String bucket) {
minioUtil.createBucket(bucket);
}
/**
* 上传文件
*/
@Override
@SneakyThrows
public void uploadFile(MultipartFile multipartFile, String bucket, String objectName) {
minioUtil.createBucket(bucket);
if(objectName != null) {
minioUtil.uploadFile(multipartFile.getInputStream(), bucket, objectName + "/" + multipartFile.getOriginalFilename());
} else {
minioUtil.uploadFile(multipartFile.getInputStream(), bucket, multipartFile.getOriginalFilename());
}
}
/**
* 获取文件在oss中的url
*/
@Override
public String getUrl(String bucket, String objectName) {
return url + "/" + bucket + "/" + objectName;
}
}
- Aliyun适配器类
java
/**
* 阿里云oss 具体实现逻辑
*/
public class OssStorageAdapter implements StorageAdapter {
@Override
public void createBucket(String bucket) {
System.out.println("oss");
}
@Override
public void uploadFile(MultipartFile multipartFile, String bucket, String objectName) {
}
@Override
public String getUrl(String bucket, String objectName) {
return"oss";
}
}
4. 定义StorageConfig类
-
如果想再加入一个新的OSS对象,只需新增一个xxadapter适配器类且在@Bean注解的方法中加一个else即可。
-
注意:这里直接使用new的方式创建实现类(实现类也不需要使用@Service注解),而不是先把所有的实现类通过注解定义出来,再直接返回对象,这样如果新增一个OSS的话,不光要加else,还需再把实现类通过直接定义出来。
java
@Configuration
public class StorageConfig {
@Value("${storage.service.type}")
private String storageType;
@Bean
public StorageAdapter storageAdapter() {
if ("minIo".equals(storageType)) {
return new MinioStorageAdapter();
} else if("oss".equals(storageType)) {
return new OssStorageAdapter();
} else {
throw new IllegalArgumentException("为找到对应的文件存储处理器");
}
}
}
5. 使用适配器工厂处理请求
java
@Service
public class FileService {
@Autowired
private StorageAdapter storageAdapter;
/**
* 创建bucket
*/
public void createBucket(String bucket) {
storageAdapter.createBucket(bucket);
}
/**
* 上传图片、返回图片在MinIo的地址
*/
public String uploadFile(MultipartFile multipartFile, String bucket, String objectName) {
storageAdapter.uploadFile(multipartFile, bucket, objectName);
objectName = (StringUtils.isEmpty(objectName) ? "" : objectName + "/") + multipartFile.getOriginalFilename();
return storageAdapter.getUrl(bucket, objectName);
}
}
java
@RestController
@Slf4j
public class FilesController {
@Resource
private FileService fileService;
/**
* 上传文件, 返回文件在oss中的地址
* @param uploadFile:文件,getOriginalFilename获取原始文件名
* @param bucket:桶名称
* @param objectName:上传后的文件在存储桶中的存储路径(存储目录)
*/
@PostMapping("/upload")
public ResponseEntity<String> upload(MultipartFile uploadFile, String bucket, String objectName) {
try {
Preconditions.checkArgument(!ObjectUtils.isEmpty(uploadFile), "文件不能为空");
Preconditions.checkArgument(!StringUtils.isEmpty(bucket), "bucket桶名称不能为空");
if (log.isInfoEnabled()) {
log.info("FileController.upload.uploadFile:{}, bucket:{}, objectName:{}", uploadFile.getOriginalFilename(), bucket, objectName);
}
String url = fileService.uploadFile(uploadFile, bucket, objectName);
return ResponseEntity.ok(url);
} catch (Exception e) {
log.info("FileController.upload.error:{}", e.getMessage(), e);
return ResponseEntity.ok("上传文件失败");
}
}
}
6. 适配器模式优势
- 接口兼容:解决了现有接口与目标接口不兼容的问题,让原本无法协同工作的类可以一起工作;
- 开闭原则:新增适配者(如新增银联支付回调)时,只需新增对应的适配器类,无需修改原有业务代码;
- 代码复用:无需修改原有适配者代码,直接复用现有接口实现,符合迪米特法则和开闭原则;
- 解耦业务与实现:客户端只需调用统一的目标接口,屏蔽了底层不同适配者的实现细节;
- 扩展性强:可灵活对适配器进行包装,增加日志、缓存、异常处理等通用逻辑,不影响原有代码。
7. 典型应用场景
- 第三方组件 / SDK 接口对接(如支付、短信、物流接口的统一适配);
- 老旧系统接口改造,在不修改原有代码的前提下适配新的业务接口;
- 多个异构系统集成,统一对外提供标准化接口;
- 框架之间的整合,适配不同框架的 API 规范;
- 自定义接口包装原生 JDK / 框架接口,简化业务调用。