V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
limpo
V2EX  ›  Go 编程语言

Go-Spring 轻装上阵,重新出发 v1.0.0 !

  •  
  •   limpo ·
    limpo1989 · 176 天前 · 6275 次点击
    这是一个创建于 176 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前言

    多年前曾经梦想着能有一天能在 Go 语言中使用上 Spring 那样强大的框架,幸运的是我找到了go-spring,一款类似 Spring 的库,我很喜欢它,并且在我的项目中深度使用它,它给我带来了非常酷的编程体验。

    然而很不幸的是最近 go-spring 停止更新了(最近的一次提交停留在九个月之前),一些问题无法得到有效的解决,因此我决定创建一个基于 go-spring 的开源库,我为它修复了一系列 bug ,并添加了一些新得特性,并对代码结构进行了简化,将外部生态从核心库中移除,仅保留核心功能,外部生态预计将来将作为独立的仓库存在,我将持续继续将它维护下去,也希望有感兴趣的人可以一起参与进来一起持续开发它。

    仓库地址: https://github.com/go-spring-projects/go-spring

    特性

    • IoC 容器: 实现了基于反射的控制反转(IoC)容器,支持结构体、函数和常量的注入。这意味着你可以使用' autowired '标签来自动注入依赖,而不必手动管理它们。
    • 配置管理: 受 Spring 的 @Value 注释的启发,Go-Spring 允许您从多个源(如环境变量、文件、命令行参数等)获取配置项。这为配置管理带来了前所未有的灵活性。
    • 配置验证器: 通过支持自定义验证器扩展扩展了其健壮的配置管理功能。这使您能够对属性执行有效性检查,确保仅将有效的配置应用于您的应用程序。
    • 结构化日志: 使用标准库 slog 提供内置日志记录器支持,以实现有效和简化的日志记录。这种增强提供了清晰、简洁和结构良好的日志信息,有助于系统调试和性能监视。
    • 动态属性: 提供动态属性刷新功能,让您无需重新启动应用程序即可动态更新应用程序属性。它满足了需要高可用性和实时响应的应用程序的需求。
    • 依赖序事件: 按照 bean 依赖关系的顺序,确保根据对象的生命周期正确通知初始化和销毁事件。这增强了系统在其生命周期运行中的鲁棒性和可靠性。

    动态属性例子

    允许在运行时动态刷新属性,不仅支持基本数据类型,还支持结构、切片和 Map 类型。

    package main
    
    import (
    	"fmt"
    	"log/slog"
    	"net/http"
    
    	"github.com/go-spring-projects/go-spring/dync"
    	"github.com/go-spring-projects/go-spring/gs"
    )
    
    type Handler struct {
    	Open dync.Bool `value:"${server.open:=true}"`
    }
    
    func (h *Handler) OnInit(ctx gs.Context) error {
    
    	http.HandleFunc("/server/status", func(writer http.ResponseWriter, request *http.Request) {
    		if !h.Open.Value() {
    			http.Error(writer, "server closed", http.StatusNotAcceptable)
    			return
    		}
    
    		fmt.Fprint(writer, "server running")
    	})
    	return nil
    }
    
    type Server struct {
    	Logger *slog.Logger `logger:""`
    }
    
    func (s *Server) OnInit(ctx gs.Context) error {
    
    	props := ctx.(gs.Container).Properties()
    
    	http.HandleFunc("/server/status/open", func(writer http.ResponseWriter, request *http.Request) {
    		props.Set("server.open", "true")
    		s.Logger.Info("server opened")
    	})
    
    	http.HandleFunc("/server/status/close", func(writer http.ResponseWriter, request *http.Request) {
    		props.Set("server.open", "false")
    		s.Logger.Info("server closed")
    	})
    
    	go func() {
    		http.ListenAndServe(":7878", nil)
    	}()
    
    	return nil
    }
    
    func main() {
    
    	gs.Object(new(Handler))
    	gs.Object(new(Server))
    
    	if err := gs.Run(); nil != err {
    		panic(err)
    	}
    }
    
    
    // Output:
    // 
    // $ curl http://127.0.0.1:7878/server/status
    // server running
    //
    // $ curl http://127.0.0.1:7878/server/status/close
    //
    // $ curl http://127.0.0.1:7878/server/status
    // server closed
    //
    // $ curl http://127.0.0.1:7878/server/status/open
    //
    // $ curl http://127.0.0.1:7878/server/status
    // server running
    

    参数校验器例子

    Go-Spring允许您注册自定义值验证器。如果值验证失败,则Go-Spring将在启动阶段报告一个错误。

    在这例子中, 我们将使用 go-validator/validator, 您可以参考这个示例来注册您的自定义验证器。

    package main
    
    import (
    	"fmt"
    	"log/slog"
    
    	"github.com/go-spring-projects/go-spring/conf"
    	"github.com/go-spring-projects/go-spring/gs"
    	"gopkg.in/validator.v2"
    )
    
    const validatorTagName = "validate"
    
    type validatorWrapper struct {
    	validator *validator.Validator
    }
    
    func (v *validatorWrapper) Field(tag string, i interface{}) error {
    	if 0 == len(tag) {
    		return nil
    	}
    	return v.validator.Valid(i, tag)
    }
    
    func init() {
    	conf.Register(validatorTagName, &validatorWrapper{validator: validator.NewValidator().WithTag(validatorTagName)})
    }
    
    //--------------------------------------------------------------------------
    
    type DBOptions struct {
    	UserName string `value:"${username}"`
    	Password string `value:"${password}"`
    	IP       string `value:"${ip}"`
    	Port     int32  `value:"${port}" validate:"min=1024,max=65535"`
    	DB       string `value:"${db}" validate:"nonzero"`
    }
    
    type MysqlDatabase struct {
    	Logger  *slog.Logger `logger:""`
    	Options DBOptions    `value:"${db}"`
    }
    
    func (md *MysqlDatabase) OnInit(ctx gs.Context) error {
    	md.Logger.Info("mysql connection summary",
    		"url", fmt.Sprintf("mysql://%s:%s@%s:%d/%s", md.Options.UserName, md.Options.Password, md.Options.IP, md.Options.Port, md.Options.DB))
    	return nil
    }
    
    func main() {
    
    	gs.Property("db.username", "admin")
    	gs.Property("db.password", "123456")
    	gs.Property("db.ip", "127.0.0.1")
    	gs.Property("db.port", "0") // set db.port=0
    	gs.Property("db.db", "test")
    
    	gs.Object(new(MysqlDatabase))
    
    	if err := gs.Run(); nil != err {
    		panic(err)
    	}
    }
    
    //
    // Output:
    // panic: container refresh failed
    // ↳object bean "main/main.MysqlDatabase:MysqlDatabase" /projects/go-project/gocase/validator/main.go:58
    // ↳bind MysqlDatabase.Options error: validate MysqlDatabase.Options.Port error: less than min
    

    更多特性请参考说明: go-spring-projects/go-spring

    最后还要感谢原作者lvan100的辛勤付出。

    111 条回复    2023-11-04 11:17:41 +08:00
    1  2  
    banmuyutian
        1
    banmuyutian  
       176 天前   ❤️ 22
    Go,Spring,轻装上阵,这三个词同时出现有点难绷……
    iold
        2
    iold  
       176 天前   ❤️ 1
    Spring 轻吗
    yazinnnn0
        3
    yazinnnn0  
       176 天前
    讲道理, 只说 spring 是轻量的, 当初 spring 开发出来就是为了取代重量级框架 EJB
    sunny352787
        4
    sunny352787  
       176 天前
    为啥要整这么复杂? gin 的路由不好用吗? go 又没有注解,你是不是还得自己加个依赖注入啥的?可别整这么一大坨了,就简单写几个 func 处理逻辑挺好
    biu7
        5
    biu7  
       176 天前
    你这项目名看上去就不轻装
    limpo
        6
    limpo  
    OP
       176 天前
    @banmuyutian @iold @yazinnnn0 相对原版 go-spring 确实是轻装了许多,精简了很多,只保留了核心的依赖注入相关的东西,而这部分代码是很轻量级的,也才几千行 😀
    limpo
        7
    limpo  
    OP
       176 天前
    @biu7 哈哈 因为确实从 Java Spring 启发而来的 😂
    limpo
        8
    limpo  
    OP
       176 天前
    @sunny352787 简单的项目确实不需要它,直接撸就行,当你的项目有一定规模的时候,这个时候维护依赖初始化,以及各种属性初始化,就会变得繁琐,也许为了加一个配置参数,要修改很多相关的多个接口
    ixx
        9
    ixx  
       176 天前
    这东西应该只对 java 转 go 的友好但也是一种限制
    毕竟 原生写 go 的人应该都不习惯 java 这一套
    所以用习惯了自己觉得会 go 了 可能一看别人代码还是会懵 毕竟实现思路不一样
    xiaocaiji111
        10
    xiaocaiji111  
       176 天前
    啊这,我还是用 spring 吧,轻量级项目用 gin 甚至不用框架都搞定了。
    abcdexx
        11
    abcdexx  
       176 天前   ❤️ 12
    g...g.... gava?
    tinyzilan123
        12
    tinyzilan123  
       176 天前
    可以改名成 go-summer
    简称还是 gs
    Oktfolio
        13
    Oktfolio  
       176 天前
    Java 警告
    limpo
        14
    limpo  
    OP
       176 天前
    @ixx 是的,我开始接触到依赖注入这个概念的时候,大呼魔法啊,怎么加个注解对象就有了,后来一顿摸索发现这个东西真的是写业务的神器啊,可以全身贯注到业务上去而不必费心依赖/配置怎么传,或者初始化来
    ZZ74
        15
    ZZ74  
       176 天前
    写 Go 不都是奔着 轻快 去的的么....来个 spring 怎么说呢 应该会有意见吧
    limpo
        16
    limpo  
    OP
       176 天前
    @xiaocaiji111 是的 Java 里面有很多好东西,比如 netty 的流水线思想,所以我也搞了一个 go-netty ,https://github.com/go-netty/go-netty 😂
    limpo
        17
    limpo  
    OP
       176 天前
    @ZZ74 不矛盾啊,Go 也有写大规模项目的需求,小规模的项目自己随便弄都行,上了规模它的好处就显现出来了
    limpo
        18
    limpo  
    OP
       176 天前
    @Oktfolio 真香警告 😀
    limpo
        19
    limpo  
    OP
       176 天前
    @tinyzilan123 还是你逗 😂,取名真的很困难,这不是为了沾 Java 的光😂
    Maboroshii
        20
    Maboroshii  
       176 天前 via Android
    是写一个小功能至少要写三个类的啰嗦写法吗(没写过 java 不太清楚,但是我同事 java 转 go 就是这么啰嗦。。
    lsk569937453
        21
    lsk569937453  
       176 天前
    有没有可能 go 不需要依赖注入。。。
    TypeErrorNone
        22
    TypeErrorNone  
       176 天前
    哈哈哈哈哈,为啥不继续写 Java ?
    Smilencer
        23
    Smilencer  
       176 天前
    这。。。啊。。。大道至简。。。
    danbai
        24
    danbai  
       176 天前
    要是我写的项目用这玩意,我直接给他删库了。
    xingjue
        25
    xingjue  
       176 天前
    gava 警告?
    limpo
        26
    limpo  
    OP
       176 天前
    @Maboroshii 是的你说的没错,简单的东西确实没有必要,过度抽象也是没有必要的,还是要合理运用
    limpo
        27
    limpo  
    OP
       176 天前
    @lsk569937453 萝卜白菜各有所需嘛
    limpo
        28
    limpo  
    OP
       176 天前
    @TypeErrorNone 我真不是 Java 转过来的,纯就是馋 Java 的生态,但是又不想入 Java 的坑 😂
    limpo
        29
    limpo  
    OP
       176 天前
    @danbai 萝卜白菜各有所爱,也许有人也喜欢呢 😀
    limpo
        30
    limpo  
    OP
       176 天前
    @xingjue 哈哈,不想入坑 Java 所以就整这么个东西
    to2false
        31
    to2false  
       176 天前
    @danbai +1
    tietou
        32
    tietou  
       176 天前
    看名字 就感觉不是很轻装
    asdfa
        33
    asdfa  
       176 天前
    🐶🐸
    limpo
        34
    limpo  
    OP
       176 天前
    @tietou 核心代码就几千行,不是 Java 那种以万行计数的庞然大物哈
    me1onsoda
        35
    me1onsoda  
       176 天前   ❤️ 1
    Javaer 又来嚯嚯其他语言了
    Dogtler
        36
    Dogtler  
       176 天前   ❤️ 1
    Javaer 又来嚯嚯其他语言了
    照搬 Java 那套 明显违背了 Go 哲学 Kiss 原则
    mightybruce
        37
    mightybruce  
       176 天前
    关键是 go 实现依赖注入很简单, 不少人或公司可以自己实现了,你除非有什么特别之处很难流行。
    社区中类似 spring 的 是 uber 的 fx 。
    这个更好,直接让 aop 和 gin 集成
    https://github.com/shenyisyn/goft-gin
    limpo
        38
    limpo  
    OP
       176 天前
    @Dogtler @me1onsoda 我还真不是 Javaer 转过来的,Go 老大一开始还说不要泛型呢,扭扭咧咧还不是出来了,Spring 确实有很多值得学习的东西,同样的还有 netty ,其流水线装配设计,也是它能一统 Java 网络库江山的核心吸引力之一
    miniliuke
        39
    miniliuke  
       176 天前
    @abcdexx jovo
    limpo
        40
    limpo  
    OP
       176 天前
    @mightybruce 是的,uber fx ,google wire ,等众多依赖注入方案都有自己的特点,这也是提供更多的选择给 Gopher 们,毕竟萝卜白菜各有所爱 😀
    winglight2016
        41
    winglight2016  
       176 天前
    现在的 spring 已经超过当初的 EJB 多少倍了,哪里还有轻这个概念,IoC 也不是什么好事儿,严重降低代码的可阅读性。

    函数式编程已经抛弃了 OOP 的封装,lz 又当成个宝捡回来。。。
    mightybruce
        42
    mightybruce  
       176 天前
    我目前简单用用就用的是这个, 我自己还改过一些代码
    https://github.com/shenyisyn/goft-ioc
    yangzzz
        43
    yangzzz  
       176 天前
    一个疑问?既然要这么做为啥不用 java 去开发呢😅
    mightybruce
        44
    mightybruce  
       176 天前
    我老大说过,写业务代码用这个其实就是用来方便控制人的。spring 让写代码的变得没有技术含量, 只关心业务。这个在 go 主力的中间件和云原生领域不好使。
    limpo
        45
    limpo  
    OP
       176 天前
    @winglight2016 我同意你说的降低代码可读性,鱼和熊掌确实很难同时得到,至于函数式/OOP 之争也不是这几年才有的,虽然但是就个人开发体验来说还是挺好用的,至于路线之争也不会马上就出结果不是吗
    limpo
        46
    limpo  
    OP
       176 天前
    @mightybruce 我先赞一个,后来学习一下~
    FreeEx
        47
    FreeEx  
       176 天前
    lbp0200
        48
    lbp0200  
       176 天前
    万物皆可 spring ,等 PHP-spring ,Python-spring ,……
    dilu
        49
    dilu  
       176 天前
    哥,求求你们这群 java 别来卷 Go 啊,因为我真的是做 Go 的啊 wuwuwu

    我就想到点下班怎么就这么难啊 doge
    limpo
        50
    limpo  
    OP
       176 天前
    @mightybruce 也许大部分的程序员工作内容也没有太多技术含量,这部分始终还是需要人去做不是吗,那我的工作就有意义了,go-spring 确实不是为中间件,基础库准备的,它是纯粹的业务框架,用来给堆业务代码的人提供方便。
    limpo
        51
    limpo  
    OP
       176 天前
    @FreeEx wire 是代码是生成的方式,go-spring 是基于反射的方式,支持更多丰富的特性
    limpo
        52
    limpo  
    OP
       176 天前
    @dilu 我不是 Javaer ,我是纯正 Gopher ,go-spring 就是为了让大家早点下班才推出的啊 😂
    limpo
        53
    limpo  
    OP
       176 天前
    @lbp0200 有没有可能他们不叫 XX-spring 但是也实现了 spring 的思想呢
    sadfQED2
        54
    sadfQED2  
       176 天前 via Android
    我写过 2 年多 java ,现在写了 3 年多 go 。go 语言本身不支持注解那一套东西,go 的设计理念也不推崇 spring 那一套。

    强行实现 java 那一套,我觉得纯粹是影响性能,恶心后续接手的人。
    securityCoding
        55
    securityCoding  
       176 天前
    java 写了 5 年,go2 年 体验下来就是我再也不想写 spring 那套东西 , 代码这东西啊简单直观永远是最优解
    mcfog
        56
    mcfog  
       176 天前 via Android
    google wire 和 uber dig 按 XP 选一个就行
    yangxiaopeipei
        57
    yangxiaopeipei  
       176 天前
    @lbp0200 php 用 laravel
    limpo
        58
    limpo  
    OP
       176 天前
    @sadfQED2 go-spring 这一套机制只工作在初始化层,不至于影响性能
    limpo
        59
    limpo  
    OP
       176 天前
    @securityCoding 但是不得不承认确实也带来很多方便,不然你看现在很多 javaer 离开 spring 都不知道怎么写程序了😂
    limpo
        60
    limpo  
    OP
       176 天前
    @mcfog 要是他俩都看不上眼,也可以来瞧瞧 go-spring 哈,万一看对眼了呢 😀
    sadfQED2
        61
    sadfQED2  
       176 天前 via Android
    @limpo 大项目中,初始化性能也跟重要,假设你十多个集群,每个集群几百台机器,启动慢的话部署一次要老命
    Leprax
        62
    Leprax  
       176 天前   ❤️ 1
    > 多年前曾经梦想着能有一天能在 Go 语言中使用上 Spring 那样强大的框架

    你的梦想很恐怖
    limpo
        63
    limpo  
    OP
       176 天前
    @sadfQED2 我这边启动速度以几十毫秒计,主要慢得地方主要是注册服务到 zk 里面比较慢
    limpo
        64
    limpo  
    OP
       176 天前
    @Leprax 至少现在还是美梦,也许以后会变,谁知道呢
    nigerunndayo
        65
    nigerunndayo  
       176 天前
    @limpo 是这样的
    kirito41dd
        66
    kirito41dd  
       176 天前
    还好我 go 转 rust 了,领先一个版本,不然被卷进去了(玩笑
    Rehtt
        67
    Rehtt  
       176 天前
    反射式注入。反射一时爽,性能火葬场
    limpo
        68
    limpo  
    OP
       176 天前
    @Rehtt 只有初始化阶段是使用反射的
    limpo
        69
    limpo  
    OP
       176 天前
    @kirito41dd 从入门到放弃 3 次了,每次都是在异步一块就看不下去了😂
    kirito41dd
        70
    kirito41dd  
       176 天前
    @limpo 你找一些 futrure 展开的样子看看就明白了,runtime 本质就是个事件驱动模型,tokio 下面是 mio ,和 c libuv libevent 啥的如出一辙,直接入就完了
    Miy4mori
        71
    Miy4mori  
       176 天前
    不要再嚯嚯我们 Java 码农了,我们真不会试图把 Spring 带去别的社区
    qingshui33
        72
    qingshui33  
       176 天前
    @abcdexx 我要笑死了这个 😂
    limpo
        73
    limpo  
    OP
       176 天前
    @kirito41dd 多谢哈,隔一段时间我还会重新去捡一遍,期待我的第四轮😀
    limpo
        74
    limpo  
    OP
       176 天前
    @Miy4mori 虽然但是写业务是真香啊
    beidounanxizi
        75
    beidounanxizi  
       176 天前
    @limpo netty 是因为原生 IO 库写的垃圾
    yrzs
        76
    yrzs  
       176 天前
    怎么名字听上去不怎么轻量,哈哈
    SoyaDokio
        77
    SoyaDokio  
       176 天前
    老实交代,README 是不是英文写了然后机翻中文的(笑
    snowlyg
        78
    snowlyg  
       176 天前
    都用 go 了还搞什么 spring?
    limpo
        79
    limpo  
    OP
       176 天前
    @beidounanxizi 语言一般只提供底层接口,上层还是要自己做的,netty 不仅仅解决了原生接口的 bug ,也提供了应用层封装,这才使得网络开发变得容易
    limpo
        80
    limpo  
    OP
       176 天前
    @yrzs 这名字就是为了沾光 😂
    limpo
        81
    limpo  
    OP
       176 天前
    @SoyaDokio 还真是,有空我去润色一下
    limpo
        82
    limpo  
    OP
       176 天前
    @snowlyg 主要是依赖注入这块,不仅仅是 spring 独有的,go 里面也有 uber fx, google wire 都是依赖注入思想的实现
    Vegetable
        83
    Vegetable  
       176 天前
    去看了一下代码,看到在 go 文件中 NewBean()真的是蚌埠住了,写 java 不好吗
    beidounanxizi
        84
    beidounanxizi  
       176 天前   ❤️ 1
    java 恨不得把什么预言都写成一股浓浓の java 味道
    limpo
        85
    limpo  
    OP
       176 天前
    @Vegetable 没有注解确实是用起来不像 Java 那么丝滑,虽然但是我还是喜欢写 Go 😄
    beidounanxizi
        86
    beidounanxizi  
       176 天前
    @limpo 那你用过 go sdk 自带 java 库 还不够好用吗?
    limpo
        87
    limpo  
    OP
       176 天前
    @beidounanxizi 我也不喜欢过度抽象,太琐碎了,go-spring 只是解决了依赖注入,不是一定要按照 Java 那种层层封装的风格去写代码
    Cheivin
        88
    Cheivin  
       176 天前
    @abcdexx jojo ,dio
    Cheivin
        89
    Cheivin  
       176 天前
    @tinyzilan123 我觉得还可以 go-winter ,让每个人感受寒冬
    limpo
        90
    limpo  
    OP
       176 天前
    @beidounanxizi 啥,go sdk 自带 Java 库?真没用过
    dongya
        91
    dongya  
       176 天前
    别来卷 go 了,
    jyhmijack
        92
    jyhmijack  
       176 天前
    我们有套用 java spring 写的业务,还在招人用 go 重写一遍呢……https://v2ex.com/t/987054
    brookegas
        93
    brookegas  
       176 天前
    别背着臭不可闻的 Java 屎山来污染 Go 了
    真的,有空好好研究一下 Go
    Spring 码农自以为得意的所谓“框架”,在 Go 面前就像个 200 斤涂着腮红的肥佬

    要不然,你们去祸害 php ,整出一个 Php-Spring ?
    fgwmlhdkkkw
        94
    fgwmlhdkkkw  
       176 天前
    golang 没有装饰器,全写在 tag 里好变态呀……
    zagfai
        95
    zagfai  
       176 天前
    disgusting
    Masoud2023
        96
    Masoud2023  
       176 天前
    我真害怕你明天把 tomcat 拿 go 实现一遍。

    语言跟语言是不一样的,Go 是自带单例的,喜欢依赖注入方便测试就多做点 interface ,封装点 struct 然后统一注入进去。

    拿框架去实现这种东西是要有性能成本的,反射都是要花 CPU 的,换语言不是把原来的思想原封不动换个语言写,你要适应新的编程思想。
    wu67
        97
    wu67  
       176 天前
    快进到 js-spring. 噢, 原来有 nest.js 啦, 那没事了 /dog
    whyso
        98
    whyso  
       176 天前
    等一个 rust-spring
    lifespy
        99
    lifespy  
       176 天前
    别这样,你们给楼主打击到了😂支持社区开花,不管是什么花
    meshell
        100
    meshell  
       176 天前
    @brookegas symfony 就是 php-spring
    1  2  
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2503 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 35ms · UTC 16:02 · PVG 00:02 · LAX 09:02 · JFK 12:02
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.