Skip to content

optimizes code comments #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 104 additions & 43 deletions gs/examples/bookman/.cover/cover.html

Large diffs are not rendered by default.

Empty file added gs/examples/bookman/README.md
Empty file.
4 changes: 4 additions & 0 deletions gs/examples/bookman/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ func init() {
gs.Banner(banner)
}

// init sets the working directory of the application to the directory
// where this source file resides.
// This ensures that any relative file operations are based on the source file location,
// not the process launch path.
func init() {
var execDir string
_, filename, _, ok := runtime.Caller(0)
Expand Down
8 changes: 7 additions & 1 deletion gs/examples/bookman/src/app/bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ func initRemoteConfig() error {
if err := getRemoteConfig(); err != nil {
return err
}
// Register a function job to refresh the configuration.
// A bean can be registered into the app during the bootstrap phase.
gs.FuncJob(refreshRemoteConfig)
return nil
}
Expand Down Expand Up @@ -74,14 +76,18 @@ func getRemoteConfig() error {
func refreshRemoteConfig(ctx context.Context) error {
for {
select {
case <-ctx.Done():
case <-ctx.Done(): // Gracefully exit when context is canceled.
// The context (ctx) is derived from the app instance.
// When the app exits, the context is canceled,
// allowing the loop to terminate gracefully.
fmt.Println("config updater exit")
return nil
case <-time.After(time.Millisecond * 500):
if err := getRemoteConfig(); err != nil {
fmt.Println("get remote config error:", err)
return err
}
// Refreshes the app configuration.
if err := gs.RefreshProperties(); err != nil {
fmt.Println("refresh properties error:", err)
return err
Expand Down
18 changes: 16 additions & 2 deletions gs/examples/bookman/src/app/common/handlers/log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,43 @@ import (
)

func init() {
// Register a group of Bean definitions during application initialization.
// GroupRegister dynamically creates multiple Beans based on the configuration (conf.Properties),
// making it ideal for scenarios like setting up multiple loggers, clients, or resources from config.
gs.GroupRegister(func(p conf.Properties) ([]*gs.BeanDefinition, error) {

var loggers map[string]struct {
Name string `value:"${name}"`
Dir string `value:"${dir}"`
Name string `value:"${name}"` // Log file name
Dir string `value:"${dir}"` // Directory where the log file will be stored
}

// Bind configuration from the "${log}" node into the 'loggers' map.
err := p.Bind(&loggers, "${log}")
if err != nil {
return nil, err
}

var ret []*gs.BeanDefinition
for k, l := range loggers {
var (
f *os.File
flag = os.O_WRONLY | os.O_CREATE | os.O_APPEND
)

// Open (or create) the log file
f, err = os.OpenFile(filepath.Join(l.Dir, l.Name), flag, os.ModePerm)
if err != nil {
return nil, err
}

// Create a new slog.Logger instance with a text handler writing to the file
o := slog.New(slog.NewTextHandler(f, nil))

// Wrap the logger into a Bean with a destroy hook to close the file
b := gs.NewBean(o).Name(k).Destroy(func(_ *slog.Logger) {
_ = f.Close()
})

ret = append(ret, b)
}
return ret, nil
Expand Down
4 changes: 3 additions & 1 deletion gs/examples/bookman/src/app/common/httpsvr/httpsvr.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ func init() {
)
}

// NewServeMux Creates a new HTTP request multiplexer and registers
// NewServeMux creates a new HTTP request multiplexer and registers
// routes with access logging middleware.
func NewServeMux(c *controller.Controller, logger *slog.Logger) *http.ServeMux {
mux := http.NewServeMux()
proto.RegisterRouter(mux, c, Access(logger))

// Users can customize routes by adding handlers to the mux
mux.Handle("GET /", http.FileServer(http.Dir("./public")))
return mux
}
Expand Down
4 changes: 4 additions & 0 deletions gs/examples/bookman/src/app/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ func init() {
gs.Object(&Controller{})
}

// Controller implements the controller interface defined in the idl package.
// In practice, controller methods can be grouped into different controllers.
// Each sub-controller can have its own dependencies and be tested independently,
// making the codebase more modular and maintainable.
type Controller struct {
BookController
}
8 changes: 6 additions & 2 deletions gs/examples/bookman/src/biz/job/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,15 @@ type Job struct{}
func (x *Job) Run(ctx context.Context) error {
for {
select {
case <-ctx.Done(): // Gracefully exit when context is canceled
case <-ctx.Done():
// Gracefully exit when the context is canceled
fmt.Println("job exit")
return nil
default:
if gs.Exiting() { // Check if the system is shutting down
// Check if the app is shutting down.
// In long-running background tasks, checking for shutdown signals
// during idle periods or between stages helps ensure timely resource cleanup.
if gs.Exiting() {
return nil
}
time.Sleep(time.Millisecond * 300)
Expand Down
6 changes: 6 additions & 0 deletions gs/examples/bookman/src/idl/http/proto/proto.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@
* limitations under the License.
*/

// Package proto defines the interfaces and route registrations generated from IDL files.
package proto

import (
"net/http"
)

// Book represents the structure of a book entity.
type Book struct {
Title string `json:"title"`
Author string `json:"author"`
Expand All @@ -29,13 +31,17 @@ type Book struct {
RefreshTime string `json:"refreshTime"`
}

// Controller defines the service interface for book-related operations.
type Controller interface {
ListBooks(w http.ResponseWriter, r *http.Request)
GetBook(w http.ResponseWriter, r *http.Request)
SaveBook(w http.ResponseWriter, r *http.Request)
DeleteBook(w http.ResponseWriter, r *http.Request)
}

// RegisterRouter registers the HTTP routes for the Controller interface.
// It maps each method to its corresponding HTTP endpoint,
// and applies the given middleware (wrap) to each handler.
func RegisterRouter(mux *http.ServeMux, c Controller, wrap func(next http.Handler) http.Handler) {
mux.Handle("GET /books", wrap(http.HandlerFunc(c.ListBooks)))
mux.Handle("GET /books/{isbn}", wrap(http.HandlerFunc(c.GetBook)))
Expand Down
8 changes: 8 additions & 0 deletions gs/examples/miniapi/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,17 @@ import (
)

func main() {
// Register an HTTP handler for the "/echo" endpoint.
http.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("hello world!"))
})

// Start the Go-Spring framework.
// Compared to http.ListenAndServe, gs.Run() starts a full-featured application context with:
// - Auto Configuration: Automatically loads properties and beans.
// - Property Binding: Binds external configs (YAML, ENV) into structs.
// - Dependency Injection: Wires beans automatically.
// - Dynamic Refresh: Updates configs at runtime without restart.
gs.Run()
}

Expand Down
1 change: 1 addition & 0 deletions gs/examples/noweb/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
)

func main() {
// Disable the built-in HTTP service.
gs.Web(false).Run()
}

Expand Down
Empty file added gs/examples/startup/README.md
Empty file.
22 changes: 14 additions & 8 deletions gs/gs.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,35 +48,38 @@ func As[T any]() reflect.Type {
type Arg = gs.Arg

// TagArg returns a TagArg with the specified tag.
// Used for property binding or object injection when providing constructor parameters.
func TagArg(tag string) Arg {
return gs_arg.Tag(tag)
}

// ValueArg returns a ValueArg with the specified value.
// Used to provide specific values for constructor parameters.
func ValueArg(v interface{}) Arg {
return gs_arg.Value(v)
}

// IndexArg returns an IndexArg with the specified index and argument.
// When most constructor parameters can use default values, IndexArg helps reduce configuration effort.
func IndexArg(n int, arg Arg) Arg {
return gs_arg.Index(n, arg)
}

// BindArg returns an BindArg for the specified function and arguments.
// BindArg returns a BindArg for the specified function and arguments.
// Used to provide argument binding for option-style constructor parameters.
func BindArg(fn interface{}, args ...Arg) *gs_arg.BindArg {
return gs_arg.Bind(fn, args...)
}

/************************************ cond ***********************************/

type (
CondFunc = gs.CondFunc
Condition = gs.Condition
CondContext = gs.CondContext
)

// OnFunc creates a Condition based on the provided function.
func OnFunc(fn CondFunc) Condition {
func OnFunc(fn func(ctx CondContext) (bool, error)) Condition {
return gs_cond.OnFunc(fn)
}

Expand Down Expand Up @@ -137,6 +140,11 @@ func None(conditions ...Condition) Condition {

/************************************ ioc ************************************/

type (
BeanID = gs.BeanID
BeanMock = gs.BeanMock
)

type (
Dync[T any] = gs_dync.Value[T]
)
Expand Down Expand Up @@ -201,21 +209,19 @@ type AppStarter struct{}

// Web enables or disables the built-in web server.
func Web(enable bool) *AppStarter {
if !enable {
EnableSimpleHttpServer(false)
}
EnableSimpleHttpServer(enable)
return &AppStarter{}
}

// Run runs the app and waits for an interrupt signal to exit.
func (s *AppStarter) Run() {
printBanner()
var err error
defer func() {
if err != nil {
syslog.Errorf("app run failed: %s", err.Error())
}
}()
printBanner()
if err = B.(*gs_app.BootImpl).Run(); err != nil {
return
}
Expand Down Expand Up @@ -274,7 +280,7 @@ func GroupRegister(fn func(p conf.Properties) ([]*BeanDefinition, error)) {

// RefreshProperties refreshes the app configuration.
func RefreshProperties() error {
p, err := Config().Refresh()
p, err := gs_app.GS.P.Refresh()
if err != nil {
return err
}
Expand Down
10 changes: 7 additions & 3 deletions gs/gstest/gstest.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (

"github.com/go-spring/spring-core/gs"
"github.com/go-spring/spring-core/gs/internal/gs_app"
"github.com/lvan100/go-assert"
)

func init() {
Expand All @@ -50,7 +49,10 @@ func MockFor[T any](name ...string) BeanMock[T] {

// With registers a mock bean.
func (m BeanMock[T]) With(obj T) {
gs_app.GS.C.Mock(obj, m.selector)
gs_app.GS.C.AddMock(gs.BeanMock{
Object: obj,
Target: m.selector,
})
}

type runArg struct {
Expand Down Expand Up @@ -110,6 +112,8 @@ func Get[T any](t *testing.T) T {
// Wire injects dependencies into the object.
func Wire[T any](t *testing.T, obj T) T {
err := gs_app.GS.C.Wire(obj)
assert.Nil(t, err)
if err != nil {
t.Fatal(err)
}
return obj
}
7 changes: 7 additions & 0 deletions gs/gstest/gstest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,19 @@ func TestMain(m *testing.M) {
}

func TestGSTest(t *testing.T) {
// The dao.Dao object was not successfully created,
// and the corresponding injection will also fail.
// The following log will be printed on the console:
// autowire error: TagArg::GetArgValue error << bind path=string type=string error << property dao.addr not exist

a := gstest.Get[*app.App](t)
assert.That(t, a.Name).Equal("test")

s := gstest.Wire(t, new(struct {
App *app.App `autowire:""`
Service *biz.Service `autowire:""`
}))
assert.Nil(t, s.Service.Dao)
assert.That(t, s.App.Name).Equal("test")
assert.That(t, s.Service.Hello("xyz")).Equal("hello xyz")
}
4 changes: 1 addition & 3 deletions gs/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,12 @@ import (
"net"
"net/http"
"time"

"github.com/go-spring/spring-core/gs/internal/gs_cond"
)

func init() {
// Register the default ServeMux as a bean if no other ServeMux instance exists
Object(http.DefaultServeMux).Condition(
gs_cond.OnMissingBean[*http.ServeMux](),
OnMissingBean[*http.ServeMux](),
OnProperty(EnableSimpleHttpServerProp).HavingValue("true").MatchIfMissing(),
)

Expand Down
9 changes: 6 additions & 3 deletions gs/internal/gs/gs.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,6 @@ type CondContext interface {
Find(s BeanSelector) ([]CondBean, error)
}

// CondFunc is a function type that determines whether a condition is satisfied.
type CondFunc func(ctx CondContext) (bool, error)

/************************************* arg ***********************************/

// Arg is an interface for retrieving argument values in function parameter binding.
Expand Down Expand Up @@ -159,6 +156,12 @@ type Server interface {

/*********************************** bean ************************************/

// BeanMock defines a mock object and its target bean selector for overriding.
type BeanMock struct {
Object interface{} // Mock instance to replace the target bean
Target BeanSelector // Selector to identify the target bean
}

// BeanID represents the unique identifier for a bean.
type BeanID struct {
Type reflect.Type
Expand Down
4 changes: 2 additions & 2 deletions gs/internal/gs_cond/cond.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ import (
// onFunc is an implementation of [gs.Condition] that wraps a function.
// It allows a condition to be evaluated based on the result of a function.
type onFunc struct {
fn gs.CondFunc
fn func(ctx gs.CondContext) (bool, error)
}

// OnFunc creates a Conditional that evaluates using a custom function.
func OnFunc(fn gs.CondFunc) gs.Condition {
func OnFunc(fn func(ctx gs.CondContext) (bool, error)) gs.Condition {
return &onFunc{fn: fn}
}

Expand Down
Loading
Loading