使用S3协议通过dfs实现Spring的SPI机制和spring.factories

news2024/11/24 12:45:25

目录

  • 参考
  • 一、SPI机制
    • 1、什么是SPI
    • 2、使用场景?
    • 3、使用介绍
    • 4、代码演示
      • 新建工程edevp-dfs-api的spi接口
      • 新建阿里云oss实现类
      • 新建minio实现类
      • 新建测试工程edevp-demo
      • 测试
    • 5、总结
      • 优点:解耦
      • 缺点:
  • 二、Spring Boot的扩展机制之Spring Factories
    • 1.概述
    • 2.实现原理是什么
    • 3.Spring Factories在Spring Boot中的应用
    • 3、edevp-dfs-sdk代码实现
      • 新建DfsProperties
      • 新建DfsClient
      • 新建DfsServiceImpl
      • 新建DfsAutoConfiguration启动类
      • 新建`META-INF/spring.factories`
      • 定义yml
      • edevp-dfs-demo引入包测试

参考

可插拔组件设计机制—SPI
Spring.factories
通过S3协议实现通用的文件存储服务中间件

一、SPI机制

1、什么是SPI

SPI 的全称是Service Provider Interface,即提供服务接口;是一种服务发现机制,SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。
通过多模块分布式文件存储edevp-dfs展示SPI机制

2、使用场景?

数据库驱动加载接口实现类的加载;如:JDBC 加载Mysql,Oracle…
日志门面接口实现类加载,如:SLF4J 对log4j、logback 的支持
Spring中大量使用了SPI,特别是spring-boot 中自动化配置的实现
Dubbo 也是大量使用SPI 的方式实现框架的扩展,它是对原生的SPI 做了封装,允许用户扩展实现Filter 接口。

3、使用介绍

要使用 Java SPI,需要遵循以下约定:

当服务提供者提供了接口的一种具体实现后,需要在JAR 包的META-INF/services 目录下创建一个以“接口全限制定名”为命名的文件,内容为实现类的全限定名;
接口实现类所在的JAR放在主程序的classpath 下,也就是引入依赖。
主程序通过java.util.ServiceLoder 动态加载实现模块,它会通过扫描META-INF/services 目录下的文件找到实现类的全限定名,把类加载值JVM,并实例化它;
SPI 的实现类必须携带一个不带参数的构造方法。
示例:

4、代码演示

新建工程edevp-dfs-api的spi接口

package com.edevp.dfs.api;
/**
 * @create 2023-04-11
 */
public interface DfsService {
    /**
     * 创建bucket
     * @param bucketName 存储桶
     */
    void createBucket(String bucketName);
    /**
     * 判断存储桶是否存在
     * @param bucketName 存储桶
     */
    default boolean doesBucketExist(String bucketName){return true;}
    /**
     * 初始化
     */
    void init();
}

新建阿里云oss实现类

public class OssServiceImpl implements DfsService {

    @Override
    public void createBucket(String bucketName) {
        if (!amazonS3.doesBucketExistV2(bucketName)) {
            amazonS3.createBucket((bucketName));
        }
    }
    public boolean doesBucketExist(String bucketName){
        return enable && amazonS3.doesBucketExistV2(bucketName);
    }

    @PostConstruct
    @Override
    public void init() {
        System.out.println("oss");
    }

在resource下新建/META-INF/services/com.edevp.dfs.api.DfsService的文件,内容如下:

com.edevp.dfs.impl.OssServiceImpl

在这里插入图片描述

新建minio实现类

public class MinioServiceImpl implements DfsService {
    @Override
    public void createBucket(String bucketName) {
        System.out.println("minio");
    }

    @Override
    public boolean doesBucketExist(String bucketName) {
        return false;
    }

    @Override
    public void init() {
        System.out.println("minio");
    }
}

在resource下新建/META-INF/services/com.edevp.dfs.api.DfsService的文件,内容如下:

com.edevp.dfs.impl.MinioServiceImpl

新建测试工程edevp-demo

引入连个子模块的jar包

<dependency>
    <groupId>com.edevp</groupId>
    <artifactId>edevp-dfs-api</artifactId>
</dependency>
<dependency>
    <groupId>com.edevp</groupId>
    <artifactId>edevp-dfs-minio</artifactId>
</dependency>
<dependency>
    <groupId>com.edevp</groupId>
    <artifactId>edevp-dfs-oss</artifactId>
</dependency>

测试

新建Application测试,通过serviceLoader加载实现类,发现有两个

public class Application {

    static void invoker(){
        ServiceLoader<DfsService>  serviceLoader = ServiceLoader.load(DfsService.class);
        Iterator<DfsService> drivers = serviceLoader.iterator();
        boolean isNotFound = true;
        while (drivers.hasNext()){
            isNotFound = false;
            drivers.next().init();
        }
        if(isNotFound){
            throw new RuntimeException("一个驱动实现类都不存在");
        }
    }

    public static void main( String[] args )
    {
        invoker();
    }
}

执行发现分别输出
在这里插入图片描述

oss
minio

5、总结

优点:解耦

SPI 的使用,使得第三方服务模块的装配控制逻辑与调用者的业务代码分离,不会耦合在一起,应用程序可以根据实际业务情况来启用框架扩展和替换框架组件。

SPI 的使用,使得无须通过下面几种方式获取实现类

代码硬编码import 导入
指定类全限定名反射获取,例如JDBC4.0 之前;Class.forName(“com.mysql.jdbc.Driver”)

缺点:

虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。

二、Spring Boot的扩展机制之Spring Factories

1.概述

在 Spring Boot 项目中,怎样将 pom.xml 文件里面添加的依赖中的 bean 注册到 Spring Boot 项目的 Spring 容器中呢?

你可能会首先想到使用 @ComponentScan 注解,遗憾的是 @ComponentScan 注解只能扫描 Spring Boot 项目包内的 bean 并注册到 Spring 容器中,项目依赖包中的 bean 不会被扫描和注册,除非通过@SpringBootApplication(scanBasePackages = {"com.xx"})。此时,我们需要使用 @EnableAutoConfiguration 注解来注册项目依赖包中的 bean。而 spring.factories 文件,可用来记录项目包外需要注册的 bean 类名。

使用 spring.factories 文件有什么好处呢?假如我们封装了一个插件,该插件提供给其他开发人员使用。我们可以在 spring.factories 文件中指定需要自动注册到 Spring 容器的 bean 和一些配置信息。使用该插件的开发人员只需少许配置,甚至不进行任何配置也能正常使用。

在Spring中也有一种类似与Java SPI的加载机制。它在META-INF/spring.factories文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。
这种自定义的SPI机制是Spring Boot Starter实现的基础。

2.实现原理是什么

spring-core包里定义了SpringFactoriesLoader类,这个类实现了检索META-INF/spring.factories文件,并获取指定接口的配置的功能。在这个类中定义了两个对外的方法:

  • loadFactories 根据接口类获取其实现类的实例,这个方法返回的是对象列表。
  • loadFactoryNames 根据接口获取其接口类的名称,这个方法返回的是类名的列表。

从代码中我们可以知道,在这个方法中会遍历整个ClassLoader中所有jar包下的spring.factories文件。也就是说我们可以在自己的jar中配置spring.factories文件,不会影响到其它地方的配置,也不会被别人的配置覆盖。

spring.factories的是通过Properties解析得到的,所以我们在写文件中的内容都是安装下面这种方式配置的:

com.xxx.interface=com.xxx.classname

3.Spring Factories在Spring Boot中的应用

在Spring Boot的很多包中都能够找到spring.factories文件,接下来我们以spring-boot包为例进行介绍
在这里插入图片描述
在日常工作中,我们可能需要实现一些SDK或者Spring Boot Starter给被人使用时,
我们就可以使用Factories机制。Factories机制可以让SDK或者Starter的使用只需要很少或者不需要进行配置,只需要在服务中引入我们的jar包即可。

3、edevp-dfs-sdk代码实现

新建DfsProperties

@Data
@ConfigurationProperties(prefix = DfsProperties.PREFIX)
public class DfsProperties {

    public DfsProperties(){
        System.out.println("===DfsProperties====");
    }

    /**
     * 配置前缀
     */
    public static final String PREFIX = "dfs";

    /**
     * 是否启用 oss,默认为:true
     */
    private boolean enable = true;

    /**
     * 对象存储服务的URL
     */
    private String endpoint;

    /**
     * 自定义域名
     */
    private String customDomain;

    /**
     * true path-style nginx 反向代理和S3默认支持 pathStyle {<a href="http://endpoint/bucketname">...</a>} false
     * supports virtual-hosted-style 阿里云等需要配置为 virtual-hosted-style
     */
    private Boolean pathStyleAccess = true;

    /**
     * 区域
     */
    private String region;

    /**
     * Access key就像用户ID,可以唯一标识你的账户
     */
    private String accessKey;

    /**
     * Secret key是你账户的密码
     */
    private String secretKey;

    /**
     * 默认的存储桶名称
     */
    private String bucketName;

}

新建DfsClient

@Slf4j
@RequiredArgsConstructor
public class DfsClient{
    private final DfsProperties ossProperties;

    private AmazonS3 amazonS3;

    /**
     * 创建bucket
     *
     * @param bucketName bucket名称
     */
    public void createBucket(String bucketName) {
        if (!amazonS3.doesBucketExistV2(bucketName)) {
            amazonS3.createBucket((bucketName));
        }
    }

    public boolean doesBucketExist(String bucketName){
        return amazonS3.doesBucketExistV2(bucketName);
    }

    @PostConstruct
    public void afterPropertiesSet()  {
        try{
            ClientConfiguration clientConfiguration = new ClientConfiguration();
            AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
                    ossProperties.getEndpoint(), ossProperties.getRegion());
            AWSCredentials awsCredentials = new BasicAWSCredentials(ossProperties.getAccessKey(),
                    ossProperties.getSecretKey());
            AWSCredentialsProvider awsCredentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);
            this.amazonS3 = AmazonS3Client.builder().withEndpointConfiguration(endpointConfiguration)
                    .withClientConfiguration(clientConfiguration).withCredentials(awsCredentialsProvider)
                    .disableChunkedEncoding().withPathStyleAccessEnabled(ossProperties.getPathStyleAccess()).build();

        }catch(Exception e){
            e.printStackTrace();
        }

    }
}

新建DfsServiceImpl

@AllArgsConstructor
public class DfsServiceImpl implements DfsService {
    private final DfsClient dfsClient;
    @Override
    public void createBucket(String bucketName) {
        dfsClient.createBucket(bucketName);
    }

    @Override
    public boolean doesBucketExist(String bucketName) {
        return dfsClient.doesBucketExist(bucketName);
    }

    @Override
    public void init() {

    }
}

新建DfsAutoConfiguration启动类

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "dfs.enable", havingValue = "true")
@EnableConfigurationProperties({DfsProperties.class})
@Import({DfsClient.class, DfsServiceImpl.class})
public class DfsAutoConfiguration {

    public DfsAutoConfiguration(){
        System.out.println("===DfsAutoConfiguration====");
    }

    @Resource
    private DfsProperties dfsProperties;

    @PostConstruct
    public void init(){

        System.out.println("===DfsAutoConfiguration==init=="+dfsProperties);
    }
}

注意:

  • @Configuration(proxyBeanMethods = false):此值默认是true哦,需要显示改为false才算是Lite模式,运行时不再需要给对应类生成CGLIB子类,提高了运行性能,降低了启动时间。可以参考
  • @AutoConfigureAfter:在加载配置的类之后再加载当前类
  • @EnableConfigurationProperties:注入配置类DfsProperties
  • @Import 实例化DfsClient和DfsServiceImpl

新建META-INF/spring.factories

内容如下

org.springframework.boot.autoconfigure.EnableAutoConfiguration = \com.edevp.dfs.sdk.config.DfsAutoConfiguration

定义yml

dfs:
  enable: true
  endpoint: http://192.168.0.44:9080
  access-key: minioadmin
  secret-key: minioadmin

edevp-dfs-demo引入包测试

用@Resouce,如果dfs.enable是false,则启动报错依赖注入,如果用@Autowired(required=false)即使dfs.enable是false也不会报错

@Slf4j
@Component
public class StartRunner implements CommandLineRunner {

    @Autowired(required=false)
    private DfsService dfsService;
    @Autowired(required=false)
    private DfsProperties dfsProperties;


    @Override
    public void run(String... args) throws Exception {
        log.info(ossService+"======StartRunner======"+dfsProperties);
        if(dfsService!= null){
            dfsService.createBucket("test");
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/448902.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MathType7最新版本下载安装与使用,注册表文件分享,添加为Word公式插件

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;mathtype 免费获取MathType7安装包注册表文件 MathType是强大的数学公式编辑器&#xff0c;与常见的文字处理软件和演示程序配合使用&#xff0c;能够在各种文档中加入复杂的数学公式和符号&#xff0c;可用在编辑数学试卷…

【回忆 总结】我的大学四年

大学四年关键词速览 如果穿越回大一&#xff0c;你想对大一的你提什么最重要的建议&#xff1f;同样是上网课&#xff0c;我为何能比大多数同学学的更好&#xff1f;回到学校&#xff0c;我的大二似乎一帆风顺&#xff1f;在不断的迷茫和徘徊中&#xff0c;大三的我做出的决定&…

Vue开发过程中那些易混淆的知识点

vue & vue cli Vue CLI Vue 一堆的js插件Vue CLI是基于 Node.js 开发出来的工具&#xff0c;它是一个官方发布 vue.js 项目脚手架&#xff0c;可以快速搭建 Vue 开发环境以及对应的 webpack 配置&#xff0c;单独编译&#xff0c;单独部署。可以再集成各种第三方插件&am…

偏好强化学习概述

文章目录 为什么需要了解偏好强化学习什么是偏好强化学习基于偏好的马尔科夫决策过程&#xff08;Markov decision processes with preferences&#xff0c;MDPP&#xff09; 反馈类型分类学习算法分类近似策略分布(Approximating the Policy Distribution)比较和排序策略(Comp…

STATS 782 - R Basic Concepts

文章目录 前言一、R basic1. R Operator2. 变量赋值3. c() - combine 函数4. 对向量的操作5. Special Numerical Values 二、 Built-in Functions1. min, max and range2. sum and prod3. Cumulative Summaries4. paste5. list&#xff08;&#xff09;6. seq&#xff08;&…

【Unity VR开发】结合VRTK4.0:添加遮蔽追踪器

语录&#xff1a; 恋爱应该是双方扶持对方共同完成自己的目标&#xff0c;而不是虚幻的思想、肤浅的物质、和纸醉金迷的生活。 前言&#xff1a; 遮蔽追踪器&#xff08;Trackers.ObscuranceTracker&#xff09;是基于游戏对象存在或不可见之间切换对象的状态&#xff0c;从而遮…

SpringBoot 通过AOP + Redis 防止表单重复提交

Spring Boot是一个用于构建Web应用程序的框架&#xff0c;通过AOP可以实现防止表单重复提交。在这篇博客中&#xff0c;我将介绍如何使用AOP来防止表单重复提交。 配置Redis 1. 添加Redis依赖 <dependency><groupId>org.springframework.boot</groupId>&…

【Python爬虫项目实战二】Chatgpt还原验证算法-解密某宝伪知网数据接口

目录 🐱背景🐱工具🐱分析流程🐔登陆分析🐔检索分析🐔模拟HTML代码请求🐔 解析HTML🐔 再次分析🐟分析js算法🐟 拿下furl🐟拿下sfname🐟拿下sfname🐔 构造请求🐔再次瓶颈🐔分析cookie🐟 成功演示🐱总结

【历史上的今天】3 月 24 日:苹果推出 Mac OS X;微软前任 CEO 出生;Spring 1.0 正式发布

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 3 月 24 日&#xff0c;在 2016 年的今天&#xff0c;暴雪娱乐公司发布了第一人称射击多人游戏《守望先锋》。根据评分汇总网站 Metacritic 的统计&#xff0c…

新形势新政策下建企分包分供管理模式优化探索

通过建筑工程分包的模式&#xff0c;总包商可以有效地扬长避短&#xff0c;选择最有优势的分包企业&#xff0c;提高资源的使用效率&#xff0c;有利于在激烈的市场竞争中取得一席之地。当前&#xff0c;国内建筑市场工程分包总体运行情况良好&#xff0c;但还是存在着诸多阻碍…

Postcat 如何生成接口文档,2 分钟学会

Postcat 是一个强大的开源、跨平台&#xff08;Windows、Mac、Linux、Browsers...&#xff09;的 API 开发测试工具&#xff0c;支持 REST、Websocket 等协议&#xff08;即将支持 GraphQL、gRPC、TCP、UDP&#xff09;&#xff0c;帮助你加速完成 API 开发和测试工作。 作为代…

研读Rust圣经解析——Rust learn-12(智能指针)

研读Rust圣经解析——Rust learn-12&#xff08;智能指针&#xff09; 智能指针智能指针选择Box<T>使用场景创建Box使用Box在堆上存储递归类型数据解决 通过 Deref trait 将智能指针当作常规引用处理追踪指针的值创建自定义的智能指针&#xff08;*&#xff09; Deref隐式…

手把手实现一个lombok

手把手实现一个lombok 一、lombok原理 JSR269二、实现步骤1.工程与环境依赖注意细节 2.注解处理器3.注解4.jcTree 修改语法4.新建模块依赖我们这个jar包进行编译5.源码调试 一、lombok原理 JSR269 什么是JSR &#xff1f; JSR是Java Specification Requests的缩写&#xff0c…

python 神经网络回归

神经网络回归数据&#xff0c;21条&#xff0c;每条12个月&#xff0c;根据输入预测下一年 数据集&#xff1a; 下载链接 620906209062090620906209062090620906209062090620906209062090660936365969622628916949069597656066534455211622826735957783727886869762952670916…

【远程访问及控制】

目录 一、OpenSSH服务器1.1、SSH远程管理1.2、ssh服务器的端口1.3、修改端口号1.4、设置用户登录 二、实验2.1、设置白名单2.2、设置黑名单 三、sshd 服务支持两种验证方式:3.1、密码验证3.2、密钥对验证公钥和私钥的关系&#xff1a; 四、使用SSH客户端程序4.1、ssh 远程登录4…

java项目之疫情网课管理系统(springboot+vue源码)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的疫情网课管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1a;风…

Java集合框架(Collection)

集合框架 一个Java对象可以在内部持有若干其他Java对象&#xff0c;并对外提供访问接口&#xff0c;把这种Java对象称为集合 集合框架都包含三大块内容&#xff1a;&#xff08;接口、实现、算法&#xff09; &#xff08;1&#xff09;对外的接口 &#xff08;2&#xff09;…

【C++】string类的简单模拟实现

目录 string类初识 string模拟实现 string类成员变量 构造函数 拷贝构造 赋值运算符重载 析构函数 深浅拷贝问题 string类初识 由于C语言中的字符串不太符合OOP(面向对象编程)的思想&#xff0c;而且其底层空间需要用户自己管理&#xff0c;经常有访问越界的情况出现。…

模仿风宇博客登录蒙层弹窗(vuex+computed实现)

效果图 使用了动态组件做组件切换使用vue内置的transition组件实现过渡效果&#xff0c;蒙层 和 弹框 都使用了transition&#xff0c;并嵌套vuex computed计算属性&#xff0c;实现数据 和 方法共享&#xff0c;让其它组件也能够控制到登录弹框蒙层使用了固定定位未实现&…

读取注册表中的REG_QWORD和REG_BINARY(二进制)类型数据

读取注册表中的REG_QWORD和REG_BINARY二进制类型数据 发现的问题注册表中的一些概念&#xff08;统一认识&#xff09;读取代码&#xff08;字节数据大于8的会显示f开头的前缀&#xff09;说明&#xff08;备注&#xff09;改进代码参考链接 发现的问题 首先我们要明确&#x…