zoukankan      html  css  js  c++  java
  • golang路上的小学生系列--使用reflect查找package路径

    本文同时发布在个人博客chinazt.ccgitbook

    今日看到了一个有趣的golang项目--kolpa(https://github.com/malisit/kolpa)。 这个项目可以用来生成伪造的姓名,地址,时间,User-Agent等等信息,在需要大量随机数据的测试环境中非常合适。

    点击fork之后,放在本地环境中build,run结果失败。运行项目中提供的demo也失败,按道理来说官方提供的demo应该都会成功,而且自己也没有修改任何一行代码,失败是不科学的。

    所以只能剖解代码,查找失败原因。

    一查不知道,原来此项目需要依赖各个语言环境下的模板文件,而模板文件都放在项目的data目录中。 在代码中,通过硬编码来确定模板文件位置:

    // Reads the file "fName" and returns its content as a slice of strings.
    func (g *Generator) fileToSlice(fName string) ([]string, error) {
    var res []string
    path := os.Getenv("GOPATH") + "/src/github.com/malisit/kolpa/data/" + g.Locale + "/" + fName
    file, err := os.Open(path)
    
    if err != nil {
    return nil, err
    }
    defer file.Close()
    
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
    res = append(res, scanner.Text())
    }
    
    if err := scanner.Err(); err != nil {
    //log.Println("Inteded generation is not valid for selected language. Switching to en_US.")
    return g.fileToSlice(fName)
    }
    
    return res, nil
    }
    

    因为我是通过fork,然后clone到本地的方式来运行demo,本地此时packge路径已经不是上面的路径了,所以导致运行失败。 很难说这是一个bug,但的确影响到了程序运行。 所以说不良的代码风格更为恰当吧。

    既然找到了问题,那下一步就是如何解决问题。 应该如何在Runtime时实时获取package位置呢?

    说到Runtime,那么一定就少不了Reflect package。

    Package reflect implements run-time reflection, allowing a program to manipulate objects with arbitrary types. The typical use is to take a value with static type interface{} and extract its dynamic type information by calling TypeOf, which returns a Type.
    

    从Reflect的介绍上看,Reflect package推荐的使用方式是通过TypeOf返回一个带有interface{}所有动态类型信息的Type类型,然后通过Type类型来获取各种程序需要的信息。

    type Type interface {
    
    // Align returns the alignment in bytes of a value of
    // this type when allocated in memory.
    Align() int
    
    // FieldAlign returns the alignment in bytes of a value of
    // this type when used as a field in a struct.
    FieldAlign() int
    
    // Method returns the i'th method in the type's method set.
    // It panics if i is not in the range [0, NumMethod()).
    //
    // For a non-interface type T or *T, the returned Method's Type and Func
    // fields describe a function whose first argument is the receiver.
    //
    // For an interface type, the returned Method's Type field gives the
    // method signature, without a receiver, and the Func field is nil.
    Method(int) Method
    
    // MethodByName returns the method with that name in the type's
    // method set and a boolean indicating if the method was found.
    //
    // For a non-interface type T or *T, the returned Method's Type and Func
    // fields describe a function whose first argument is the receiver.
    //
    // For an interface type, the returned Method's Type field gives the
    // method signature, without a receiver, and the Func field is nil.
    MethodByName(string) (Method, bool)
    
    // NumMethod returns the number of exported methods in the type's method set.
    NumMethod() int
    
    // Name returns the type's name within its package.
    // It returns an empty string for unnamed types.
    Name() string
    
    // PkgPath returns a named type's package path, that is, the import path
    // that uniquely identifies the package, such as "encoding/base64".
    // If the type was predeclared (string, error) or unnamed (*T, struct{}, []int),
    // the package path will be the empty string.
    PkgPath() string
    
    // Size returns the number of bytes needed to store
    // a value of the given type; it is analogous to unsafe.Sizeof.
    Size() uintptr
    
    // String returns a string representation of the type.
    // The string representation may use shortened package names
    // (e.g., base64 instead of "encoding/base64") and is not
    // guaranteed to be unique among types. To test for type identity,
    // compare the Types directly.
    String() string
    
    // Kind returns the specific kind of this type.
    Kind() Kind
    
    // Implements reports whether the type implements the interface type u.
    Implements(u Type) bool
    
    // AssignableTo reports whether a value of the type is assignable to type u.
    AssignableTo(u Type) bool
    
    // ConvertibleTo reports whether a value of the type is convertible to type u.
    ConvertibleTo(u Type) bool
    
    // Comparable reports whether values of this type are comparable.
    Comparable() bool
    
    // Bits returns the size of the type in bits.
    // It panics if the type's Kind is not one of the
    // sized or unsized Int, Uint, Float, or Complex kinds.
    Bits() int
    
    // ChanDir returns a channel type's direction.
    // It panics if the type's Kind is not Chan.
    ChanDir() ChanDir
    
    // IsVariadic reports whether a function type's final input parameter
    // is a "..." parameter. If so, t.In(t.NumIn() - 1) returns the parameter's
    // implicit actual type []T.
    //
    // For concreteness, if t represents func(x int, y ... float64), then
    //
    //	t.NumIn() == 2
    //	t.In(0) is the reflect.Type for "int"
    //	t.In(1) is the reflect.Type for "[]float64"
    //	t.IsVariadic() == true
    //
    // IsVariadic panics if the type's Kind is not Func.
    IsVariadic() bool
    
    // Elem returns a type's element type.
    // It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.
    Elem() Type
    
    // Field returns a struct type's i'th field.
    // It panics if the type's Kind is not Struct.
    // It panics if i is not in the range [0, NumField()).
    Field(i int) StructField
    
    // FieldByIndex returns the nested field corresponding
    // to the index sequence. It is equivalent to calling Field
    // successively for each index i.
    // It panics if the type's Kind is not Struct.
    FieldByIndex(index []int) StructField
    
    // FieldByName returns the struct field with the given name
    // and a boolean indicating if the field was found.
    FieldByName(name string) (StructField, bool)
    
    // FieldByNameFunc returns the struct field with a name
    // that satisfies the match function and a boolean indicating if
    // the field was found.
    //
    // FieldByNameFunc considers the fields in the struct itself
    // and then the fields in any anonymous structs, in breadth first order,
    // stopping at the shallowest nesting depth containing one or more
    // fields satisfying the match function. If multiple fields at that depth
    // satisfy the match function, they cancel each other
    // and FieldByNameFunc returns no match.
    // This behavior mirrors Go's handling of name lookup in
    // structs containing anonymous fields.
    FieldByNameFunc(match func(string) bool) (StructField, bool)
    
    // In returns the type of a function type's i'th input parameter.
    // It panics if the type's Kind is not Func.
    // It panics if i is not in the range [0, NumIn()).
    In(i int) Type
    
    // Key returns a map type's key type.
    // It panics if the type's Kind is not Map.
    Key() Type
    
    // Len returns an array type's length.
    // It panics if the type's Kind is not Array.
    Len() int
    
    // NumField returns a struct type's field count.
    // It panics if the type's Kind is not Struct.
    NumField() int
    
    // NumIn returns a function type's input parameter count.
    // It panics if the type's Kind is not Func.
    NumIn() int
    
    // NumOut returns a function type's output parameter count.
    // It panics if the type's Kind is not Func.
    NumOut() int
    
    // Out returns the type of a function type's i'th output parameter.
    // It panics if the type's Kind is not Func.
    // It panics if i is not in the range [0, NumOut()).
    Out(i int) Type
    // contains filtered or unexported methods
    }
    

    在Type接口定义中,和Package Path有关的有两个函数:

    • PkgPath()
    • String()

    PkgPath 返回指定类型的import package path,也就是说,如果代码中有import encoding/base64这样的语句, 那么通过PkgPath()就会返回encoding/base64,而不是base64package所在的实际路径。 反言之,PkgPath()返回的是import package path。

    而如果继续使用上例中的encoding/base64来说,String()返回的是base64,而不是encoding/base64。 String()返回的是实际使用的package name。

    所以总结如下:

    • PkgPath() 返回import package path
    • String() 返回import package name

    显然,PkgPath()更适合我们的需求。 简单改造代码如下:

    1.增加 Pkg path

    // Generator struct to access various generator functions
    type Generator struct {
    Locale string
    Pkg string
    }
    

    2.获取Pkg

    // C is the creator function, initiates kolpa with or without locale
    // setting. The default locale setting is "en_US".
    // Returns a generator type that will be used to call generator methods.
    func C(localeVar ...string) Generator {
    newGenerator := Generator{}
    if len(localeVar) > 0 {
    newGenerator.Locale = localeVar[0]
    } else {
    newGenerator.Locale = "en_US"
    }
    // newGenerator.populateFunctions()
    newGenerator.Pkg = reflect.TypeOf(newGenerator).PkgPath()
    return newGenerator
    }
    

    3.替换硬编码

    // Reads the file "fName" and returns its content as a slice of strings.
    func (g *Generator) fileToSlice(fName string) ([]string, error) {
    var res []string
    path := os.Getenv("GOPATH") + "/src/" + g.Pkg + "/data/" + g.Locale + "/" + fName
    file, err := os.Open(path)
    
    if err != nil {
    return nil, err
    }
    defer file.Close()
    
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
    res = append(res, scanner.Text())
    }
    
    if err := scanner.Err(); err != nil {
    //log.Println("Inteded generation is not valid for selected language. Switching to en_US.")
    return g.fileToSlice(fName)
    }
    
    return res, nil
    }
    

    4.运行demo

    ⋊> ~/S/g/g/s/t/kopla ./kopla
    石洁玉
    幸和平
    

    完美生成两个随机姓名,话说"和平"也是一代人经常起的名字。

    最后提一句,有的地方曾经说到尽量少使用Reflect package。 因此反射使用多了,影响效率。 我想这种想法应该是从Java VM那里流传出来的吧。 因为Java VM负责将字节码翻译成机器码,因此频繁调用反射会加重VM切换上下文的代价,也就是把装载期做的事情搬到了运行期,势必降低运行效率。 golang的反射减少了翻译环节,同时借助于编译器进行了代码优化,虽然同样在反射时需要进行额外的安全检查和类型检查,但不会降低太多效率。 而且上面,我们也看到只有在调用时也只发生了一次反射调用,影响几乎可忽略不计。

  • 相关阅读:
    Other.ini配置文件解读以及大众评委打分的最后得分两种模式选择及解析选项解释
    大作业练习:用Asp.net Mvc4做一个:学生考试成绩管理系统-简易版
    网络营销实施步骤及疑难问题汇编
    Web前端知识汇编收集B
    Web前端知识汇编收集A
    FlexItem 多行测试
    Last Work-随机出题加法游戏
    Android DisplayMetrics类获取屏幕大小
    Java简介
    Failed to resolve:junit:junit:4.12
  • 原文地址:https://www.cnblogs.com/vikings-blog/p/7131618.html
Copyright © 2011-2022 走看看