前言
嗨,我小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库进行数据校验,开了~~~