• fx 源码阅读


    fx

    fx 为 uber 提供的一个 控制反转和自动化装配的golang语言的解决方案

    demo 示例

    示例:

    package main
    
    import (
    	"context"
    	"log"
    	"net/http"
    	"os"
    	"time"
    
    	"go.uber.org/fx"
    )
    
    // NewLogger constructs a logger. It's just a regular Go function, without any
    // special relationship to Fx.
    //
    // Since it returns a *log.Logger, Fx will treat NewLogger as the constructor
    // function for the standard library's logger. (We'll see how to integrate
    // NewLogger into an Fx application in the main function.) Since NewLogger
    // doesn't have any parameters, Fx will infer that loggers don't depend on any
    // other types - we can create them from thin air.
    //
    // Fx calls constructors lazily, so NewLogger will only be called only if some
    // other function needs a logger. Once instantiated, the logger is cached and
    // reused - within the application, it's effectively a singleton.
    //
    // By default, Fx applications only allow one constructor for each type. See
    // the documentation of the In and Out types for ways around this restriction.
    func NewLogger() *log.Logger {
    	logger := log.New(os.Stdout, "" /* prefix */, 0 /* flags */)
    	logger.Print("Executing NewLogger.")
    	return logger
    }
    
    // NewHandler constructs a simple HTTP handler. Since it returns an
    // http.Handler, Fx will treat NewHandler as the constructor for the
    // http.Handler type.
    //
    // Like many Go functions, NewHandler also returns an error. If the error is
    // non-nil, Go convention tells the caller to assume that NewHandler failed
    // and the other returned values aren't safe to use. Fx understands this
    // idiom, and assumes that any function whose last return value is an error
    // follows this convention.
    //
    // Unlike NewLogger, NewHandler has formal parameters. Fx will interpret these
    // parameters as dependencies: in order to construct an HTTP handler,
    // NewHandler needs a logger. If the application has access to a *log.Logger
    // constructor (like NewLogger above), it will use that constructor or its
    // cached output and supply a logger to NewHandler. If the application doesn't
    // know how to construct a logger and needs an HTTP handler, it will fail to
    // start.
    //
    // Functions may also return multiple objects. For example, we could combine
    // NewHandler and NewLogger into a single function:
    //
    //   func NewHandlerAndLogger() (*log.Logger, http.Handler, error)
    //
    // Fx also understands this idiom, and would treat NewHandlerAndLogger as the
    // constructor for both the *log.Logger and http.Handler types. Just like
    // constructors for a single type, NewHandlerAndLogger would be called at most
    // once, and both the handler and the logger would be cached and reused as
    // necessary.
    func NewHandler(logger *log.Logger) (http.Handler, error) {
    	logger.Print("Executing NewHandler.")
    	return http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
    		logger.Print("Got a request.")
    	}), nil
    }
    
    // NewMux constructs an HTTP mux. Like NewHandler, it depends on *log.Logger.
    // However, it also depends on the Fx-specific Lifecycle interface.
    //
    // A Lifecycle is available in every Fx application. It lets objects hook into
    // the application's start and stop phases. In a non-Fx application, the main
    // function often includes blocks like this:
    //
    //   srv, err := NewServer() // some long-running network server
    //   if err != nil {
    //     log.Fatalf("failed to construct server: %v", err)
    //   }
    //   // Construct other objects as necessary.
    //   go srv.Start()
    //   defer srv.Stop()
    //
    // In this example, the programmer explicitly constructs a bunch of objects,
    // crashing the program if any of the constructors encounter unrecoverable
    // errors. Once all the objects are constructed, we start any background
    // goroutines and defer cleanup functions.
    //
    // Fx removes the manual object construction with dependency injection. It
    // replaces the inline goroutine spawning and deferred cleanups with the
    // Lifecycle type.
    //
    // Here, NewMux makes an HTTP mux available to other functions. Since
    // constructors are called lazily, we know that NewMux won't be called unless
    // some other function wants to register a handler. This makes it easy to use
    // Fx's Lifecycle to start an HTTP server only if we have handlers registered.
    func NewMux(lc fx.Lifecycle, logger *log.Logger) *http.ServeMux {
    	logger.Print("Executing NewMux.")
    	// First, we construct the mux and server. We don't want to start the server
    	// until all handlers are registered.
    	mux := http.NewServeMux()
    	server := &http.Server{
    		Addr:    ":8080",
    		Handler: mux,
    	}
    	// If NewMux is called, we know that another function is using the mux. In
    	// that case, we'll use the Lifecycle type to register a Hook that starts
    	// and stops our HTTP server.
    	//
    	// Hooks are executed in dependency order. At startup, NewLogger's hooks run
    	// before NewMux's. On shutdown, the order is reversed.
    	//
    	// Returning an error from OnStart hooks interrupts application startup. Fx
    	// immediately runs the OnStop portions of any successfully-executed OnStart
    	// hooks (so that types which started cleanly can also shut down cleanly),
    	// then exits.
    	//
    	// Returning an error from OnStop hooks logs a warning, but Fx continues to
    	// run the remaining hooks.
    	lc.Append(fx.Hook{
    		// To mitigate the impact of deadlocks in application startup and
    		// shutdown, Fx imposes a time limit on OnStart and OnStop hooks. By
    		// default, hooks have a total of 15 seconds to complete. Timeouts are
    		// passed via Go's usual context.Context.
    		OnStart: func(context.Context) error {
    			logger.Print("Starting HTTP server.")
    			// In production, we'd want to separate the Listen and Serve phases for
    			// better error-handling.
    			go server.ListenAndServe()
    			return nil
    		},
    		OnStop: func(ctx context.Context) error {
    			logger.Print("Stopping HTTP server.")
    			return server.Shutdown(ctx)
    		},
    	})
    
    	return mux
    }
    
    // Register mounts our HTTP handler on the mux.
    //
    // Register is a typical top-level application function: it takes a generic
    // type like ServeMux, which typically comes from a third-party library, and
    // introduces it to a type that contains our application logic. In this case,
    // that introduction consists of registering an HTTP handler. Other typical
    // examples include registering RPC procedures and starting queue consumers.
    //
    // Fx calls these functions invocations, and they're treated differently from
    // the constructor functions above. Their arguments are still supplied via
    // dependency injection and they may still return an error to indicate
    // failure, but any other return values are ignored.
    //
    // Unlike constructors, invocations are called eagerly. See the main function
    // below for details.
    func Register(mux *http.ServeMux, h http.Handler) {
    	mux.Handle("/", h)
    }
    
    func main() {
    	app := fx.New(
    		// Provide all the constructors we need, which teaches Fx how we'd like to
    		// construct the *log.Logger, http.Handler, and *http.ServeMux types.
    		// Remember that constructors are called lazily, so this block doesn't do
    		// much on its own.
    		fx.Provide(
    			NewLogger,
    			NewHandler,
    			NewMux,
    		),
    		// Since constructors are called lazily, we need some invocations to
    		// kick-start our application. In this case, we'll use Register. Since it
    		// depends on an http.Handler and *http.ServeMux, calling it requires Fx
    		// to build those types using the constructors above. Since we call
    		// NewMux, we also register Lifecycle hooks to start and stop an HTTP
    		// server.
    		fx.Invoke(Register),
    	)
    
    	// In a typical application, we could just use app.Run() here. Since we
    	// don't want this example to run forever, we'll use the more-explicit Start
    	// and Stop.
    	startCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
    	defer cancel()
    	if err := app.Start(startCtx); err != nil {
    		log.Fatal(err)
    	}
    
    	// Normally, we'd block here with <-app.Done(). Instead, we'll make an HTTP
    	// request to demonstrate that our server is running.
    	http.Get("http://localhost:8080/")
    
    	stopCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
    	defer cancel()
    	if err := app.Stop(stopCtx); err != nil {
    		log.Fatal(err)
    	}
    
    }
    

    核心代码分析

    核心方法

    fx.Provide

    提供构造函数

    func Provide(constructors ...interface{}) Option {
    	return provideOption{
    		Targets: constructors,
    		Stack:   fxreflect.CallerStack(1, 0),
    	}
    }
    

    fx.Invoke

    声明目标函数

    func Invoke(funcs ...interface{}) Option {
    	return invokeOption{
    		Targets: funcs,
    		Stack:   fxreflect.CallerStack(1, 0),
    	}
    }
    

    fx.New

    根据provide,invoke构造依赖关系图,
    执行 invoke 里面的类

    func New(opts ...Option) *App {
    	logger := fxlog.New()
    	lc := &lifecycleWrapper{lifecycle.New(logger)}
    
    	app := &App{
    		lifecycle:    lc,
    		logger:       logger,
    		startTimeout: DefaultTimeout,
    		stopTimeout:  DefaultTimeout,
    	}
    
    	for _, opt := range opts {
    		opt.apply(app)
    	}
    
    	app.container = dig.New(
    		dig.DeferAcyclicVerification(),
    		dig.DryRun(app.validate),
    	)
    
    	for _, p := range app.provides {
    		app.provide(p)
    	}
    
    	frames := fxreflect.CallerStack(0, 0) // include New in the stack for default Provides
    	app.provide(provide{
    		Target: func() Lifecycle { return app.lifecycle },
    		Stack:  frames,
    	})
    	app.provide(provide{Target: app.shutdowner, Stack: frames})
    	app.provide(provide{Target: app.dotGraph, Stack: frames})
    
    	if app.err != nil {
    		app.logger.Printf("Error after options were applied: %v", app.err)
    		return app
    	}
    
    	if err := app.executeInvokes(); err != nil {
    		app.err = err
    
    		if dig.CanVisualizeError(err) {
    			var b bytes.Buffer
    			dig.Visualize(app.container, &b, dig.VisualizeError(err))
    			err = errorWithGraph{
    				graph: b.String(),
    				err:   err,
    			}
    		}
    		errorHandlerList(app.errorHooks).HandleError(err)
    	}
    	return app
    }
    

    app.Start

    // 调用 app.start 启动
    func (app *App) Start(ctx context.Context) error {
    	return withTimeout(ctx, app.start)
    }
    
    //app.start 执行 hook
    func (app *App) start(ctx context.Context) error {
    	if app.err != nil {
    		// Some provides failed, short-circuit immediately.
    		return app.err
    	}
    
    	// Attempt to start cleanly.
    	if err := app.lifecycle.Start(ctx); err != nil {
    		// Start failed, roll back.
    		app.logger.Printf("ERROR		Start failed, rolling back: %v", err)
    		if stopErr := app.lifecycle.Stop(ctx); stopErr != nil {
    			app.logger.Printf("ERROR		Couldn't rollback cleanly: %v", stopErr)
    			return multierr.Append(err, stopErr)
    		}
    		return err
    	}
    
    	app.logger.Printf("RUNNING")
    	return nil
    }
    

    缺点

    1. 写法上感觉不太优雅
    2. 自动获取实例的形式
    3. 在构造函数自动注入的方式可能比较友好
  • 相关阅读:
    Linux集群之间配置NTP时间同步ntp
    CentOS7安装配置MariaDB(mysql)数据主从同步
    常用邮件SMTP POP3服务器地址大全
    为应用创建多个独立python运行环境
    Linux中安装配置KVM虚拟化
    Linux系统管理和调优(内存、CPU、磁盘IO、网络)
    CentOS8Linux中配置网易云网络yum源安装软件
    CentOS7linux通过http配置共享自动创建yum源的shell脚本
    BigDecimal类型比较数字大小
    Double值保留两位小数的四种方法
  • 原文地址:https://www.cnblogs.com/SLchuck/p/14317823.html
Copyright © 2020-2023  润新知