在Spring中使用策略模式
|Word count:947|Reading time:3min
策略模式
问题:
虽然使用策略模式能很好的解决这个问题,通过将不同的云存储的sdk封装为各自的工具类,都去实现同一个接口,让我们在使用文件上传的时候无需关注到底是通过哪个云存储去上传。也很方便的增加或者减少各种工具类。但是在Spring如何去判断和导入各种不同平台的文件上传工具类呢?
我们定义了这么多策略,应该怎么优雅的组织起来呢,这就需要用到Spring提供的一些扩展特性了,Spring主要为我们提供了三类扩展点,分别对应不同Bean生命周期阶段:
具体实现
1、编写文件上传的接口
1 2 3 4 5
| public interface FileUploadService {
void upload(String bucketName, String fileName, InputStream stream);
}
|
2、添加不同的云存储sdk工具类,都实现该接口
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
| @Component("Minio") @Slf4j public class MinionUtil implements FileUploadService {
@Resource private MinioClient minioClient;
@Override @SneakyThrows(Exception.class) public void upload(String bucketName, String fileName, InputStream stream) { if (stream == null){ log.error("文件流为空,上传文件失败"); return; } minioClient.putObject( PutObjectArgs.builder() .bucket(bucketName) .object(fileName) .stream(stream, stream.available(), -1) .build()); log.info(fileName + "上传成功"); }
} @Component("Oss") public class OssUtil implements FileUploadService { @Override public void upload(String bucketName, String fileName, InputStream stream) { } }
|
类似这样直接添加需要的就行,在上述的代码中可以使用 @Component
注解类交由 Spring 进行管理。
3、实现策略选择的过程
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
| @Component @Slf4j public class UploadStrategyContext implements ApplicationContextAware, InitializingBean {
private static final String defaultCloudType = CloudType.MINIO.getValue();
private ApplicationContext applicationContext;
private Map<String, FileUploadService> uploadStrategyMap = new ConcurrentHashMap<>();
@Override public void afterPropertiesSet() throws Exception { uploadStrategyMap = applicationContext.getBeansOfType(FileUploadService.class); }
@Override public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }
public FileUploadService getResource(String cloudType) { FileUploadService uploadService = uploadStrategyMap.get(cloudType); return uploadService != null ? uploadService : uploadStrategyMap.get(defaultCloudType); }
}
|
这里使用了 Spring 的 ApplicationContextAware
接口,该接口要求实现 setApplicationContext
方法,然后在通过 applicationContext
获取所有的 FileUploadService
接口实现。
同时这样做一个最大的好处就是添加新的存储系统的时候直接继承 FileUploadService
接口进行实现即可,无需对原有的代码进行修改,做到了真正的对修改封闭,对扩展开放。
上面我们这里用到的主要是 Aware 接口和 InitializingBean 两个扩展点,关键点就在于实现 ApplicationContextAware 接口的 setApplicationContext 方法和 InitializingBean 接口的 afterPropertiesSet 方法。
实现 ApplicationContextAware 接口的目的就是要拿到 Spring 容器的资源,从而方便的使用它提供的 getBeansOfType 方法(该方法返回的是 map 类型,key 对应 beanName, value 对应 bean);而实现 InitializingBean 接口的目的则是方便为 Service 类的 handlers 属性执行定制初始化逻辑。
可以很明显的看出,如果以后还有一些其他的业务需要制定相应的鉴权逻辑,我们只需要编写对应的策略类就好了,无需再破坏当前 Service 类的逻辑,很好的保证了开闭原则。
4、封装成统一的文件上传工具类,所有文件上传通过此工具类上传
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
| @Slf4j @Component public class FileUploadUtil {
@Resource private CloudProperties cloudProperties;
@Resource private UploadStrategyContext uploadStrategyContext;
public void upload(String bucketName, String fileName, InputStream stream){ FileUploadService uploadService = uploadStrategyContext.getResource(cloudProperties.getType()); uploadService.upload(bucketName,fileName,stream); }
}
|
参考:https://zhuanlan.zhihu.com/p/347389684?utm_source=wechat_session&utm_medium=social&utm_oi=779429778875772928&utm_campaign=shareopn