溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Golang中反射怎么應(yīng)用

發(fā)布時間:2022-08-26 10:12:01 來源:億速云 閱讀:133 作者:iii 欄目:開發(fā)技術(shù)

本文小編為大家詳細介紹“Golang中反射怎么應(yīng)用”,內(nèi)容詳細,步驟清晰,細節(jié)處理妥當(dāng),希望這篇“Golang中反射怎么應(yīng)用”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識吧。

    引言

    首先來一段簡單的代碼邏輯熱身,下面的代碼大家覺得應(yīng)該會打印什么呢?

    type OKR struct {
       id      int
       content string
    }
    func getOkrDetail(ctx context.Context, okrId int) (*OKR, *okrErr.OkrErr) {
       return &OKR{id: okrId, content: fmt.Sprint(rand.Int63())}, nil
    }
    func getOkrDetailV2(ctx context.Context, okrId int) (*OKR, okrErr.OkrError) {
       if okrId == 2{
          return nil, okrErr.OKRNotFoundError
       }
       return &OKR{id: okrId, content: fmt.Sprint(rand.Int63())}, nil
    }
    func paperOkrId(ctx context.Context) (int, error){
       return 1, nil
    }
    func Test001(ctx context.Context) () {
       var okr *OKR
       okrId, err := paperOkrId(ctx)
       if err != nil{
          fmt.Println("####   111   ####")
       }
       okr, err = getOkrDetail(ctx, okrId)
       if err != nil {
          fmt.Println("####   222   ####")
       }
       okr, err = getOkrDetailV2(ctx, okrId)
       if err != nil {
          fmt.Println("####   333   ####")
       }
       okr, err = getOkrDetailV2(ctx, okrId + 1)
       if err != nil {
          fmt.Println("####   444   ####")
       }
       fmt.Println("####   555   ####")
       fmt.Printf("%v", okr)
    }
    func main() {
       Test001(context.Background())
    }

    Golang中反射怎么應(yīng)用

    Golang類型設(shè)計原則

    在講反射之前,先來看看 Golang 關(guān)于類型設(shè)計的一些原則

    • 在 Golang 中變量包括(type, value)兩部分

    • 理解這一點就能解決上面的簡單問題了

    • type 包括 static type 和 concrete type. 簡單來說 static type 是你在編碼是看見的類型(如 int、string),concrete type 是 runtime 系統(tǒng)看見的類型。類型斷言能否成功,取決于變量的 concrete type,而不是 static type.

    接下來要說的反射,就是能夠在運行時更新變量和檢查變量的值、調(diào)用變量的方法和變量支持的內(nèi)在操作,而不需要在編譯時就知道這些變量的具體類型。這種機制被稱為反射。Golang 的基礎(chǔ)類型是靜態(tài)的(也就是指定 int、string 這些的變量,它的 type 是 static type),在創(chuàng)建變量的時候就已經(jīng)確定,反射主要與 Golang 的 interface 類型相關(guān)(它的 type 是 concrete type),只有運行時 interface 類型才有反射一說。

    Golang 中為什么要使用反射/什么場景可以(應(yīng)該)使用反射

    當(dāng)程序運行時, 我們獲取到一個 interface 變量, 程序應(yīng)該如何知道當(dāng)前變量的類型,和當(dāng)前變量的值呢?

    當(dāng)然我們可以有預(yù)先定義好的指定類型, 但是如果有一個場景是我們需要編寫一個函數(shù),能夠處理一類共性邏輯的場景,但是輸入類型很多,或者根本不知道接收參數(shù)的類型是什么,或者可能是沒約定好;

    也可能是傳入的類型很多,這些類型并不能統(tǒng)一表示。

    這時反射就會用的上了,典型的例子如:json.Marshal。

    再比如說有時候需要根據(jù)某些條件決定調(diào)用哪個函數(shù),比如根據(jù)用戶的輸入來決定。這時就需要對函數(shù)和函數(shù)的參數(shù)進行反射,在運行期間動態(tài)地執(zhí)行函數(shù)。

    舉例場景:

    比如我們需要將一個 struct 執(zhí)行某種操作(用格式化打印代替),這種場景下我們有多種方式可以實現(xiàn),比較簡單的方式是:switch case

    func Sprint(x interface{}) string {
        type stringer interface {
            String() string
        }
        switch x := x.(type) {
        case stringer:
            return x.String()
        case string:
            return x
        case int:
            return strconv.Itoa(x)
        // int16, uint32...
        case bool:
            if x {
                return "true"
            }
            return "false"
        default:
            return "wrong parameter type"
        }
    }
    type permissionType int64

    但是這種簡單的方法存在一個問題, 當(dāng)增加一個場景時,比如需要對 slice 支持,則需要在增加一個分支,這種增加是無窮無盡的,每當(dāng)我需要支持一種類型,哪怕是自定義類型, 本質(zhì)上是 int64 也仍然需要增加一個分支。

    反射的基本用法

    在 Golang 中為我們提供了兩個方法,分別是 reflect.ValueOf  和 reflect.TypeOf,見名知意這兩個方法分別能幫我們獲取到對象的值和類型。Valueof 返回的是 Reflect.Value 對象,是一個 struct,而 typeof 返回的是 Reflect.Type 是一個接口。我們只需要簡單的使用這兩個進行組合就可以完成多種功能。

    type GetOkrDetailResp struct {
       OkrId   int64
       UInfo   *UserInfo
       ObjList []*ObjInfo
    }
    type ObjInfo struct {
       ObjId int64
       Content string
    }
    type UserInfo struct {
       Name         string
       Age          int
       IsLeader     bool
       Salary       float64
       privateFiled int
    }
    // 利用反射創(chuàng)建struct
    func NewUserInfoByReflect(req interface{})*UserInfo{
      if req == nil{
        return nil
      }
       reqType :=reflect.TypeOf(req)
      if reqType.Kind() == reflect.Ptr{
          reqType = reqType.Elem()
       }
       return reflect.New(reqType).Interface().(*UserInfo)
    }
    // 修改struct 字段值
    func ModifyOkrDetailRespData(req interface{}) {
       reqValue :=reflect.ValueOf(req).Elem()
       fmt.Println(reqValue.CanSet())
       uType := reqValue.FieldByName("UInfo").Type().Elem()
       fmt.Println(uType)
       uInfo := reflect.New(uType)
       reqValue.FieldByName("UInfo").Set(uInfo)
    }
    // 讀取 struct 字段值,并根據(jù)條件進行過濾
    func FilterOkrRespData(reqData interface{}, objId int64){
    // 首先獲取req中obj slice 的value
    for i := 0 ; i < reflect.ValueOf(reqData).Elem().NumField(); i++{
          fieldValue := reflect.ValueOf(reqData).Elem().Field(i)
    if fieldValue.Kind() != reflect.Slice{
    continue
          }
          fieldType := fieldValue.Type() // []*ObjInfo
          sliceType := fieldType.Elem() // *ObjInfo
          slicePtr := reflect.New(reflect.SliceOf(sliceType)) // 創(chuàng)建一個指向 slice 的指針
          slice := slicePtr.Elem()
          slice.Set(reflect.MakeSlice(reflect.SliceOf(sliceType), 0, 0))  // 將這個指針指向新創(chuàng)建slice
    // 過濾所有objId == 當(dāng)前objId 的struct
    for i := 0 ;i < fieldValue.Len(); i++{
    if fieldValue.Index(i).Elem().FieldByName("ObjId").Int() != objId {
    continue
             }
             slice = reflect.Append(slice, fieldValue.Index(i))
          }
    // 將resp 的當(dāng)前字段設(shè)置為過濾后的slice
          fieldValue.Set(slice)
       }
    }
    func Test003(){
    // 利用反射創(chuàng)建一個新的對象
    var uInfo *UserInfo
       uInfo = NewUserInfoByReflect(uInfo)
       uInfo = NewUserInfoByReflect((*UserInfo)(nil))
    // 修改resp 返回值里面的 user info 字段(初始化)
       reqData1 := new(GetOkrDetailResp)
       fmt.Println(reqData1.UInfo)
       ModifyOkrDetailRespData(reqData1)
       fmt.Println(reqData1.UInfo)
    // 構(gòu)建請求參數(shù)
       reqData := &GetOkrDetailResp{OkrId: 123}
       for i := 0; i < 10; i++{
          reqData.ObjList = append(reqData.ObjList, &ObjInfo{ObjId: int64(i), Content: fmt.Sprint(i)})
       }
    // 輸出過濾前結(jié)果
       fmt.Println(reqData)
    // 對respData進行過濾操作
       FilterOkrRespData(reqData, 6)
    // 輸出過濾后結(jié)果
       fmt.Println(reqData)
    }

    反射的性能分析與優(yōu)缺點

    大家都或多或少聽說過反射性能偏低,使用反射要比正常調(diào)用要低幾倍到數(shù)十倍,不知道大家有沒有思考過反射性能都低在哪些方面,我先做一個簡單分析,通過反射在獲取或者修改值內(nèi)容時,多了幾次內(nèi)存引用,多繞了幾次彎,肯定沒有直接調(diào)用某個值來的迅速,這個是反射帶來的固定性能損失,還有一方面的性能損失在于,結(jié)構(gòu)體類型字段比較多時,要進行遍歷匹配才能獲取對應(yīng)的內(nèi)容。

    下面就根據(jù)反射具體示例來分析性能:

    測試反射結(jié)構(gòu)體初始化

    // 測試結(jié)構(gòu)體初始化的反射性能
    func Benchmark_Reflect_New(b *testing.B) {
       var tf *TestReflectField
       t := reflect.TypeOf(TestReflectField{})
       for i := 0; i < b.N; i++ {
          tf = reflect.New(t).Interface().(*TestReflectField)
       }
       _ = tf
    }
    // 測試結(jié)構(gòu)體初始化的性能
    func Benchmark_New(b *testing.B) {
       var tf *TestReflectField
       for i := 0; i < b.N; i++ {
          tf = new(TestReflectField)
       }
       _ = tf
    }

    運行結(jié)果:

    Golang中反射怎么應(yīng)用

    可以看出,利用反射初始化結(jié)構(gòu)體和直接使用創(chuàng)建 new 結(jié)構(gòu)體是有性能差距的,但是差距不大,不到一倍的性能損耗,看起來對于性能來說損耗不是很大,可以接受。

    測試結(jié)構(gòu)體字段讀取/賦值

    // ---------    ------------  字段讀  ----------- ----------- -----------
    // 測試反射讀取結(jié)構(gòu)體字段值的性能
    func Benchmark_Reflect_GetField(b *testing.B) {
       var tf = new(TestReflectField)
       var r int64
       temp := reflect.ValueOf(tf).Elem()
       for i := 0; i < b.N; i++ {
          r = temp.Field(1).Int()
       }
       _ = tf
       _ = r
    }
    // 測試反射讀取結(jié)構(gòu)體字段值的性能
    func Benchmark_Reflect_GetFieldByName(b *testing.B) {
       var tf = new(TestReflectField)
       temp := reflect.ValueOf(tf).Elem()
       var r int64
       for i := 0; i < b.N; i++ {
          r = temp.FieldByName("Age").Int()
       }
       _ = tf
       _ = r
    }
    // 測試結(jié)構(gòu)體字段讀取數(shù)據(jù)的性能
    func Benchmark_GetField(b *testing.B) {
       var tf = new(TestReflectField)
       tf.Age = 1995
       var r int
       for i := 0; i < b.N; i++ {
          r = tf.Age
       }
       _ = tf
       _ = r
    }
    // ---------    ------------  字段寫  ----------- ----------- -----------
    // 測試反射設(shè)置結(jié)構(gòu)體字段的性能
    func Benchmark_Reflect_Field(b *testing.B) {
       var tf = new(TestReflectField)
       temp := reflect.ValueOf(tf).Elem()
       for i := 0; i < b.N; i++ {
          temp.Field(1).SetInt(int64(25))
       }
       _ = tf
    }
    // 測試反射設(shè)置結(jié)構(gòu)體字段的性能
    func Benchmark_Reflect_FieldByName(b *testing.B) {
       var tf = new(TestReflectField)
       temp := reflect.ValueOf(tf).Elem()
       for i := 0; i < b.N; i++ {
          temp.FieldByName("Age").SetInt(int64(25))
       }
       _ = tf
    }
    // 測試結(jié)構(gòu)體字段設(shè)置的性能
    func Benchmark_Field(b *testing.B) {
       var tf = new(TestReflectField)
       for i := 0; i < b.N; i++ {
          tf.Age = i
       }
       _ = tf
    }

    測試結(jié)果:

    Golang中反射怎么應(yīng)用

    Golang中反射怎么應(yīng)用

    從上面可以看出,通過反射進行 struct 字段讀取耗時是直接讀取耗時的百倍。直接對實例變量進行賦值每次 0.5 ns,性能是通過反射操作實例指定位置字段的10 倍左右。

    使用 FieldByName("Age") 方法性能比使用 Field(1) 方法性能要低十倍左右,看代碼的話我們會發(fā)現(xiàn),F(xiàn)ieldByName 是通過遍歷匹配所有的字段,然后比對字段名稱,來查詢其在結(jié)構(gòu)體中的位置,然后通過位置進行賦值,所以性能要比直接使用 Field(index) 低上很多。

    建議:

    • 如果不是必要盡量不要使用反射進行操作, 使用反射時要評估好引入反射對接口性能的影響。

    • 減少使用 FieldByName 方法。在需要使用反射進行成員變量訪問的時候,盡可能的使用成員的序號。如果只知道成員變量的名稱的時候,看具體代碼的使用場景,如果可以在啟動階段或在頻繁訪問前,通過 TypeOf() 、Type.FieldByName() 和 StructField.Index 得到成員的序號。注意這里需要的是使用的是 reflect.Type 而不是 reflect.Value,通過 reflect.Value 是得不到字段名稱的。

    測試結(jié)構(gòu)體方法調(diào)用

    // 測試通過結(jié)構(gòu)體訪問方法性能
    func BenchmarkMethod(b *testing.B) {
       t := &TestReflectField{}
       for i := 0; i < b.N; i++ {
          t.Func0()
       }
    }
    // 測試通過序號反射訪問無參數(shù)方法性能
    func BenchmarkReflectMethod(b *testing.B) {
       v := reflect.ValueOf(&TestReflectField{})
       for i := 0; i < b.N; i++ {
          v.Method(0).Call(nil)
       }
    }
    // 測試通過名稱反射訪問無參數(shù)方法性能
    func BenchmarkReflectMethodByName(b *testing.B) {
       v := reflect.ValueOf(&TestReflectField{})
       for i := 0; i < b.N; i++ {
          v.MethodByName("Func0").Call(nil)
       }
    }
    // 測試通過反射訪問有參數(shù)方法性能
    func BenchmarkReflectMethod_WithArgs(b *testing.B) {
       v := reflect.ValueOf(&TestReflectField{})
       for i := 0; i < b.N; i++ {
          v.Method(1).Call([]reflect.Value{reflect.ValueOf(i)})
       }
    }
    // 測試通過反射訪問結(jié)構(gòu)體參數(shù)方法性能
    func BenchmarkReflectMethod_WithArgs_Mul(b *testing.B) {
       v := reflect.ValueOf(&TestReflectField{})
       for i := 0; i < b.N; i++ {
          v.Method(2).Call([]reflect.Value{reflect.ValueOf(TestReflectField{})})
       }
    }
    // 測試通過反射訪問接口參數(shù)方法性能
    func BenchmarkReflectMethod_WithArgs_Interface(b *testing.B) {
       v := reflect.ValueOf(&TestReflectField{})
       for i := 0; i < b.N; i++ {
          var tf TestInterface = &TestReflectField{}
          v.Method(3).Call([]reflect.Value{reflect.ValueOf(tf)})
       }
    }
    // 測試訪問多參數(shù)方法性能
    func BenchmarkMethod_WithManyArgs(b *testing.B) {
       s := &TestReflectField{}
       for i := 0; i < b.N; i++ {
          s.Func4(i, i, i, i, i, i)
       }
    }
    // 測試通過反射訪問多參數(shù)方法性能
    func BenchmarkReflectMethod_WithManyArgs(b *testing.B) {
       v := reflect.ValueOf(&TestReflectField{})
       va := make([]reflect.Value, 0)
       for i := 1; i <= 6; i++ {
          va = append(va, reflect.ValueOf(i))
       }
       for i := 0; i < b.N; i++ {
          v.Method(4).Call(va)
       }
    }
    // 測試訪問有返回值的方法性能
    func BenchmarkMethod_WithResp(b *testing.B) {
       s := &TestReflectField{}
       for i := 0; i < b.N; i++ {
          _ = s.Func5()
       }
    }
    // 測試通過反射訪問有返回值的方法性能
    func BenchmarkReflectMethod_WithResp(b *testing.B) {
       v := reflect.ValueOf(&TestReflectField{})
       for i := 0; i < b.N; i++ {
          _ = v.Method(5).Call(nil)[0].Int()
       }
    }

    Golang中反射怎么應(yīng)用

    這個測試結(jié)果同上面的分析相同

    優(yōu)缺點

    優(yōu)點:

    • 反射提高了程序的靈活性和擴展性,降低耦合性,提高自適應(yīng)能力。

    • 合理利用反射可以減少重復(fù)代碼

    缺點:

    • 與反射相關(guān)的代碼,經(jīng)常是難以閱讀的。在軟件工程中,代碼可讀性也是一個非常重要的指標。

    • Go 語言作為一門靜態(tài)語言,編碼過程中,編譯器能提前發(fā)現(xiàn)一些類型錯誤,但是對于反射代碼是無能為力的。所以包含反射相關(guān)的代碼,很可能會運行很久,才會出錯,這時候經(jīng)常是直接 panic,可能會造成嚴重的后果。

    • 反射對性能影響還是比較大的,比正常代碼運行速度慢一到兩個數(shù)量級。所以,對于一個項目中處于運行效率關(guān)鍵位置的代碼,盡量避免使用反射特性。

    反射在 okr 中的簡單應(yīng)用

    func OkrBaseMW(next endpoint.EndPoint) endpoint.EndPoint {
       return func(ctx context.Context, req interface{}) (resp interface{}, err error) {
          if req == nil {
             return next(ctx, req)
          }
          requestValue := reflect.ValueOf(req)
          // 若req為指針,則轉(zhuǎn)換為非指針值
          if requestValue.Type().Kind() == reflect.Ptr {
             requestValue = requestValue.Elem()
          }
          // 若req的值不是一個struct,則不注入
          if requestValue.Type().Kind() != reflect.Struct {
             return next(ctx, req)
          }
          if requestValue.IsValid() {
             okrBaseValue := requestValue.FieldByName("OkrBase")
             if okrBaseValue.IsValid() &amp;&amp; okrBaseValue.Type().Kind() == reflect.Ptr {
                okrBase, ok := okrBaseValue.Interface().(*okrx.OkrBase)
                if ok {
                   ctx = contextWithUserInfo(ctx, okrBase)
                   ctx = contextWithLocaleInfo(ctx, okrBase)
                   ctx = contextWithUserAgent(ctx, okrBase)
                   ctx = contextWithCsrfToken(ctx, okrBase)
                   ctx = contextWithReferer(ctx, okrBase)
                   ctx = contextWithXForwardedFor(ctx, okrBase)
                   ctx = contextWithHost(ctx, okrBase)
                   ctx = contextWithURI(ctx, okrBase)
                   ctx = contextWithSession(ctx, okrBase)
                }
             }
          }
          return next(ctx, req)
       }
    }

    讀到這里,這篇“Golang中反射怎么應(yīng)用”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領(lǐng)會,如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

    向AI問一下細節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI