電話:177-0815-5860
RELATEED CONSULTING
相關咨詢
選擇下列產品馬上在線溝通
服務時間:9:30-18:00
關閉右側工具欄

技術支持

go-spring 使用學習
  • 作者:admin
  • 發表時間:2019-12-22 08:05
  • 來源:互聯網

go-spring 使用學習

前言

  • 最近發現了 go-spring 并且發布了 v1.0.0-beta 版。
  • 看了一下感覺挺不錯的,最近離職在家學習就花了一天時間學習這邊記錄一下

一、安裝


	

# 拉取 go spring

$ go get github.com/go-spring/go-spring@master


# 如果需要使用 go-spring 做 web 服務需要以下包

# go-spring-boot-starter 是使用 spring-boot 包裝的支持 web 以及其它的啟動器

$ go get github.com/go-spring/go-spring-boot-starter@master

# go-spring-web 則是配合 go-spring-boot-starter 使用的各種 web 框架的封裝

$ go get github.com/go-spring/go-spring-web@master

由于 go-spring 現在還是 beta 版,每天都有可能有一些重要更新建議拉取最新的 master。

不過到了后面 go-spring 正式版也許就不需要直接手動拉取 @master 了,請自行判斷。

二、go-spring 項目包結構介紹



$ tree . -L 1

.

├── CONTRIBUTING.md

├── LICENSE

├── README.md

├── RunAllTests.sh

├── RunCodeCheck.sh

├── RunGoDoc.sh

├── boot-starter

├── go.mod

├── go.sum

├── package-info.go

├── spring-boot

├── spring-core

├── starter-echo

├── starter-gin

└── starter-web


6 directories, 9 files

其中 starter 開頭相關的已經被拆分到 go-spring-boot-starter 倉庫里了。

這樣一看就只剩下 spring-corespring-boot, boot-starter。

  • spring-core 是用于 IoC 容器注入的核心庫。
  • spring-boot 是使用了 spring-core 構建的配置自動載入,還有注入的對象的啟動和關閉的統一管理。
  • boot-starter 簡單啟動和監聽信號包裝器。

三、一個簡單 gin web 服務



package main


import (

SpringWeb "github.com/go-spring/go-spring-web/spring-web"

SpringBoot "github.com/go-spring/go-spring/spring-boot"

"net/http"


_ "github.com/go-spring/go-spring-boot-starter/starter-gin"

_ "github.com/go-spring/go-spring-boot-starter/starter-web"

)


func init() {

SpringBoot.RegisterBean(new(Controller))

}


type Controller struct{}


func (c *Controller) InitWebBean(wc SpringWeb.WebContainer) {

wc.GET("/", c.Home)

}


func (c *Controller) Home(ctx SpringWeb.WebContext) {

ctx.String(http.StatusOK, "OK!")

}


func main() {

SpringBoot.RunApplication("config/")

}
  • 其中 init 方法里我們注冊了一個 Controller 的空實例,這個不一定要在 init 中注冊,可以在 SpringBoot.RunApplication 調用前的任意地方注冊,使用 init 的原因是可以不依賴包內部方法只需要導入即可注入。
  • InitWebBean(wc SpringWeb.WebContainer) 這個實例方法是 starter-web 庫提供的路由初始化回調能力 SpringWeb.WebContainer 封裝了各種 web 庫的路由注冊能力,現在支持 gin, echo。
  • Home(ctx SpringWeb.WebContext) 里的 SpringWeb.WebContext 則封裝了請求響應操作。
  • github.com/go-spring/go-spring-boot-starter/starter-gin 導入替換為 github.com/go-spring/go-spring-boot-starter/starter-echo 可以直接替換為 echo 框架。

執行該文件會打出大量的注冊初始化日志,正式版應該會能夠關閉。



$ go run main.go

register bean "github.com/go-spring/go-spring-boot-starter/starter-web/WebStarter.WebServerStarter:*WebStarter.WebServerStarter"

register bean "main/main.Controller:*main.Controller"

register bean "github.com/go-spring/go-spring/spring-boot/SpringBoot.DefaultApplicationContext:*SpringBoot.DefaultApplicationContext"

register bean "github.com/go-spring/go-spring-boot-starter/starter-web/WebStarter.WebServerConfig:*WebStarter.WebServerConfig"

wire bean github.com/go-spring/go-spring/spring-boot/SpringBoot.DefaultApplicationContext:*SpringBoot.DefaultApplicationContext

success wire bean "github.com/go-spring/go-spring/spring-boot/SpringBoot.DefaultApplicationContext:*SpringBoot.DefaultApplicationContext"

wire bean github.com/go-spring/go-spring-boot-starter/starter-web/WebStarter.WebServerConfig:*WebStarter.WebServerConfig

success wire bean "github.com/go-spring/go-spring-boot-starter/starter-web/WebStarter.WebServerConfig:*WebStarter.WebServerConfig"

wire bean github.com/go-spring/go-spring-boot-starter/starter-web/WebStarter.WebServerStarter:*WebStarter.WebServerStarter

success wire bean "github.com/go-spring/go-spring-boot-starter/starter-web/WebStarter.WebServerStarter:*WebStarter.WebServerStarter"

wire bean main/main.Controller:*main.Controller

success wire bean "main/main.Controller:*main.Controller"

spring boot started

? http server started on :8080

訪問 http://127.0.0.1 可以看到上面的代碼效果。

該章節代碼見 post-1 分支。

四、拆分 controller 并自動注冊路由

現代項目都是 controller + service 外加一個實體層,這里我們試著把 controller 拆分出去。

新建一個 controllers 目錄下面創建一個 controllers.go 來導入各個獨立的 controller。

controllers/home/home.go



package home


import (

SpringWeb "github.com/go-spring/go-spring-web/spring-web"

SpringBoot "github.com/go-spring/go-spring/spring-boot"

"net/http"

)


type Controller struct {}


func init() {

SpringBoot.RegisterBean(new(Controller))

}


func (c *Controller) InitWebBean(wc SpringWeb.WebContainer) {

wc.GET("/", c.Home)

}


func (c *Controller) Home(ctx SpringWeb.WebContext) {

ctx.String(http.StatusOK, "OK!")

}

controllers/controllers.go



package controllers


// 導入各個 controller 即可實現路由掛載

import (

_ "github.com/zeromake/spring-web-demo/controllers/home"

)

main.go



package main


import (

_ "github.com/go-spring/go-spring-boot-starter/starter-gin"

_ "github.com/go-spring/go-spring-boot-starter/starter-web"

SpringBoot "github.com/go-spring/go-spring/spring-boot"

_ "github.com/zeromake/spring-web-demo/controllers"

)


func main() {

SpringBoot.RunApplication("config/")

}

重新運行 go run main.go 訪問瀏覽器能獲得相同的效果,這樣我們就把 controller 拆分出去了。

該章節代碼見 post-2 分支。

五、構建 service 的自動注入到 controller

上面說到 controller 的主要的能力為路由注冊,參數處理復雜的邏輯應當拆分到 service 當中。

在我使用 go-spring 之前都是手動的構建一個 map[string]interface{} 然后把 service 按照自定義名字掛進去。

然后在 controller 構建時從這個 map 中取出并強制轉換為 service 類型或者抽象的接口。

這個方案問題蠻大的,手動的 service 名稱容易出錯,而且注冊和在 controller 注入都是非常麻煩的,而且錯誤處理也都沒做。

但是這一切有了 go-spring 就不一樣了,我只需要在 service 注冊,在 controller 里的結構體里聲明這個 service 類型實例就可以使用。

為了不作為一個示例而太簡單讓學習者覺得沒有什么意義,我決定做一個上傳的能力,先看未拆分 service 的情況

controllers/upload/upload.go



package upload


import (

// ……

)


type Controller struct{}


func init() {

SpringBoot.RegisterBean(new(Controller))

}


func (c *Controller) InitWebBean(wc SpringWeb.WebContainer) {

wc.POST("/upload", c.Upload)

}


func (c *Controller) Upload(ctx SpringWeb.WebContext) {

file, err := ctx.FormFile("file")

if err != nil {

// ……

return

}

w, err := file.Open()

if err != nil {

// ……

return

}

defer func() {

_ = w.Close()

}()

out := path.Join("temp", file.Filename)

if !PathExists(out) {

dir := path.Dir(out)

if !PathExists(dir) {

err = os.MkdirAll(dir, DIR_MARK)

if err != nil {

// ……

return

}

}

dst, err := os.OpenFile(out, FILE_FLAG, FILE_MAEK)

if err != nil {

// ……

return

}

defer func() {

_ = dst.Close()

}()

_, err = io.Copy(dst, w)

if err != nil {

// ……

return

}

} else {

// ……

return

}

ctx.JSON(http.StatusOK, gin.H{

"code": 0,

"message": http.StatusText(http.StatusOK),

"data": map[string]string{

"url": out,

},

})

}


func PathExists(path string) bool {

// ……

}

運行 go run main.go 然后用 curl 上傳測試。



$ curl -F "file=@./README.md" http://127.0.0.1:8080/upload

{"code":0,"data":{"url":"temp/README.md"},"message":"OK"}

# 重復上傳會發現文件已存在

$ curl -F "file=@./README.md" http://127.0.0.1:8080/upload

{"code":1,"message":"該文件已存在"}

在項目下的 temp 文件夾中能夠找到上傳后的文件。

以上能正常運行但是 controller 中包含了大量的邏輯而且均為文件操作 api 耦合性過高。

我們需要把上面的的文件操作拆分到 service 當中。

services/file/file.go

將文件操作邏輯抽取為 PutObject(name string, r io.Reader, size int64) (err error)ExistsObject(name string) bool。



package file


type Service struct{}


func init() {

SpringBoot.RegisterBean(new(Service))

}


func (s *Service) PutObject(name string, r io.Reader, size int64) (err error) {

// ……

}


func (s *Service) ExistsObject(name string) bool {

// ……

}

services/services.go



package services


import (

_ "github.com/zeromake/spring-web-demo/services/file"

)

main.go

增加 services 的導入。



package main


import (

// ……

_ "github.com/zeromake/spring-web-demo/services"

)


func main() {

SpringBoot.RunApplication("config/")

}

controllers/upload/upload.go

Controller 上聲明 File 并設置 tag autowire,這樣 spring-boot 會自動注入 service 那邊注冊的實例。



package upload


import (

"github.com/gin-gonic/gin"

SpringWeb "github.com/go-spring/go-spring-web/spring-web"

SpringBoot "github.com/go-spring/go-spring/spring-boot"

"github.com/zeromake/spring-web-demo/services/file"

"net/http"

"path"

)


type Controller struct {

File *file.Service `autowire:""`

}


func (c *Controller) Upload(ctx SpringWeb.WebContext) {

// ……

if !c.File.ExistsObject(out) {

err = c.File.PutObject(out, w, f.Size)

if err != nil {

ctx.JSON(http.StatusInternalServerError, gin.H{

"code": 1,

"message": "保存失敗",

"error": err.Error(),

})

return

}

} else {

ctx.JSON(http.StatusBadRequest, gin.H{

"code": 1,

"message": "該文件已存在",

})

return

}

// ……

}

重新運行 go run main.go 并測試,功能正常



$ rm temp/README.md

$ curl -F "file=@./README.md" http://127.0.0.1:8080/upload

{"code":0,"data":{"url":"temp/README.md"},"message":"OK"}


$ curl -F "file=@./README.md" http://127.0.0.1:8080/upload

{"code":1,"message":"該文件已存在"}

未拆分 service 的完整代碼在 post-3 拆分了 service 的完整代碼在 post-4

六、spring-boot 加載配置注入對象

我們啟動服務時有傳入一個 config/ 這個實際上是配置文件搜索路徑。

SpringBoot.RunApplication("config/")

spring-boot 支持不少格式的配置和命名方式,這些都不介紹了。

只介紹一下怎么使用這些文件

config/application.toml



[spring.application]

name = "demo-config"


[file]

dir = "temp"

controllers/upload/upload.gocontroller 使用配置替換硬編碼的保存文件夾路徑, value:"${file.dir}" 對應配置文件的路徑綁定。



type Controller struct {

File *file.Service `autowire:""`

Dir string `value:"${file.dir}"`

}


func (c *Controller) Upload(ctx SpringWeb.WebContext) {

// ……

// 替換為注入的配置

out := path.Join(c.Dir, f.Filename)

// ……

}

當然 spring-boot 也支持對結構體實例化配置數據還有默認值。




type Config struct {

Dir string `value:"${file.dir=tmp}"`

}


type Controller struct {

File *file.Service `autowire:""`

Config Config

}


func (c *Controller) Upload(ctx SpringWeb.WebContext) {

// ……

// 替換為注入的配置

out := path.Join(c.Config.Dir, f.Filename)

// ……

}

該章完整代碼在 post-5

七、通過接口類型解除 controller 對 service 的依賴

以上代碼已經很完整了,但是 controller 直接導入 service 造成對邏輯的直接依賴,這樣會照成很高的代碼耦合,而且導入 service 包也比較麻煩。

這里我們可以使用 interface 來做到解除依賴,這樣不僅解決的導入的問題也能夠快速的替換 serivce 的實現。

types/services.go

之前抽取的抽象方法派上用處了。



package types


import (

"io"

)


type FileProvider interface {

PutObject(name string, r io.Reader, size int64) error

ExistsObject(name string) bool

}

controllers/upload/upload.go

然后把 *file.Service 類型替換為 types.FileProvider 即可,spring-boot 會自動匹配接口對應的實例。



type Controller struct {

File types.FileProvider `autowire:""`

Dir string `value:"${file.dir}"`

}

該章完整代碼在 post-6

八、通過 Condition 來限制 Bean 的注冊來做到不同的 service 切換

上面我們說到用 interface 結構后是可以替換不同的邏輯實現的,這里我們就來一個對象存儲和本地文件存儲能力的更換,可以通過配置文件替換文件操作邏輯實現。

這里使用 minio 作為遠端對象存儲服務。

docker-compose 這里我們用 docker 快速創建一個本地的 minio 服務。



version: "3"

services:

minio:

image: "minio/minio:RELEASE.2019-10-12T01-39-57Z"

volumes:

- "./minio:/data"

ports:

- "9000:9000"

environment:

MINIO_ACCESS_KEY: minio

MINIO_SECRET_KEY: minio123

command:

- "server"

- "/data"

config/application.toml 添加 minio 配置



[minio]

enable = true

host = "127.0.0.1"

port = 9000

access = "minio"

secret = "minio123"

secure = false

bucket = "demo"

modules/minio/minio.go 單獨的用 module 來做 minio 的客戶端初始化。



package minio


type MinioConfig struct {

Enable bool `value:"${minio.enable:=true}"` // 是否啟用 HTTP

Host string `value:"${minio.host:=127.0.0.1}"` // HTTP host

Port int `value:"${minio.port:=9000}"` // HTTP 端口

Access string `value:"${minio.access:=}"` // Access

Secret string `value:"${minio.secret:=}"` // Secret

Secure bool `value:"${minio.secure:=true}"` // Secure

Bucket string `value:"${minio.bucket:=}"`

}


func init() {

SpringBoot.RegisterNameBeanFn(

// 給這個實例起個名字

"minioClient",

// 自動注入 minio 配置

func(config MinioConfig) *minio.Client {

// ……

},

// 前面的 0 代表參數位置,后面則是配置前綴

"0:${}",

// ConditionOnPropertyValue 會檢查配置文件來確認是否注冊

).ConditionOnPropertyValue(

"minio.enable",

true,

)

}

記得收集導入到 main.go。

services/file/file.go

本地存儲 service 需要在沒有注冊 minioClient 的情況才注冊。



func init() {

SpringBoot.RegisterBean(new(Service)).ConditionOnMissingBean("minioClient")

}

services/minio/minio.go



package minio


type Service struct {

// 自動注入 minio client

Client *minio.Client `autowire:""`

Bucket string `value:"${minio.bucket:=}"`

}


func init() {

// 在已注冊了 minioClient 才注冊

SpringBoot.RegisterBean(new(Service)).ConditionOnBean("minioClient")

}


func (s *Service) PutObject(name string, r io.Reader, size int64) error {

// ……

}


func (s *Service) ExistsObject(name string) bool {

// ……

}

	

然后啟動 docker-compose up -d minio 啟動 minio 服務。

修改 config/application.tomlminio.enable 可以切換存儲能力。

本章完整代碼在 post-7

求職

我是 zeromake 現在我離職中。

希望能夠找到一個合適的新工作。

目標:Golang 開發,廈門優先

我的在線簡歷: zeromake的簡歷

順便推廣一下: docker-debug

国产精品视频一区无码