首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何与 etcd 服务端进行通信?客户端 API 实践与核心方法介绍

如何与 etcd 服务端进行通信?客户端 API 实践与核心方法介绍

作者头像
aoho求索
发布于 2021-10-26 05:45:32
发布于 2021-10-26 05:45:32
3.3K02
代码可运行
举报
文章被收录于专栏:aoho求索aoho求索
运行总次数:2
代码可运行

你好,我是 aoho,今天我和你分享的是通信接口:客户端 API 实践与核心方法。

etcd 提供了哪些接口?你仔细阅读过 etcd 的接口文档吗?接口该如何使用?

学习客户端与 etcd 服务端的通信以及 etcd 集群节点的内部通信接口对于我们更好地使用和掌握 etcd 组件很有帮助,也是所必需了解的内容。

本文篇幅较长,我们将会介绍 etcd 的 gRPC 通信接口以及客户端的实践。

etcd clientv3 客户端

etcd 客户端 clientv3 接入的示例将会以 Go 客户端为主,读者需要准备好基本的开发环境。

首先是 etcd clientv3 的初始化,我们根据指定的 etcd 节点,建立客户端与 etcd 集群的连接。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    cli,err := clientv3.New(clientv3.Config{
        Endpoints:[]string{"localhost:2379"},
        DialTimeout: 5 * time.Second,
    })

如上的代码实例化了一个 client,这里需要传入的两个参数:

  • Endpoints:etcd 的多个节点服务地址,因为我是单点本机测试,所以只传 1 个。
  • DialTimeout:创建 client 的首次连接超时,这里传了 5 秒,如果 5 秒都没有连接成功就会返回 err;值得注意的是,一旦 client 创建成功,我们就不用再关心后续底层连接的状态了,client 内部会重连。

etcd 客户端初始化

解决完包依赖之后,我们初始化 etcd 客户端。客户端初始化代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// client_init_test.go
package client

import (
 "context"
 "fmt"
 "go.etcd.io/etcd/clientv3"
 "testing"
 "time"
)
// 测试客户端连接
func TestEtcdClientInit(t *testing.T) {

 var (
  config clientv3.Config
  client *clientv3.Client
  err    error
 )
 // 客户端配置
 config = clientv3.Config{
  // 节点配置
  Endpoints:   []string{"localhost:2379"},
  DialTimeout: 5 * time.Second,
 }
 // 建立连接
 if client, err = clientv3.New(config); err != nil {
  fmt.Println(err)
 } else {
  // 输出集群信息
  fmt.Println(client.Cluster.MemberList(context.TODO()))
 }
 client.Close()
}

如上的代码,预期的执行结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
=== RUN   TestEtcdClientInit
&{cluster_id:14841639068965178418 member_id:10276657743932975437 raft_term:3  [ID:10276657743932975437 name:"default" peerURLs:"http://localhost:2380" clientURLs:"http://0.0.0.0:2379" ] {} [] 0} <nil>
--- PASS: TestEtcdClientInit (0.08s)
PASS

可以看到 clientv3 与 etcd Server 的节点 localhost:2379 成功建立了连接,并且输出了集群的信息,下面我们就可以对 etcd 进行操作了。

client 定义

接着我们来看一下 client 的定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type Client struct {
    Cluster
    KV
    Lease
    Watcher
    Auth
    Maintenance

    // Username is a user name for authentication.
    Username string
    // Password is a password for authentication.
    Password string
}

注意,这里显示的都是可导出的模块结构字段,代表了客户端能够使用的几大核心模块,其具体功能介绍如下:

  • Cluster:向集群里增加 etcd 服务端节点之类,属于管理员操作。
  • KV:我们主要使用的功能,即操作 K-V。
  • Lease:租约相关操作,比如申请一个 TTL=10 秒的租约。
  • Watcher:观察订阅,从而监听最新的数据变化。
  • Auth:管理 etcd 的用户和权限,属于管理员操作。
  • Maintenance:维护 etcd,比如主动迁移 etcd 的 leader 节点,属于管理员操作。
proto3

etcd v3 的通信基于 gRPC,proto 文件是定义服务端和客户端通讯接口的标准。包括:

  • 客户端该传什么样的参数
  • 服务端该返回什么参数
  • 客户端该怎么调用
  • 是阻塞还是非阻塞
  • 是同步还是异步。

gRPC 推荐使用 proto3 消息格式,在进行核心 API 的学习之前,我们需要对 proto3 的基本语法有初步的了解。proto3 是原有 Protocol Buffer 2(被称为 proto2)的升级版本,删除了一部分特性,优化了对移动设备的支持。

gRPC 服务

发送到 etcd 服务器的每个 API 请求都是一个 gRPC 远程过程调用。etcd3 中的 RPC 接口定义根据功能分类到服务中。

处理 etcd 键值的重要服务包括:

  • KV 服务,创建、更新、获取和删除键值对。
  • 监视,监视键的更改。
  • 租约,消耗客户端保持活动消息的基元。
  • 锁,etcd 提供分布式共享锁的支持。
  • 选举,暴露客户端选举机制。
请求和响应

etcd3 中的所有 RPC 都遵循相同的格式。每个 RPC 都有一个函数名,该函数将 NameRequest 作为参数并返回 NameResponse 作为响应。例如,这是 Range RPC 描述:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
service KV {
  Range(RangeRequest) returns (RangeResponse)
  ...
}
响应头

etcd API 的所有响应都有一个附加的响应标头,其中包括响应的群集元数据:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
message ResponseHeader {
  uint64 cluster_id = 1;
  uint64 member_id = 2;
  int64 revision = 3;
  uint64 raft_term = 4;
}
  • Cluster_ID - 产生响应的集群的 ID。
  • Member_ID - 产生响应的成员的 ID。
  • Revision - 产生响应时键值存储的修订版本号。
  • Raft_Term - 产生响应时,成员的 Raft 称谓。

应用服务可以通过 Cluster_ID 和 Member_ID 字段来确保,当前与之通信的正是预期的那个集群或者成员。

应用服务可以使用修订号字段来知悉当前键值存储库最新的修订号。当应用程序指定历史修订版以进行时程查询并希望在请求时知道最新修订版时,此功能特别有用。

应用服务可以使用 Raft_Term 来检测集群何时完成一个新的 leader 选举。

下面开始介绍 etcd 中这几个重要的服务和接口。

KV 存储

kv 对象的实例获取通过如下的方式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
kv  := clientev3.NewKV(client)

我们来看一下 kv 接口的具体定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type KV interface {

    Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error)

    // 检索 keys.
    Get(ctx context.Context, key string, opts ...OpOption) (*GetResponse, error)

    // 删除 key,可以使用 WithRange(end), [key, end) 的方式
    Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error)

    // 压缩给定版本之前的 KV 历史
    Compact(ctx context.Context, rev int64, opts ...CompactOption) (*CompactResponse, error)

    // 指定某种没有事务的操作
    Do(ctx context.Context, op Op) (OpResponse, error)

    // Txn 创建一个事务
    Txn(ctx context.Context) Txn
}

从 KV 对象的定义我们可知,它就是一个接口对象,包含几个主要的 kv 操作方法:

kv 存储 put

put 的定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error)

其中的参数:

  • ctx: Context 包对象,是用来跟踪上下文的,比如超时控制
  • key: 存储对象的 key
  • val: 存储对象的 value
  • opts:  可变参数,额外选项

Put 将一个键值对放入 etcd 中。请注意,键值可以是纯字节数组,字符串是该字节数组的不可变表示形式。要获取字节字符串,请执行 string([] byte {0x10,0x20})

put 的使用方法如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
putResp, err := kv.Put(context.TODO(),"aa", "hello-world!")
kv 查询 get

现在可以对存储的数据进行取值了。默认情况下,Get 将返回 “ key” 对应的值。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Get(ctx context.Context, key string, opts ...OpOption) (*GetResponse, error)

OpOption 为可选的函数传参,传参为 WithRange(end) 时,Get 将返回 [key,end)范围内的键;传参为 WithFromKey() 时,Get 返回大于或等于 key 的键;当通过 rev> 0 传递 WithRev(rev) 时,Get 查询给定修订版本的键;如果压缩了所查找的修订版本,则返回请求失败,并显示 ErrCompacted。传递 WithLimit(limit) 时,返回的 key 数量受 limit 限制;传参为 WithSort 时,将对键进行排序。对应的使用方法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
getResp, err := kv.Get(context.TODO(), "aa")

从以上数据的存储和取值,我们知道 put 返回 PutResponse、get 返回 GetResponse,注意:不同的 KV 操作对应不同的 response 结构,定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type (
    CompactResponse pb.CompactionResponse
    PutResponse     pb.PutResponse
    GetResponse     pb.RangeResponse
    DeleteResponse  pb.DeleteRangeResponse
    TxnResponse     pb.TxnResponse
)

我们分别来看一看 PutResponse 和 GetResponse 映射的 RangeResponse 结构的定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type PutResponse struct {
    Header *ResponseHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"`
    // if prev_kv is set in the request, the previous key-value pair will be returned.
    PrevKv *mvccpb.KeyValue `protobuf:"bytes,2,opt,name=prev_kv,json=prevKv" json:"prev_kv,omitempty"`
}
//Header 里保存的主要是本次更新的 revision 信息

type RangeResponse struct {
    Header *ResponseHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"`
    // kvs is the list of key-value pairs matched by the range request.
    // kvs is empty when count is requested.
    Kvs []*mvccpb.KeyValue `protobuf:"bytes,2,rep,name=kvs" json:"kvs,omitempty"`
    // more indicates if there are more keys to return in the requested range.
    More bool `protobuf:"varint,3,opt,name=more,proto3" json:"more,omitempty"`
    // count is set to the number of keys within the range when requested.
    Count int64 `protobuf:"varint,4,opt,name=count,proto3" json:"count,omitempty"`
}

Kvs 字段,保存了本次 Get 查询到的所有 kv 对,我们继续看一下 mvccpb.KeyValue 对象的定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type KeyValue struct {

    Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
    // create_revision 是当前 key 的最后创建版本
    CreateRevision int64 `protobuf:"varint,2,opt,name=create_revision,json=createRevision,proto3" json:"create_revision,omitempty"`
    // mod_revision 是指当前 key 的最新修订版本
    ModRevision int64 `protobuf:"varint,3,opt,name=mod_revision,json=modRevision,proto3" json:"mod_revision,omitempty"`
    // key 的版本,每次更新都会增加版本号
    Version int64 `protobuf:"varint,4,opt,name=version,proto3" json:"version,omitempty"`

    Value []byte `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"`

    // 绑定了 key 的租期 Id,当 lease 为 0 ,则表明没有绑定 key;租期过期,则会删除 key
    Lease int64 `protobuf:"varint,6,opt,name=lease,proto3" json:"lease,omitempty"`
}

至于 RangeResponse.More 和 Count,当我们使用 withLimit() 选项进行 Get 时会发挥作用,相当于分页查询。

接下来,我们通过一个特别的 Get 选项,获取 aa 目录下的所有子目录:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
rangeResp, err := kv.Get(context.TODO(), "/aa", clientv3.WithPrefix())

WithPrefix() 用于查找以 /aa 为前缀的所有 key,因此可以模拟出查找子目录的效果。

我们知道 etcd 是一个有序的 kv 存储,因此 /aa 为前缀的 key 总是顺序排列在一起。

withPrefix 实际上会转化为范围查询,它根据前缀 /aa 生成了一个 key range,[“/aa/”, “/aa0”),这是因为比 / 大的字符是 0,所以以 /aa0 作为范围的末尾,就可以扫描到所有的 /aa/ 打头的 key 了。

KV 操作实践

键值对的操作是 etcd 中最基本、最常用的功能,主要包括读、写、删除三种基本的操作。在 etcd 中定义了 kv 接口,用来对外提供这些操作,下面我们进行具体的测试:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package client

import (
 "context"
 "fmt"
 "github.com/google/uuid"
 "go.etcd.io/etcd/clientv3"
 "testing"
 "time"
)

func TestKV(t *testing.T) {
 rootContext := context.Background()
 // 客户端初始化
 cli, err := clientv3.New(clientv3.Config{
  Endpoints:   []string{"localhost:2379"},
  DialTimeout: 2 * time.Second,
 })
 // etcd clientv3 >= v3.2.10, grpc/grpc-go >= v1.7.3
 if cli == nil || err == context.DeadlineExceeded {
  // handle errors
  fmt.Println(err)
  panic("invalid connection!")
 }
 // 客户端断开连接
 defer cli.Close()
 // 初始化 kv
 kvc := clientv3.NewKV(cli)
 //获取值
 ctx, cancelFunc := context.WithTimeout(rootContext, time.Duration(2)*time.Second)
 response, err := kvc.Get(ctx, "cc")
 cancelFunc()
 if err != nil {
  fmt.Println(err)
 }
 kvs := response.Kvs
 // 输出获取的 key
 if len(kvs) > 0 {
  fmt.Printf("last value is :%s\r\n", string(kvs[0].Value))
 } else {
  fmt.Printf("empty key for %s\n", "cc")
 }
 //设置值
 uuid := uuid.New().String()
 fmt.Printf("new value is :%s\r\n", uuid)
 ctx2, cancelFunc2 := context.WithTimeout(rootContext, time.Duration(2)*time.Second)
 _, err = kvc.Put(ctx2, "cc", uuid)
 // 设置成功之后,将该 key 对应的键值删除
 if delRes, err := kvc.Delete(ctx2, "cc"); err != nil {
  fmt.Println(err)
 } else {
  fmt.Printf("delete %s for %t\n", "cc", delRes.Deleted > 0)
 }
 cancelFunc2()
 if err != nil {
  fmt.Println(err)
 }
}

如上的测试用例,主要是针对 kv 的操作,依次获取 key,即 Get(),对应 etcd 底层实现的 range 接口;其次是写入键值对,即 put 操作;最后删除刚刚写入的键值对。预期的执行结果如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
=== RUN   Test
empty key for cc
new value is: 41e1362a-28a7-4ac9-abf5-fe1474d93f84
delete cc for true
--- PASS: Test (0.11s)
PASS

可以看到,刚开始 etcd 并没有存储键 cc 的值,随后写入新的键值对并测试将其删除。

其他通信接口

其他常用的接口还有 Transaction、Compact、watch、Lease、Lock 等。我们依次看看这些接口的定义。

事务 Transaction

Txn 方法在单个事务中处理多个请求。txn 请求增加键值存储的修订版本并为每个完成的请求生成带有相同修订版本的事件。etcd 不容许在一个 txn 中多次修改同一个 key。Txn 接口定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
rpc Txn(TxnRequest) returns (TxnResponse) {}
Compact 方法

Compact 方法压缩 etcd 键值对存储中的事件历史。键值对存储应该定期压缩,否则事件历史会无限制的持续增长。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
rpc Compact(CompactionRequest) returns (CompactionResponse) {}

请求的消息体是 CompactionRequest, CompactionRequest 压缩键值对存储到给定修订版本。所有修订版本比压缩修订版本小的键都将被删除

watch

Watch API 提供了一个基于事件的接口,用于异步监视键的更改。etcd3 监视程序通过从给定的修订版本(当前版本或历史版本)持续监视 key 更改,并将 key 更新流回客户端。

在 rpc.proto 中 Watch service 定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
service Watch {
  rpc Watch(stream WatchRequest) returns (stream WatchResponse) {}
}

Watch 观察将要发生或者已经发生的事件。输入和输出都是流;输入流用于创建和取消观察,而输出流发送事件。一个观察 RPC 可以在一次性在多个 key 范围上观察,并为多个观察流化事件。整个事件历史可以从最后压缩修订版本开始观察。WatchService 只有一个 Watch 方法。

Lease service

Lease service 提供租约的支持。Lease 是一种检测客户端存活状况的机制。群集授予具有生存时间的租约。如果 etcd 群集在给定的 TTL 时间内未收到 keepAlive,则租约到期。

为了将租约绑定到键值存储中,每个 key 最多可以附加一个租约。当租约到期或被撤销时,该租约所附的所有 key 都将被删除。每个过期的密钥都会在事件历史记录中生成一个删除事件。

在 rpc.proto 中 Lease service 定义的接口如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
service Lease {

  rpc LeaseGrant(LeaseGrantRequest) returns (LeaseGrantResponse) {}

  rpc LeaseRevoke(LeaseRevokeRequest) returns (LeaseRevokeResponse) {}

  rpc LeaseKeepAlive(stream LeaseKeepAliveRequest) returns (stream LeaseKeepAliveResponse) {}

  rpc LeaseTimeToLive(LeaseTimeToLiveRequest) returns (LeaseTimeToLiveResponse) {}
}
  • LeaseGrant 创建一个租约
  • LeaseRevoke 撤销一个租约
  • LeaseKeepAlive 用于维持租约
  • LeaseTimeToLive 获取租约信息
Lock service

Lock service 提供分布式共享锁的支持。Lock service 以 gRPC 接口的方式暴露客户端锁机制。在 v3lock.proto 中 Lock service 定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
service Lock {

  rpc Lock(LockRequest) returns (LockResponse) {}

  rpc Unlock(UnlockRequest) returns (UnlockResponse) {}
}
  • Lock 方法,在给定命令锁上获得分布式共享锁。
  • Unlock 使用 Lock 返回的 key 并释放对锁的持有。

小结

这篇文章主要介绍了 etcd 的 gRPC 通信接口以及 clientv3 客户端的实践,主要包括键值对操作(增删改查)、watch、Lease、锁和 Compact 等接口。通过对客户端 API 通信接口的学习,了解 etcd 客户端的使用以及常用功能的接口定义,对于我们在日常工作中能够得心应手的使用 etcd 实现相应的功能能够很有帮助。

了解更多关于 etcd 的原理与实践,欢迎支持我的新书《etcd工作笔记:架构分析、优化与最佳实践》,现已完成印刷,即将登陆各大网上商城。我已经拿到了样书,就是文章的封面图。过几天搞活动会赠送几本关注我的读者。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-10-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 aoho求索 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
九种方式,教你读取 resources 目录下的文件路径
点击上方“芋道源码”,选择“设为星标” 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 10:33 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | Java 2021 超神之路,很肝~ 中文详细注释的开源项目 RPC 框架 Dubbo 源码解析 网络应用框架 Netty 源码解析 消息中间件 RocketMQ 源码解析 数据库中间件 Sharding-JDBC 和 MyCAT 源码解析 作业调度中间件 Elastic-Job 源码解析 分布式事务中间件 TCC-Transaction
芋道源码
2022/08/29
2K0
九种方式,教你读取 resources 目录下的文件路径
Java获取/resources目录下的资源文件方法
Web项目开发中,经常会有一些静态资源,被放置在resources目录下,随项目打包在一起,代码中要使用的时候,通过文件读取的方式,加载并使用;
军军不吃鸡
2022/11/14
1.9K0
java获取 /resources 目录资源文件的 6 种方法
公用的打印文件方法 /** * 根据文件路径读取文件内容 * * @param fileInPath * @throws IOException */ public static void getFileContent(Object fileInPath) throws IOException { BufferedReader br = null; if (fileInPath == null) { return; } if (fileInPath
adu
2022/10/30
21.2K0
【SpringBoot】四种读取 Spring Boot 项目中 jar 包中的 resources 目录下的文件
在SpringBoot应用中,经常需要读取打包在jar包中的资源文件,比如配置文件、模板文件等。这些资源文件通常放在src/main/resources目录下,在打包成jar包后,它们会被存储在jar包的根目录下。本文将介绍4种在SpringBoot中读取这些资源文件的方法。
程序员洲洲
2024/06/07
7.1K0
【SpringBoot】四种读取 Spring Boot 项目中 jar 包中的 resources 目录下的文件
【小家Spring】资源访问利器---Spring提供的Resource接口以及它的常用子类源码分析
资源是一个抽象的概念,什么是资源?我们已知Spring中有很多xml配置文件,同时还可能自建各种properties资源文件,还有可能进行网络交互,收发各种文件、二进制流等。
YourBatman
2019/09/03
1.6K0
【小家Spring】资源访问利器---Spring提供的Resource接口以及它的常用子类源码分析
Java文件路径/服务器路径的获取
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/157583.html原文链接:https://javaforall.cn
全栈程序员站长
2022/09/14
5.1K0
springboot读取resources文件夹下的文件
只是适合打成war下使用的,有一些在eclipse或者Idea下使用时正常的,但是一打成jar就会出现FileNotFoundException 了。比如:在开发中,我们需要获取类路径下的某个资源文件,一般我们都会使用ResourceUtils工具类,快捷方便,但是在打包的时候,会出现一些异常 解决方案也很简单,换一个工具类就可以了:
似水的流年
2019/12/05
9K0
php getrealpath,java_java 获取路径的各种方法(总结),(1)、request.getRealPath(“/”);//不推 – phpStudy…
(1)、request.getRealPath(“/”);//不推荐使用获取工程的根路径
全栈程序员站长
2022/11/04
6410
Java-利用Spring提供的Resource/ResourceLoader接口操作资源文件
JDK提供的访问资源的类(如java.net.URL、File等)并不能很好地满足各种底层资源的访问需求,比如缺少从类路径或者Web容器上下文中获取资源的操作类。
小小工匠
2021/08/16
1.8K0
spring 之资源操作:Resources
在 Java 编程中,java.net.URL 类常用于进行资源操作。然而,这个类在访问某些底层资源时存在局限性。例如,它不能直接从类路径中获取资源,或者在 Web 项目中无法方便地访问相对于服务器上下文的资源。此外,java.net.URL 在功能方面也有所欠缺,比如无法检测某个资源是否存在。
叫我阿杰好了
2024/01/03
2760
spring 之资源操作:Resources
Spring 源码第一篇开整!配置文件是怎么加载的?
松哥给最近连载的 Spring Security 系列也录制了视频教程,感兴趣的小伙伴请戳这里->Spring Boot+Vue+微人事视频教程(Spring Boot 第十章就是 Spring Security)。
江南一点雨
2020/06/17
5080
[Java拾遗三]JavaWeb基础之Servlet
Servlet 1,servlet介绍         servlet是一项动态web资源开发技术.         运行在服务器端.         作用:处理业务逻辑,生成动态的内容,返回给浏览器.         本质就是一个类      servlet的入门         1.编写servlet(类)--- 继承HttpServlet         2.编写关系--- web.xml(在WEB-INF下)         3.访问:             路径:http://localho
一枝花算不算浪漫
2018/05/18
7330
项目打包成 jar 后包无法读取src/main/resources下文件
该代码功能是利用 common.io 包下的FileUtils来读取文件, 放到一个字符串中
时间静止不是简史
2022/05/06
14.8K0
项目打包成 jar 后包无法读取src/main/resources下文件
MySQL---数据库从入门走向大神系列(九)-用Java向数据库读写大文本/二进制文件数据
配置文件在前面这篇博客说明: http://blog.csdn.net/qq_26525215/article/details/52153452
谙忆
2021/01/21
8450
MySQL---数据库从入门走向大神系列(九)-用Java向数据库读写大文本/二进制文件数据
Spring Resources资源操作
Java的标准java.net.URL类和各种URL前缀的标准处理程序无法满足所有对low-level资源的访问,比如:没有标准化的 URL 实现可用于访问需要从类路径或相对于 ServletContext 获取的资源。并且缺少某些Spring所需要的功能,例如检测某资源是否存在等。而Spring的Resource声明了访问low-level资源的能力。
鱼找水需要时间
2023/06/15
3330
Spring Resources资源操作
读取properties文件的6种方式,建议收藏!
这年头基本上都是使用Spring Boot开发,然后都知道在项目中会有个application.properties配置文件(也有的是application.yaml,反正就是用来保存我们的一些配置信息),通常我们会把一些配置信息写到properties文件中,比如:数据库连接信息、第三方接口信息(密钥、用户名、密码、地址等),连接池、Redis配置信息、各种第三方组件配置信息等。
田维常
2023/08/31
3.4K0
读取properties文件的6种方式,建议收藏!
用Spring的这个类来读取配置文件真的是赞
在开发中读取项目中的配置或者静态文件是家常便饭的事情,我相信很多同学都从网上找下面的例子来进行文件加载读取操作。
码农小胖哥
2021/01/04
6920
用Spring的这个类来读取配置文件真的是赞
Java中文件路径及其访问
1. URI形式的绝对资源路径:例如:file:/E:/EclipseWorkSpace/JavaTest/bin/images/me.jpg。URL是URI的特例,URL的前缀必须是Java认识的。URL可以打开资源而URI不行。URL和URI可以通过各自的toURI()和toURL()方法进行转换。
卡尔曼和玻尔兹曼谁曼
2019/01/25
4.6K0
【Java】解决Java报错:FileNotFoundException
在Java编程中,FileNotFoundException 是一种常见的受检异常,通常发生在试图打开一个不存在的文件或文件路径错误时。这类错误提示为:“FileNotFoundException: [file path] (No such file or directory)”,意味着程序无法找到指定的文件。本文将详细探讨FileNotFoundException的成因、解决方案以及预防措施,帮助开发者理解和避免此类问题,从而提高代码的健壮性和可靠性。
E绵绵
2024/06/12
7240
相对路径和绝对路径的区别
绝对路径:绝对路径就是你的主页上的文件或目录在硬盘上真正的路径,(URL和物理路径)例如: C:\xyz\test.txt 代表了test.txt文件的绝对路径。http://www.sun.com/index.htm也代表了一个 URL绝对路径。 相对路径:相对与某个基准目录的路径。包含Web的相对路径(HTML中的相对目录),例如:在 Servlet中,"/"代表Web应用的根目录。和物理路径的相对表示,例如:"./" 代表当前目录, "../"代表上级目录。这种类似的表示,也是属于相对路径。
狼啸风云
2020/07/06
6.8K0
推荐阅读
相关推荐
九种方式,教你读取 resources 目录下的文件路径
更多 >
LV.1
马上科普教育科技有限公司
目录
  • 你好,我是 aoho,今天我和你分享的是通信接口:客户端 API 实践与核心方法。
    • etcd clientv3 客户端
    • etcd 客户端初始化
    • client 定义
      • proto3
      • gRPC 服务
      • 请求和响应
      • 响应头
    • KV 存储
      • kv 存储 put
      • kv 查询 get
      • KV 操作实践
    • 其他通信接口
      • 事务 Transaction
      • Compact 方法
      • watch
      • Lease service
      • Lock service
    • 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档