11面向接口编程(下):一切皆服务,服务基于协议
服务容器的实现
一个服务容器主要的功能是:为服务提供注册绑定、提供获取服务实例,所以服务容器至少有两个方法:注册方法 Bind、获取实例方法 Make。
- 对于注册的方法,直接将一个服务提供者注册到容器中,参数是之前定义的服务提供者,返回值则是 error 是否注册成功。
// Bind 绑定一个服务提供者,如果关键字凭证已经存在,会进行替换操作,不返回 error
Bind(provider ServiceProvider) error
- 获取实例的方法是 Make,它会根据一个关键字凭证,来获取容器中已经实例化好的服务。所以参数是一个关键字凭证 string,返回值是实例化好的服务 interface 和是否有错误的 error 信息。
// Make 根据关键字凭证获取一个服务,
Make(key string) (interface{}, error)
除了这两个,再考虑在注册绑定及获取服务实例过程中,有什么方面可以扩展。
- 是否已绑定: IsBind, 参数为关键字凭证,返回为 bool 表示是否已经绑定
- 不关心Make方法的error: MustMake, 它的参数和 Make 方法一样,为关键字凭证,返回值为实例化服务,但是不返回 error
- 根据参数获取不同初始化实例: MakeNew
// Container 是一个服务容器,提供绑定服务和获取服务的功能
type Container interface {// Bind 绑定一个服务提供者,如果关键字凭证已经存在,会进行替换操作,返回 errorBind(provider ServiceProvider) error// IsBind 关键字凭证是否已经绑定服务提供者IsBind(key string) bool// Make 根据关键字凭证获取一个服务,Make(key string) (interface{}, error)// MustMake 根据关键字凭证获取一个服务,如果这个关键字凭证未绑定服务提供者,那么会 panic。// 所以在使用这个接口的时候请保证服务容器已经为这个关键字凭证绑定了服务提供者。MustMake(key string) interface{}// MakeNew 根据关键字凭证获取一个服务,只是这个服务并不是单例模式的// 它是根据服务提供者注册的启动函数和传递的 params 参数实例化出来的// 这个函数在需要为不同参数启动不同实例的时候非常有用MakeNew(key string, params []interface{}) (interface{}, error)
}
具体实现
在 framework/container.go 中 定义HadeContainer, 表示容器的具体实现:
// HadeContainer 是服务容器的具体实现
type HadeContainer struct {Container // 强制要求 HadeContainer 实现 Container 接口// providers 存储注册的服务提供者,key 为字符串凭证providers map[string]ServiceProvider// instance 存储具体的实例,key 为字符串凭证instances map[string]interface{}// lock 用于锁住对容器的变更操作lock sync.RWMutex
}
// Bind 将服务容器和关键字做了绑定
func (hade *HadeContainer) Bind(provider ServiceProvider) error {hade.lock.Lock()defer hade.lock.Unlock()key := provider.Name()hade.providers[key] = provider// if provider is not deferif provider.IsDefer() == false {if err := provider.Boot(hade); err != nil {return err}// 实例化方法params := provider.Params(hade)method := provider.Register(hade)instance, err := method(params...)if err != nil {return errors.New(err.Error())}hade.instances[key] = instance}return nil
}// Make 方式调用内部的 make 实现
func (hade *HadeContainer) Make(key string) (interface{}, error) {return hade.make(key, nil, false)
}// MakeNew 方式使用内部的 make 初始化
func (hade *HadeContainer) MakeNew(key string, params []interface{}) (interface{}, error) {return hade.make(key, params, true)
}// 真正的实例化一个服务
func (hade *HadeContainer) make(key string, params []interface{}, forceNew bool) (interface{}, error) {hade.lock.RLock()defer hade.lock.RUnlock()// 查询是否已经注册了这个服务提供者,如果没有注册,则返回错误sp := hade.findServiceProvider(key)if sp == nil {return nil, errors.New("contract " + key + " have not register")}if forceNew {return hade.newInstance(sp, params)}// 不需要强制重新实例化,如果容器中已经实例化了,那么就直接使用容器中的实例if ins, ok := hade.instances[key]; ok {return ins, nil}// 容器中还未实例化,则进行一次实例化inst, err := hade.newInstance(sp, nil)if err != nil {return nil, err}hade.instances[key] = instreturn inst, nil
}
容器和框架的结合
hade 框架中最核心的两个数据结构 Engine 和 Context。
- Engine 就是 Core 数据结构,这个数据结构是整个框架的入口,也承担了整个框架最核心的路由、中间件等部分。
- Context 对应封装request和response的ctx,它为每个请求创建一个 Context,其中封装了各种对请求操作的方法。
对应来看我们的服务容器,它提供了两类方法,绑定操作和获取操作。绑定操作是全局的操作,而获取操作是在单个请求中使用的。所以在全局,我们为服务容器绑定了服务提供方,就能在单个请求中获取这个服务。
那么对应到框架中,可以将服务容器存放在 Engine 中,并且在 Engine 初始化 Context 的时候,将服务容器传递进入 Context。
- 修改framework/gin/gin.go中Engine 的数据结构, 添加container字段
type Engine struct {// 容器container framework.Container...
}func New() *Engine {debugPrintWARNINGNew()engine := &Engine{...// 这里注入了 containercontainer: framework.NewHadeContainer(),...}...return engine
}
- 在 Engine 创建 context 的时候, 将容器注入到ctx中
// engine 创建 context
func (engine *Engine) allocateContext() *Context {v := make(Params, 0, engine.maxParams)// 在分配新的 Context 的时候,注入了 containerreturn &Context{engine: engine, params: &v, container: engine.container}
}
这样就完成了服务容器的创建和传递,接下来完成服务容器方法的封装。
根据上面描述的,Engine 中负责绑定,Context 中负责获取,所以我们将 container 的五个能力拆分到 Engine 和 Context 数据结构中。Engine 封装 Bind 和 IsBind 方法,Context 封装 Make、MakeNew、MustMake 方法。
将这些为 Engine 和 Context 增加的新的方法单独存放在一个新的文件 framework/gin/hade_context.go 中。
// engine 实现 container 的绑定封装
func (engine *Engine) Bind(provider framework.ServiceProvider) error {return engine.container.Bind(provider)
}// IsBind 关键字凭证是否已经绑定服务提供者
func (engine *Engine) IsBind(key string) bool {return engine.container.IsBind(key)
}// context 实现 container 的几个封装
// 实现 make 的封装
func (ctx *Context) Make(key string) (interface{}, error) {return ctx.container.Make(key)
}// 实现 mustMake 的封装
func (ctx *Context) MustMake(key string) interface{} {return ctx.container.MustMake(key)
}// 实现 makenew 的封装
func (ctx *Context) MakeNew(key string, params []interface{}) (interface{}, error) {return ctx.container.MakeNew(key, params)
}
创建一个服务提供方
下面我们来创建一个服务 DemoService,为这个服务创建一个服务提供方 DemoServiceProvider,并注入到服务容器中。在业务目录中创建一个目录 provider/demo 存放这个服务。
先搞清楚需要为这个服务设计几个文件。
- 要有一个服务接口文件 contract.go,存放服务的接口文件和服务凭证。
- 需要设计一个 provider.go,这个文件存放服务提供方 ServiceProvider 的实现。
- 最后在 service.go 文件中实现具体的服务实例。
如何通过服务提供方创建服务
这里实现非常简单,我们需要做两个操作,绑定服务提供方、获取服务。
首先是在业务文件夹的 main.go 中绑定操作,在 main 函数中,完成 engine 的创建之后,用在 engine 中封装的 Bind 方法做一次绑定操作。
func main() {// 创建 engine 结构core := gin.New()// 绑定具体的服务core.Bind(&demo.DemoServiceProvider{})...
}
然后就是服务的获取了。在具体的业务逻辑控制器中,我们选择路由 /subject/list/all 对应的控制器 SubjectListController,使用为 context 封装的 MustMake 方法来获取 demo 服务实例。
MustMake 的参数为 demo 的服务凭证 demo.Key,返回的是一个 interface 结构,这个 interface 结构实际上是实现了 demo.Service 接口的一个服务实例。
而在接口的具体输出中,输出的是这个接口定义的 GetFoo() 方法的输出,也就是最终会从服务容器中获取到 DemoService 的 GetFoo() 方法的返回值 Foo 结构,带有字段 Name: “i am foo”输出在页面上。
// 对应路由 /subject/list/all
func SubjectListController(c *gin.Context) {// 获取 demo 服务实例demoService := c.MustMake(demo.Key).(demo.Service)// 调用服务实例的方法foo := demoService.GetFoo()// 输出结果c.ISetOkStatus().IJson(foo)
}
最后验证一下,在浏览器中,我们访问这个路由 /subject/list/all,获取到了 Foo 数据结构 Json 化出来的结果。
【小结】
- 将框架作为一个容器,服务注入到框架的engine和ctx中
- 注册时为全局,因此用engine
- 使用服务时为请求独自使用,因此用ctx来获取服务
- 注意获取服务时是否有并发竞争问题
相关文章:
11面向接口编程(下):一切皆服务,服务基于协议
服务容器的实现 一个服务容器主要的功能是:为服务提供注册绑定、提供获取服务实例,所以服务容器至少有两个方法:注册方法 Bind、获取实例方法 Make。 对于注册的方法,直接将一个服务提供者注册到容器中,参数是之前定…...
不要以没时间来说测试用例写不好
工作当中,总会有人为自己的测试用例写得不够好去找各种理由,时间不够是我印象当中涉及到最多的,也是最反感。想写好测试用例,前提是测试分析和需求拆解做的足够好,通过xmind或者UML图把需求和开发设计提供的产品信息提炼出来。 我个人的提炼标准一般是&…...
day57-day58【代码随想录】二刷数组
文章目录前言一、螺旋矩阵||(力扣59)二、螺旋矩阵(力扣54)三、顺时针打印矩阵(剑指 Offer29)四、在排序数组中查找元素的第一个和最后一个位置(力扣34)【二分查找】五、有多少小于当…...
【NLP】自动化计算文本文件TTR的bash脚本
自动化计算文本文件TTR的bash脚本 简介 这是一个可以计算文本文件TTR的bash脚本,文件名为:calculate_TTR.sh。它会接收一个文件名作为参数,并输出总单词数、特异单词数和TTR。 TTR是什么 TTR(Type-Token Ratio)是用…...
蓝桥杯单片机组省赛十二届第一场(关于矩阵,温度ds18b20,时间ds1302的学习,以及继电器等外设的综合利用)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录一、该题目如下二、使用步骤1.矩阵键盘实现2.温度传感器ds18b20的实现总结提示:以下是本篇文章正文内容,下面案例可供参考 一、该题目如下 分…...
Ubuntu 新人上手 Microk8s 指南
文章目录1. 什么是 Ubuntu 核心2. 什么是 Kubernetes3. 什么是MicroK8s4. 为什么选择 Microk8s on Core5. 安装Ubuntu Core6. Ubuntu Core上安装 MicroK8S7. 启动 Microk8s8. 启用必要的 MicroK8s 插件9. 部署示例容器工作负载10. 检查部署状态并访问您的应用程序11. 管理镜像1…...
初阶C语言——实用调试技巧【详解】
文章目录1. 什么是bug?2. 调试是什么?有多重要?2.1 调试是什么?2.2 调试的基本步骤2.3 Debug和Release的介绍3.学会使用快捷键4.调试的时候查看程序当前信息4.1 查看临时变量的值4.2 查看内存信息4.3 查看调用堆栈4.4 查看汇编信息…...
Android 绘图基础:Canvas画布——自定义View基础(绘制表盘、矩形、圆形、弧、渐变)
Canvas画布,通过它我们可以自定义一个View,设置View的相关效果之类的。感觉用法差不多,重要的是要理解方法中传入的参数的含义,比如float类型的参数,传递的是坐标,已开是没有注意传入的参数时坐标,导致我迷…...
js拷贝数组对象:浅拷贝深拷贝
前言 js拷贝数组对象:浅拷贝&深拷贝,包括:Object.assign、concat、slice、JSON.parse(JSON.stringify()) 场景:弹窗选择组织结构(树形结构),选择后显示相关数据至输入框中(每次选…...
【C++】string类的使用
目录 一、标准库中的string类 二、string类的常用接口 1、string类对象的常见构造 2、string类对象的容量操作 2.1、size 与 length 2.2、capacity 与 reserve 2.3、resize 2.4、总结 3、string类对象的访问及遍历操作 3.1、operator[] 与 at 3.2、begin end 3.3、…...
微服务架构简介
微服务 软件架构是一个包含各种组织的系统组织,这些组件包括 Web服务器, 应用服务器, 数据库,存储, 通讯层), 它们彼此或和环境存在关系。系统架构的目标是解决利益相关者的关注点。 image Conway’s law: Organizations which design systems[...] are constrained…...
【Spring源码】AOP的开端:核心对象创建的准备工作
AOP的核心成员是如何被被加载的?本篇我们主要分析使用xml的逻辑,如果使用注解,增加注解处理类即可(ConfigurationClassPostProcessor)拿之前分析循环的时候举的例子🌰,它的日志切面就是通过xml进…...
新号涨粉22w,搞笑博主再次爆火,小红书近期创作趋势是什么?
2月借势元宵、情人节,小红书平台又涌现出哪些黑马博主?品牌在投放种草方面有何亮眼表现?为洞察小红书平台的内容创作趋势及品牌营销策略,新红推出2月月度榜单,从创作者及品牌两方面入手,解析月榜数据&#…...
【C++】30h速成C++从入门到精通(内存管理、函数/类模板)
C内存分布我们先来看一下下面的一段代码相关问题int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] {1, 2, 3, 4};char char2[] "abcd";char* pChar3 "abcd";int* ptr1 (int*)mal…...
自动驾驶决策概况
文章目录1. 第一章行为决策在自动驾驶系统架构中的位置2. 行为决策算法的种类2.1 基于规则的决策算法2.1.1 决策树2.1.2 有限状态机(FSM)2.1.3 基于本体论(Ontologies-based)2.2 基于统计的决策算法2.2.1 贝叶斯网络(B…...
金山轻维表项目进展自动通知
项目经理作为项目全局把控者,经常要和时间“赛跑”。需要实时了解到目前进展如何,跟进人是那些?哪些事项还未完成?项目整体会不会逾期?特别是在一些大型公司中,优秀的项目经理已经学会使用金山轻维表做项目…...
基于上下文分析的 Python 实时 API 推荐
原文来自微信公众号“编程语言Lab”:基于上下文分析的 Python 实时 API 推荐 搜索关注 “编程语言Lab”公众号(HW-PLLab)获取更多技术内容! 欢迎加入 编程语言社区 SIG-程序分析 参与交流讨论(加入方式:添加…...
软件测试-接口测试-代码实现接口测试
文章目录 1.request1.1 request介绍1.2 发送get请求1.3 发送set请求1.4 其他请求方式1.5 传递url参数1.6 响应内容解析1.7 cookie1.8 设置session2.集成UnitTest2.1 接口测试框架开发2.2 案例:使用TPShop项目完成对登录功能的接口测试1.request 1.1 request介绍 概念 基于py…...
中村成洋《垃圾回收的算法与实现》PDF 读书笔记
观前提醒 为了能够锻炼自己,我会查阅大量外文不停的修改内容,少部分会提示成中文。 可能有误,请见谅 提示:若是觉得阅读困难,可以看如下内容 脚本之家可获取,若失效可私信浏览器的沙拉查词扩展…...
docker 网络模式
docker 网络模式主要分为四种,可以通过docker network ls 查看 ~$ docker network ls NETWORK ID NAME DRIVER SCOPE a51d97d72f10 bridge br…...
数据库开发(一文概括mysql基本知识)
Mysql 是最流行的关系型数据库管理系统,在 WEB 应用方面 MySQL 是最好的 关系型数据库(Relational Database Management System:关系数据库管理系统)应用软件之一。mysql在问开发中,几乎必不可少,因为其他的可能是要收费的&#x…...
【JVM】详解Java内存区域和分配
这里写目录标题一、前言二、运行时数据分区2.1程序计数器(PC)2.2 Java虚拟机栈2.3 本地方法栈2.4 Java堆2.5 方法区2.5.1 运行时常量池2.6 直接内存三、HotSpot虚拟机对象探秘3.1 对象的创建3.2 对象的内存布局3.3 对象的访问定位一、前言 C/C需要自行回收和释放已经没用的对象…...
JAVA开发(史上最完整追本溯源JAVA历史、发展和学习)
(第二次世界大战1931-1945) 世界上最先进的技术往往是由于战争催生,在第二次世界大战中除了飞机,坦克和大炮的武器较量外,在隐秘战线的情报工作其实更为重要,在军队将领来往的电报中,为了防止军事情报的泄漏ÿ…...
Qt 防止程序退出
文章目录摘要QWidgetQML方法 1方法 2关键字: Qt、 eventFilter、 Close、 键盘、 任务管理器摘要 今天要聊得内容还是怎么防止别人关闭我的程序,之前都是在win下面,一般都是用过钩子连捕获键盘事件,完了吧对应的事件忽略&#x…...
【校验码 - 循环冗余校验码CRC】
水善利万物而不争,处众人之所恶,故几于道💦 目录 循环冗余校验码 1.多项式 2.CRC编码的组成 3.校验码的生成 4.例题: 循环冗余校验码 广泛地在网络通信及磁盘存储时采用。 1.多项式 在循环冗余校验(CRC)码中,无一例…...
【Rust】一文讲透Rust中的PartialEq和Eq
前言 本文将围绕对象:PartialEq和Eq,以及PartialOrd和Ord,即四个Rust中重点的Compare Trait进行讨论并解释其中的细节,内容涵盖理论以及代码实现。 在正式介绍PartialEq和Eq、以及PartialOrd和Ord之前,本文会首先介绍…...
Vulnhub靶场----9、DC-9
文章目录一、环境搭建二、渗透流程三、思路总结一、环境搭建 DC-9下载地址:https://download.vulnhub.com/dc/DC-9.zip kali:192.168.144.148 DC-9:192.168.144.158 二、渗透流程 1、信息收集nmap -T5 -A -p- -sV -sT 192.168.144.158思路&am…...
使用Containerd搭建K8s集群【v1.25】
[toc] 一、安装要求 在开始之前,部署Kubernetes集群机器需要满足以下几个条件: 一台或多台机器,操作系统 CentOS7.x-86_x64硬件配置:2GB或更多RAM,2个CPU或更多CPU,硬盘30GB或更多集群中所有机器之间网络互通可以访问外网,需要拉取镜像禁止swap分区二、准备环境 角色IP…...
NMT - 构建双语概率词典(Probabilistic dictionaries)
文章目录一、安装依赖包mosesdecoder安装 mgiza二、数据预处理三、训练本文参考:How to train your Bicleaner https://github.com/bitextor/bicleaner/wiki/How-to-train-your-Bicleaner 一、安装依赖包 这个过程主要依赖于 mosesdecodermgiza mosesdecoder git…...
《ChatGPT是怎样炼成的》
ChatGPT 在全世界范围内风靡一时,我现在每天都会使用 ChatGPT 帮我回答几个问题,甚至有的时候在一天内我和它对话的时间比和正常人类对话还要多,因为它确实“法力无边,功能强大”。 ChatGPT 可以帮助我解读程序,做翻译…...
织梦做的网站打包在dw修改/2022最火营销方案
大小尾数 小尾数:低位字节存储在低内存位置或寄存器的低位地址,高位字节存储在高内存位置或寄存器的高位地址。 大尾数:和小尾数的存储顺序是相反的,低位字节存储在高位地址。 intel采用的是小尾数存储格式: 检测&…...
中职网站建设与维护考试题/网站宣传费用
首先从TidMessage中获得邮件的头信息: strHeader:aIdMessage.Headers.text; 然后,用正则表达式取出Received: vReceiveIP:GetNeedStrByPerlReg(strHeader,(Received:)(.)(])); 再取出X-Originating-IP: vOriIP:GetNeedStrByPerlReg(strHea…...
北京交通管制信息网站/深圳网络推广平台
目录1、引入依赖2、获取方法1、引入依赖 <!-- 获取客户端信息 --> <!-- https://mvnrepository.com/artifact/eu.bitwalker/UserAgentUtils --> <dependency><groupId>eu.bitwalker</groupId><artifactId>UserAgentUtils</artifactId&…...
网站改版 权重/创建网址链接
package com.kk.innerClass;/*** 通过内部类实现接口* 解决多个接口中方法重名问题**/interface Machine {void run();}class Person {void run() {System.out.println("person start");}}public class Android extends Person {private class MachineHeart implemen…...
seo是做网站源码还是什么/怎么做公司网站推广
mongod会启动一个非常基本的HTTP服务器,监听数字比端口号高1000的端口,也就是28017端口。通过浏览器访问http://localhost:28017,能够获取数据库的管理信息。 mongodb shell会连接到服务器的test数据库,并将数据库连接赋值给全局…...
深圳宝安网站建设/网站引流推广软件
随着近几年蓝牙耳机的发展,不少人会在外出时选择佩戴蓝牙耳机,一是为了方便,二也是因为蓝牙耳机的性能越来越丰富。2023年了,有没有便宜好用的蓝牙耳机品牌呢?在此,我来给大家推荐几款性价比高的蓝牙耳机&a…...