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())
}
foreword
-
Variables in Golang include (type, value) two parts -
Understanding this will solve the simple problem above -
Type includes static type and concrete type. In short, static type is the type you see in coding (such as int, string), and concrete type is the type that the runtime system sees. The success of type assertion depends on the concrete type of the variable, not the static type.
When the program runs, we get an interface variable. How should the program know the type of the current variable and the value of the current variable?
Of course we can have pre-defined specified types, but if there is a scenario where we need to write a function that can handle a type of common logic, but there are many input types, or we don't know what the type of the received parameter is, or maybe There is no agreement; it may also be that there are many types passed in, and these types cannot be represented uniformly. At this time, reflection will be used, a typical example is: json.Marshal.
For another example, sometimes it is necessary to decide which function to call based on certain conditions, such as deciding based on user input. At this time, it is necessary to reflect on the function and the parameters of the function, and execute the function dynamically during the runtime.
Example scene:
For example, we need to perform a certain operation on a struct (replace with formatted printing). In this scenario, we can achieve it in many ways. The simpler way is: 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
Basic usage of reflection
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
}
// 利用反射创建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 字段值,并根据条件进行过滤
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)) // 创建一个指向 slice 的指针
slice := slicePtr.Elem()
slice.Set(reflect.MakeSlice(reflect.SliceOf(sliceType), 0, 0)) // 将这个指针指向新创建slice
// 过滤所有objId == 当前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 的当前字段设置为过滤后的slice
fieldValue.Set(slice)
}
}
func Test003(){
// 利用反射创建一个新的对象
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)
// 构建请求参数
reqData := &GetOkrDetailResp{OkrId: 123}
for i := 0; i < 10; i++{
reqData.ObjList = append(reqData.ObjList, &ObjInfo{ObjId: int64(i), Content: fmt.Sprint(i)})
}
// 输出过滤前结果
fmt.Println(reqData)
// 对respData进行过滤操作
FilterOkrRespData(reqData, 6)
// 输出过滤后结果
fmt.Println(reqData)
}
Performance Analysis and Advantages and Disadvantages of Reflection
Test reflection struct initialization
// 测试结构体初始化的反射性能
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
}
// 测试结构体初始化的性能
func Benchmark_New(b *testing.B) {
var tf *TestReflectField
for i := 0; i < b.N; i++ {
tf = new(TestReflectField)
}
_ = tf
}
operation result:
It can be seen that there is a performance gap between using reflection to initialize a structure and directly using it to create a new structure, but the gap is not large, and the performance loss is less than double. It seems that the performance loss is not very large, which is acceptable.
Test struct field read/assign
// --------- ------------ 字段读 ----------- ----------- -----------
// 测试反射读取结构体字段值的性能
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
}
// 测试反射读取结构体字段值的性能
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
}
// 测试结构体字段读取数据的性能
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
}
// --------- ------------ 字段写 ----------- ----------- -----------
// 测试反射设置结构体字段的性能
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
}
// 测试反射设置结构体字段的性能
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
}
// 测试结构体字段设置的性能
func Benchmark_Field(b *testing.B) {
var tf = new(TestReflectField)
for i := 0; i < b.N; i++ {
tf.Age = i
}
_ = tf
}
Test Results:
As can be seen from the above, the time-consuming of struct field reading through reflection is a hundred times that of direct reading. Assigning an instance variable directly takes 0.5 ns each time, and the performance is about 10 times that of specifying the location field of the instance through the reflection operation. The performance of using the FieldByName("Age") method is about ten times lower than that of using the Field(1). The position in the body, and then assign values through the position, so the performance is much lower than using Field(index) directly.
2. Reduce the use of the FieldByName method . When you need to use reflection to access member variables, use the member's ordinal number as much as possible. If you only know the name of the member variable, look at the usage scenario of the specific code. If you can get the serial number of the member through TypeOf(), Type.FieldByName() and StructField.Index in the startup phase or before frequent access. Note that what is needed here is to use reflect.Type instead of reflect.Value. You can't get the field name through reflect.Value.
Test struct method calls
// 测试通过结构体访问方法性能
func BenchmarkMethod(b *testing.B) {
t := &TestReflectField{}
for i := 0; i < b.N; i++ {
t.Func0()
}
}
// 测试通过序号反射访问无参数方法性能
func BenchmarkReflectMethod(b *testing.B) {
v := reflect.ValueOf(&TestReflectField{})
for i := 0; i < b.N; i++ {
v.Method(0).Call(nil)
}
}
// 测试通过名称反射访问无参数方法性能
func BenchmarkReflectMethodByName(b *testing.B) {
v := reflect.ValueOf(&TestReflectField{})
for i := 0; i < b.N; i++ {
v.MethodByName("Func0").Call(nil)
}
}
// 测试通过反射访问有参数方法性能
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)})
}
}
// 测试通过反射访问结构体参数方法性能
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{})})
}
}
// 测试通过反射访问接口参数方法性能
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)})
}
}
// 测试访问多参数方法性能
func BenchmarkMethod_WithManyArgs(b *testing.B) {
s := &TestReflectField{}
for i := 0; i < b.N; i++ {
s.Func4(i, i, i, i, i, i)
}
}
// 测试通过反射访问多参数方法性能
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()
}
}
This test result is the same as the above analysis
Advantages and disadvantages:
advantage:
-
Reflection improves the flexibility and expansibility of the program, reduces the coupling, and improves the adaptive ability. -
Reasonable use of reflection can reduce duplication of code
-
Code related to reflection is often difficult to read. In software engineering, code readability is also a very important indicator.
-
Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成严重的后果。
-
反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。
反射在 okr 中的简单应用
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为指针,则转换为非指针值
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() && 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)
}
}
结论:
加入我们
我们来自字节跳动飞书商业应用研发部(Lark Business Applications),目前我们在北京、深圳、上海、武汉、杭州、成都、广州、三亚都设立了办公区域。我们关注的产品领域主要在企业经验管理软件上,包括飞书 OKR、飞书绩效、飞书招聘、飞书人事等 HCM 领域系统,也包括飞书审批、OA、法务、财务、采购、差旅与报销等系统。