Golang Go语言中 go-spring 使用学习

发布于 1周前 作者 ionicwang 来自 Go语言

Golang Go语言中 go-spring 使用学习

前言

  • 最近发现了 go-spring 并且发布了 v1.0.0-beta 版。
  • 看了一下感觉挺不错的,最近离职在家学习就花了一天时间学习这边记录一下

一、安装

# 拉取 go spring
$ go get github.com/go-spring/go-spring[@master](/user/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](/user/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 仓库里,作者为减少引入包已经把这些 starter 移动到了 go-spring 仓库里。

starter 部分的暂时无视,这样一看就只剩下 spring-corespring-bootboot-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/starter-gin"
_ "github.com/go-spring/go-spring/starter-web"

)

func init() { SpringBoot.RegisterBean(new(Controller)).InitFunc(func(c *Controller) { SpringBoot.GetMapping("/", c.Home) }) }

type Controller struct{}

func (c *Controller) Home(ctx SpringWeb.WebContext) { ctx.String( http.StatusOK, “OK!”) }

func main() { SpringBoot.RunApplication(“config/”) }

  • 其中 init 方法里我们注册了一个 Controller 的空实例,这个不一定要在 init 中注册,可以在 SpringBoot.RunApplication 调用前的任意地方注册,使用 init 的原因是可以不依赖包内部方法只需要导入即可注入。
  • 然后通过 InitFunc 注册路由,SpringBoot.GetMapping 是统一封装的路由挂载器
  • Home(ctx SpringWeb.WebContext) 里的 SpringWeb.WebContext 则封装了请求响应操作。
  • github.com/go-spring/go-spring/starter-gin 导入替换为 github.com/go-spring/go-spring/starter-echo 可以直接替换为 echo 框架。

执行该文件会打出大量的注册初始化日志,正式版应该会能够关闭。

$ go run main.go
register bean "github.com/go-spring/go-spring/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/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/starter-web/WebStarter.WebServerConfig:*WebStarter.WebServerConfig
success wire bean "github.com/go-spring/go-spring/starter-web/WebStarter.WebServerConfig:*WebStarter.WebServerConfig"
wire bean github.com/go-spring/go-spring/starter-web/WebStarter.WebServerStarter:*WebStarter.WebServerStarter
success wire bean "github.com/go-spring/go-spring/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)).InitFunc(func(c *Controller) { SpringBoot.GetMapping("/", 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/starter-gin” _ “github.com/go-spring/go-spring/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))InitFunc(func(c *Controller) { SpringBoot.GetMapping("/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

版权信息

本文作者: zeromake

原文链接: [https://blog.zeromake.com/pages/go-spring-learn]https://blog.zeromake.com/pages/go-spring-learn

最后更新: 2019-12-22 17:20:57+08:00

版权声明: 本博客所有文章除特别声明外, 均采用 CC BY-NC-SA 4.0 许可协议. 转载请注明出处!


更多关于Golang Go语言中 go-spring 使用学习的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

36 回复

刚要从 java 到 go。。。又来 spring。。

更多关于Golang Go语言中 go-spring 使用学习的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


#1 那你可以手动的做实例化并且在个个组件间使用,不过也可以用一个更简单的注入工具 https://github.com/uber-go/dig 一共就三个方法。
container := dig.New()

把 go 写成 java。。
我写 go 就简单粗暴了,直接撸 init 函数把依赖注入到全局定义的 var sync.Map 变量里

#3 我之前也这么干,然后找到了 diggo-spring

spring 那套太重了不喜欢

来踩一踩,另外:
异端!!!

你用 Spring 干啥,如果只是为了 DI,Google 有官方的 wire

#7
我去看了一下 wire,感觉有点难用还不如用 dig 的感觉

楼主去了两家听说技术要求比较高的公司。厦门区域的话,几个大厂投一下看看。小公司薪资都一般

#9
投过一个亿联,也是薪资一般

。。。感觉更啰嗦了

#9
还有就是两家里的后面那家稿定现在的 node 后端有点炸,技术要求并不高,面试倒是挺难的,我撸了一整年的 crud。
我觉得要去稿定要么去前端编辑器那边,要么去稿定的 ai 部门,网站后端部门今年没有一个人晋升职级了,前端编辑器好几个都升了。

#11

还好吧至少比手动做依赖注入方便的多,也比 dig 的少了很多代码,就是 go-spring 的 SpringBoot 强制管理了配置和服务的启动关闭有点入侵的感觉。

我想用 Spring 为啥不用 Java。。。

#14
你搞反了我是想在 go 里用 spring 的依赖注入能力而已,原来我的 web 项目的 controller 和 service 之间的依赖关系和实例化都很麻烦,用了 dig 稍微好点,但是实例化后的对象的属性设置还是要手动做,用了 go-spring 直接就告别了对实例化后的对象做属性设置。

第一反应是 spring 追杀我到 golang 了

现在微服务的玩法还没碰到过一个微服务复杂到要用依赖注入来处理 想用依赖注入可以用 https://github.com/uber-go/fx

#16
在用 go-spring 之前就是用的 fx 的底层 dig 做注入啦,不过因为实例化的逻辑还是得自己写就换 go-spring 了。

看见 go 都开始 spring 我就放心了~

反射对性能有影响啊

#19 依赖注入只是启动时的依赖获取吧。

你为什么不直接用 Spring,是什么原因让你这么自虐…

#21
因为我只是拿来做 go 的依赖注入啊,又不是 spring 全套。

依赖为什么要诸如 每一个都放到自己的包里 然后搞个单例 传来传去不香吗

穿着棉袄去洗澡

#23 不香……很麻烦

一次只干一件事

算了,我选择自己写,或者用 gin,这搞的太复杂了又

真够费劲的,就你们这些人闲的没事,搞这些花里胡哨的东西,go 追求的就是简洁,别整天生搬硬套

go 不需要依赖注入🌚

那为什么不用 JAVA 啊 。。。。。。

#30
#28
#29

真的是够了,我这边是已经有了项目而且也都是 go 写的,现在只是使用 go-spring 做 service 和 controller 的依赖注入。

使用 go-spring 可以简化我的 service, controller 实例化。

很好奇用了 spring 这名字侵权不
https://github.com/go-spring 一堆的 spring
go-spring-boot-demo
go-spring-web
go-spring-boot-starter
go-spring-website
go-spring-rpc
go-spring-parent
go-spring-redis
go-spring-orm

感觉很好啊,java 程序员转 go 更容易上手了

厦门能有 20K 的很少吧

写 Go 我从来不用第三方的库 直接在 SDK 上撸 酸爽

关于Go语言中go-spring的使用学习,以下是一些专业的解答和建议:

Go-Spring是一个基于Go语言的轻量级应用框架,它借鉴了Spring Boot的设计理念和部分功能,提供了依赖注入、面向切面编程、配置管理等特性,为Go开发者提供了一种简单、高效的应用开发方式。

在使用go-spring时,首先需要确保开发环境已经安装了Go语言和go-spring框架。然后,可以通过在项目中添加对性能监控模块的依赖,并在配置文件中开启Actuator的端点暴露,来配置和使用go-spring的性能监控模块。这可以帮助开发者轻松地监控和管理应用程序的运行状态和性能指标。

此外,go-spring还支持自定义监控指标,以满足特定的监控需求。开发者可以通过实现相应的接口,并注册自定义的监控指标到Actuator模块中,然后通过HTTP请求访问这些自定义指标。

在学习go-spring时,建议查阅官方文档或社区提供的教程,以获取更准确和详细的信息。同时,也可以参与社区讨论,与其他开发者交流和分享使用经验。

总之,go-spring是一个功能强大且易于使用的Go语言框架,它提供了丰富的特性和工具,可以帮助开发者更高效地开发和管理应用程序。

回到顶部