共计 9211 个字符,预计需要花费 24 分钟才能阅读完成。
前言
嗨,我小 asong 又回来了。托了两周没有更新,最近比较忙,再加上本人懒,所以嘛,嗯嗯,你们懂的。不过我明天的带来的分享,相对干货,在理论我的项目中开发也是须要用到的,所以为了可能讲明确,我特意写了一个样例,仅供参考。本文会围绕样例进行开展学习,已上传 github,可自行下载。好了,不说废话了,晓得你们急不可待了,咱们间接开始吧!!!
wire
依赖注入
在介绍 wire 之前,咱们先来理解一下什么是依赖注入。应用过 Spring 的同学对这个应该不会生疏。其中管制反转(IOC)最常见的形式就叫做依赖注入。将依赖的类作为行参放入依赖中的类就成为依赖注入。这么说可能你们不太懂。用一句大白话来说,一个实例化的对象,原本我承受各种参数来结构一个对象,当初只承受一个参数,对对象的依赖是注入进来的,和它的结构形式解耦了。结构他这个管制操作也交给了第三方,即管制反转。举个例子:go 中是没有类的概念的,以构造体的模式体现。假如咱们当初船类,有个浆类,咱们当初想要设置该船有 12 个浆,那咱们能够写出如下代码:
package mainimport ("fmt")type ship struct {pulp *pulp}func NewShip(pulp *pulp) *ship{return &ship{ pulp: pulp,}}type pulp struct {count int}func Newpulp(count int) *pulp{return &pulp{ count: count,}}func main(){ p:= Newpulp(12) s := NewShip(p) fmt.Println(s.pulp.count)}
置信你们一眼就看出问题了,每当需要变动时,咱们都要从新创立一个对象来指定船桨,这样的代码不易保护,咱们变通一下。
package mainimport ("fmt")type ship struct {pulp *pulp}func NewShip(pulp *pulp) *ship{return &ship{ pulp: pulp,}}type pulp struct {count int}func Newpulp() *pulp{ return &pulp{}}func (c *pulp)set(count int) {c.count = count}func (c *pulp)get() int { return c.count}func main(){ p:= Newpulp() s := NewShip(p) s.pulp.set(12) fmt.Println(s.pulp.get())}
这个代码的益处就在于代码松耦合,易保护,还易测试。如果咱们当初更换需要了,须要 20 个船桨,间接 s.pulp.set(20)
就能够了。
wire 的应用
wire
有两个根底概念,Provider
(结构器)和 Injector
(注入器)。Provider
实际上就是创立函数,大家意会一下。咱们下面 InitializeCron
就是 Injector
。每个注入器实际上就是一个对象的创立和初始化函数。在这个函数中,咱们只须要通知wire
要创立什么类型的对象,这个类型的依赖,wire
工具会为咱们生成一个函数实现对象的创立和初始化工作。
拉了这么长,就是为了引出 wire,下面的代码尽管是实现了依赖注入,这是在代码量少,构造不简单的状况下,咱们本人来实现依赖是没有问题的,当构造之间的关系变得非常复杂的时候,这时候手动创立依赖,而后将他们组装起来就会变的异样繁琐,并且很容出错。所以 wire 的作用就来了。在应用之前咱们先来装置一下 wire。
$ go get github.com/google/wire/cmd/wire
执行该命令会在 $GOPATH/bin
中生成一个可执行程序 wire
,这个就是代码生成器。别忘了吧$GOPATH/bin
退出零碎环境变量 $PATH
中。
先依据下面的简略例子,咱们先来看看 wire 怎么用。咱们先创立一个 wire 文件,文件内容如下:
//+build wireinjectpackage mainimport ("github.com/google/wire")type Ship struct {Pulp *Pulp}func NewShip(pulp *Pulp) *Ship {return &Ship{ pulp: pulp,}}type Pulp struct {Count int}func NewPulp() *Pulp { return &Pulp{}}func (c *Pulp)set(count int) {c.count = count}func (c *Pulp)get() int { return c.count}func InitShip() *Ship { wire.Build( NewPulp, NewShip,) return &Ship{}}func main(){}
其中 InitShip
这个函数的返回值就是咱们须要创立的对象类型,wire
只须要晓得类型,返回什么不重要。在函数中咱们调用了 wire.Build()
将创立 ship
所依赖的的类型结构器传进去。这样咱们就编写好了,当初咱们须要到控制台执行wire
。
$ wirewire: asong.cloud/Golang_Dream/wire_cron_example/ship: wrote /Users/asong/go/src/asong.cloud/Golang_Dream/wire_cron_example/ship/wire_gen.go
咱们看到生成了 wire_gen.go 这个文件:
// Code generated by Wire. DO NOT EDIT.//go:generate wire//+build !wireinjectpackage main// Injectors from mian.go:func InitShip() *Ship { pulp := NewPulp() ship := NewShip(pulp) return ship}// mian.go:type Ship struct {pulp *Pulp}func NewShip(pulp *Pulp) *Ship {return &Ship{ pulp: pulp,}}type Pulp struct {count int}func NewPulp() *Pulp { return &Pulp{}}func (c *Pulp) set(count int) {c.count = count}func (c *Pulp) get() int { return c.count}func main() {}
能够看进去,生成的这个文件依据方才定义的生成了 InitShip()
这个函数,依赖绑定关系也都实现了,咱们间接调用这个函数,就能够了,省去了大量代码本人去实现依赖绑定关系。
留神:如果你是第一次应用 wire,那么你肯定会遇到一个问题,生成的代码和原来的代码会呈现抵触,因为都定义雷同的函数func InitShip() *Ship
,所以这里须要在原文件中首行增加//+build wireinject
,并且还要与包名有空行,这样就解决了抵触。
下面的例子还算是简略,上面咱们来看一个比拟多一点的例子,咱们在日常 web 后盾开发时,代码都是有分层的,比拟相熟的有 dao
、service
、controller
、model
等等。其实 dao
、service
、controller
,是有调用先后顺序的。controller
调用 service
层,service
层调用 dao
层,这就造成了依赖关系,咱们在理论开发中,通过分层依赖注入的形式,更加层次分明,且代码是易于保护的。所以,我写了一个样例,让咱们来学习一下怎么应用。这个采纳 cron
定时工作代替 controller
来代替 controller
,cron
定时工作我会在后文进行解说。
//+build wireinjectpackage wireimport ("github.com/google/wire" "asong.cloud/Golang_Dream/wire_cron_example/config" "asong.cloud/Golang_Dream/wire_cron_example/cron" "asong.cloud/Golang_Dream/wire_cron_example/cron/task" "asong.cloud/Golang_Dream/wire_cron_example/dao" "asong.cloud/Golang_Dream/wire_cron_example/service")func InitializeCron(mysql *config.Mysql) *cron.Cron{wire.Build( dao.NewClientDB, dao.NewUserDB, service.NewUserService, task.NewScanner, cron.NewCron,) return &cron.Cron{}}
咱们来看看这段代码,dao.NewClientDB
即创立一个 *sql.DB
对象,依赖于 mysql
的配置文件,dao.NewUserDB
即创立一个 *UserDB
对象,他依赖于 *sql.DB
,service.NewUserService
即创立一个 UserService
对象,依赖于 *UserDB
对象,task.NewScanner
创立一个 *Scanner
对象,他依赖于 *UserService
对象,cron。NewCron
创立一个 *Cron
对象,他依赖于 *Scanner
对象,其实这里是层层绑定关系,一层调一层,层次分明,且易于代码保护。
好啦,根本应用就介绍到这里,咱们接下来咱们学习一下cron
。
cron
根底学习
咱们在日常开发或运维中,常常遇到一些周期性执行的工作或需要,例如:每一段时间执行一个脚本,每个月执行一个操作。linux 给咱们提供了一个便捷的形式—— crontab 定时工作;crontab 就是一个自定义定时器,咱们能够利用 crontab 命令在固定的间隔时间执行指定的零碎指令或 shell script 脚本。而这个工夫距离的写法与咱们平时用到的 cron 表达式类似。作用都是通过利用字符或命令去设置定时周期性地执行一些操作.
晓得了基本概念,咱们就来介绍一下 cron 表达式。罕用的 cron 标准格局有两种:一种是“规范”cron 格局,由 cron linux
零碎程序应用,还有一种是 Quartz Scheduler
应用 cron 格局。这两种的差异就在一个是反对 seconds
字段的,一个是不反对的,不过差距不是很大,咱们接下来的解说都带上 seconds
这个字段,没有影响的。
cron 表达式是一个字符串,该字符串由 6
个空格分为 7
个域,每一个域代表一个工夫含意。格局如下:
[秒] [分] [时] [日] [月] [周] [年]
[年]的局部通常是能够省略的,实际上由前六局部组成。
对于各局部的定义,咱们以一个表格的模式出现:
域
是否必填
值以及范畴
通配符
秒
是
0-59
, – * /
分
是
0-59
, – * /
时
是
0-23
, – * /
日
是
1-31
, – * ? / L W
月
是
1-12 或 JAN-DEC
, – * /
周
是
1-7 或 SUN-SAT
, – * ? / L #
年
否
1970-2099
, – * /
看这个值的范畴,还是很好了解的,最难了解的是通配符,咱们着重来讲一下通配符。
,
这里指的是在两个以上的工夫点中都执行,如果咱们在“分”这个域中定义为5,10,15
,则示意别离在第 5 分,第 10 分 第 15 分执行该定时工作。-
这个比拟好了解就是指定在某个域的间断范畴,如果咱们在“时”这个域中定义6-12
,则示意在 6 到 12 点之间每小时都触发一次,用,
示意6,7,8,9,10,11,12
*
示意所有值,可解读为“每”。如果在“日”这个域中设置*
, 示意每一天都会触发。?
示意不指定值。应用的场景为不须要关怀以后设置这个字段的值。例如: 要在每月的 8 号触发一个操作,但不关怀是周几,咱们能够这么设置0 0 0 8 * ?
/
在某个域上周期性触发,该符号将其所在域中的表达式分为两个局部,其中第一局部是起始值,除了秒以外都会升高一个单位,比方 在“秒”上定义5/10
示意从 第 5 秒开始 每 10 秒执行一次,而在“分”上则示意从 第 5 秒开始 每 10 分钟执行一次。L
示意英文中的LAST 的意思,只能在“日”和“周”中应用。在“日”中设置,示意当月的最初一天(根据以后月份,如果是二月还会根据是否是润年), 在“周”上示意周六,相当于”7”或”SAT”。如果在”L”前加上数字,则示意该数据的最初一个。例如在“周”上设置”7L”这样的格局, 则示意“本月最初一个周六”W
示意离指定日期的最近那个工作日 (周一至周五) 触发,只能在“日”中应用且只能用在具体的数字之后。若在“日”上置”15W”,示意离每月 15 号最近的那个工作日触发。如果 15 号正好是周六,则找最近的周五 (14 号) 触发, 如果 15 号是周未,则找最近的下周一 (16 号) 触发. 如果 15 号正好在工作日(周一至周五),则就在该天触发。如果是“1W”就只能往本月的下一个最近的工作日推不能跨月往上一个月推。#
示意每月的第几个周几,只能作用于“周”上。例如”2#3”示意在每月的第三个周二。
学习了通配符,上面咱们来看几个例子:
- 每天 10 点执行一次:
0 0 10 * * *
- 每隔 10 分钟执行一次:
0 */10 * * *
- 每月 1 号凌晨 3 点执行一次:
0 0 3 1 * ?
- 每月最初一天 23 点 30 分执行一次:
0 30 23 L * ?
- 每周周六凌晨 3 点履行一次:
0 0 3 ? * L
- 在 30 分、50 分执行一次:
0 30,50 * * * ?
go 中应用 cron
后面咱们学习了根底,当初咱们想要在 go 我的项目中应用定时工作,咱们该怎么做呢?github
上有一个星星比拟高的一个 cron
库,咱们能够应用 robfig/cron
这个库开发咱们的定时工作。
学习之前,咱们先来装置一下cron
$ go get -u github.com/robfig/cron/v3
这是目前比较稳定的版本,当初这个版本是采纳标准规范的,默认是不带seconds
,如果想要带上字段,咱们须要创立 cron 对象是去指定。一会展现。咱们先来看一个简略的应用:
package mainimport ("fmt" "time" "github.com/robfig/cron/v3")func main() { c := cron.New() c.AddFunc("@every 1s", func() {fmt.Println("task start in 1 seconds") }) c.Start() select{}}
这里咱们应用 cron.New
创立一个 cron 对象,用于治理定时工作。调用 cron
对象的 AddFunc()
办法向管理器中增加定时工作。AddFunc()
承受两个参数,参数 1 以字符串模式指定触发工夫规定,参数 2 是一个无参的函数,每次触发时调用。@every 1s
示意每秒触发一次,@every
后加一个工夫距离,示意每隔多长时间触发一次。例如 @every 1h
示意每小时触发一次,@every 1m2s
示意每隔 1 分 2 秒触发一次。time.ParseDuration()
反对的格局都能够用在这里。调用 c.Start()
启动定时循环。
留神一点,因为 c.Start()
启动一个新的 goroutine 做循环检测,咱们在代码最初加了一行 select{}
避免主 goroutine 退出。
下面咱们定义工夫时,应用的是 cron
预约义的工夫规定,那咱们就学习一下他都有哪些预约义的一些工夫规定:
@yearly
:也能够写作@annually
,示意每年第一天的 0 点。等价于0 0 1 1 *
;@monthly
:示意每月第一天的 0 点。等价于0 0 1 * *
;@weekly
:示意每周第一天的 0 点,留神第一天为周日,即周六完结,周日开始的那个 0 点。等价于0 0 * * 0
;@daily
:也能够写作@midnight
,示意每天 0 点。等价于0 0 * * *
;@hourly
:示意每小时的开始。等价于0 * * * *
。
cron
也是反对固定工夫距离的,格局如下:
@every <duration>
含意为每隔 duration
触发一次。<duration>
会调用 time.ParseDuration()
函数解析,所以 ParseDuration
反对的格局都能够。
我的项目应用
因为我本人写的我的项目是通过实现 job 接口来退出定时工作,所以上面咱们再来介绍一下 Job 接口的应用,除了间接将无参函数作为回调外,cron
还反对 job
接口:
type Job interface{Run()}
咱们须要实现这个接口,这里我就以我写的例子来做演示吧,我当初这个定时工作是周期扫 DB 表中的数据,实现工作如下:
package taskimport ("fmt" "asong.cloud/Golang_Dream/wire_cron_example/service")type Scanner struct {lastID uint64 user *service.UserService}const (ScannerSize = 10)func NewScanner(user *service.UserService) *Scanner{return &Scanner{ user: user,}}func (s *Scanner)Run() { err := s.scannerDB() if err != nil{fmt.Errorf(err.Error()) }}func (s *Scanner)scannerDB() error{ s.reset() flag := false for {users,err:=s.user.MGet(s.lastID,ScannerSize) if err != nil{return err} if len(users) < ScannerSize{flag = true} s.lastID = users[len(users) - 1].ID for k,v := range users{fmt.Println(k,v) } if flag{return nil} }}func (s *Scanner)reset() { s.lastID = 0}
下面是实现 Run
办法的局部,之后咱们还须要调用 cron
对象的 AddJob 办法将 Scanner
对象增加到定时管理器中。
package cronimport ("github.com/robfig/cron/v3" "asong.cloud/Golang_Dream/wire_cron_example/cron/task")type Cron struct {Scanner *task.Scanner Schedule *cron.Cron}func NewCron(scanner *task.Scanner) *Cron {return &Cron{ Scanner: scanner, Schedule: cron.New(), }}func (s *Cron)Start() error{ _,err := s.Schedule.AddJob("*/1 * * * *",s.Scanner) if err != nil{return err} s.Schedule.Start() return nil}
实际上 AddFunc()
办法外部也调用了 AddJob()
办法。首先,cron
基于 func()
类型定义一个新的类型FuncJob
:
// cron.gotype FuncJob func()
而后让 FuncJob
实现 Job
接口:
// cron.gofunc (f FuncJob) Run() { f()}
在 AddFunc()
办法中,将传入的回调转为 FuncJob
类型,而后调用 AddJob()
办法:
func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error) {return c.AddJob(spec, FuncJob(cmd))}
好啦,根本的应用到这里咱们就解说完了,最初再补充一下最初一个知识点,也就工夫标准的问题,默认 v3
版本是不带 seconds
字段的,要想应用须要这样应用
cron.New(cron.WithSeconds())
创建对象的传入这个参数就能够了。
好啦。我想要解说的完事了,代码就不运行了,已上传 github,可自行下载学习:https://github.com/asong2020/…
总结
明天的文章就到这里了,这一篇总结的并不全,只是达到入门的一个成果,想要持续深刻,还须要各位小伙伴自行看文档学习呦。学会看官网文档,能力提高更多的呦。就比方工夫标准这里,如果不看文档,我就不会晓得当初应用的工夫标准是什么的,所以还是要养成看文档的好习惯。打个预报,下一期是 go-elastic 的教程,有须要的小伙伴能够关注一下。
结尾给大家发一个小福利吧,最近我在看 [微服务架构设计模式] 这一本书,讲的很好,本人也收集了一本 PDF,有须要的小伙能够到自行下载。获取形式:关注公众号:[Golang 梦工厂],后盾回复:[微服务],即可获取。
我翻译了一份 GIN 中文文档,会定期进行保护,有须要的小伙伴后盾回复 [gin] 即可下载。
我是 asong,一名普普通通的程序猿,让我一起缓缓变强吧。欢送各位的关注,咱们下期见~~~
举荐往期文章:
- 据说你还不会 jwt 和 swagger- 饭我都不吃了带着实际我的项目我就来了
- 把握这些 Go 语言个性,你的程度将进步 N 个品位(二)
- go 实现多人聊天室,在这里你想聊什么都能够的啦!!!
- grpc 实际 - 学会 grpc 就是这么简略
- go 规范库 rpc 实际
- 2020 最新 Gin 框架中文文档 asong 又捡起来了英语,用心翻译
- 基于 gin 的几种热加载形式
- boss: 这小子还不会应用 validator 库进行数据校验,开了~~~