122. Go反射中与结构体相关的常用方法与应用
文章目录
- encoding/json
- reflect 简介
- reflect.Value 常用方法
- reflect.Type 常用方法
- 应用一:使用 reflect 实现 encoding/json
- 序列化
- 反序列化
- 应用二:使用Tag实现字段级别的访问控制
- tag 行为自定义
- 案例:结构体字段访问控制
- 总结
在使用 Go
语言开发过程中,我们经常需要实现结构体到 JSON
字符串的序列化(Marshal
)或 JSON
字符串到结构体的反序列化(Unmarshal
)操作。Go
为我们提供了 encoding/json
库可以很方便的实现这一需求。
encoding/json
中就大量用到了反射的知识 60.Go反射库reflect,在本文中,我们将探索如何使用 Go
的反射机制自己来实现一个简易版的 encoding/json
库。这个过程不仅能帮助我们理解序列化和反序列化的基本原理,还能提供一种实用的反射使用方法,加深我们对反射的理解。
通过本文的学习,我们将实现一个能够将结构体与 JSON
字符串互相转换的包。
encoding/json
我们先来回顾下在 Go
中如何使用 encoding/json
库实现结构体和 JSON
字符串互转。
示例代码如下:
package mainimport ("encoding/json""fmt"
)type User struct {Name string `json:"name"`Age int `json:"age"`Email string
}func main() {{user := User{Name: "测试",Age: 20,Email: "111@163.com",}jsonData, err := json.Marshal(user)if err != nil {fmt.Println("Error marshal to JSON:", err)return}fmt.Printf("JSON data: %s\n", jsonData)}{jsonData := `{"name": "测试", "age": 20, "Email": "111@163.com"}`var user Usererr := json.Unmarshal([]byte(jsonData), &user)if err != nil {fmt.Println("Error unmarshal from JSON:", err)return}fmt.Printf("User struct: %+v\n", user)}
}
示例程序中定义了一个 User
结构体,结构体包含三个字段,Name
、Age
和 Email
。
encoding/json
会根据结构体字段上的 JSON Tag
(标签)进行序列化和反序列化。序列化时,JSON Tag
会作为 JSON
字符串的 key
,字段值作为 JSON
字符串的 value
。反序列化时,JSON
字符串的 key
所对应的值会被映射到具有同样 JSON Tag
的结构体字段上。
Name
字段的 JSON Tag
是 name
,则对应的 JSON
字符串的 key
为 name
;Age
字段的 JSON Tag
是 age
,则对应的 JSON
字符串的 key
为 age
;Email
字段没有 JSON Tag
,则默认会使用字段名 Email
作为对应的 JSON
字符串 key
。
执行示例代码,得到如下输出:
$ go run main.go
JSON data: {"name":"测试","age":20,"Email":"111@163.com"}
User struct: {Name:测试 Age:20 Email:111@163.com}
reflect 简介
reflect
是 Go
语言为我们提供的反射库,用于在运行时检查类型并操作对象。它是实现动态编程和元编程的基础,使程序能够在运行时获取类型信息并进行相应的操作。
有如下示例代码:
package mainimport ("fmt""reflect"
)type User struct {Name string `json:"name"`Age int `json:"age"`Email string
}func main() {// 内置类型{age := 20val := reflect.ValueOf(age)typ := reflect.TypeOf(age)fmt.Println(val, typ)// 自定义结构体类型{user := User{Name: "测试",Age: 20,Email: "111@163.com",}val := reflect.ValueOf(user)typ := reflect.TypeOf(user)fmt.Println(val, typ)}
}
执行示例代码,得到如下输出:
$ go run main.go
20 int
{测试 20 111@163.com} main.User
reflect
最常用的两个方法分别是 reflect.ValueOf
和 reflect.TypeOf
,它们分别返回 reflect.Value结构体
和 reflect.Type接口
类型。这两个方法可以应用于任何类型对象(any
)。
-
reflect.Value
:表示一个Go
值,它提供了一些方法,可以获取值的详细信息,也可以操作值,例如获取值的类型、设置值等。 -
reflect.Type
:表示一个Go
类型,它提供了一些方法,可以获取类型的详细信息,例如类型的名称(Name
)、种类(Kind
,基本类型、结构体、切片等)。
接下来对 reflect.Value
和 reflect.Type
类型的常用方法进行介绍,以如下实例化 User
结构体指针作为被操作对象:
// 实例化 User 结构体指针
user := &User{Name: "测试",Age: 20,Email: "111@163.com",
}
reflect.Value 常用方法
reflect.Value
提供了 Kind
方法可以获取对应的类型类别:
// 注意这里传递的是指针类型
kind := reflect.ValueOf(user).Kind()
fmt.Println(kind)
kind = reflect.ValueOf(*user).Kind()
fmt.Println(kind)
kind = reflect.ValueOf(user).Elem().Kind()
fmt.Println(kind)
这段示例代码将得到如下输出:
ptr
struct
struct
这里 Kind
方法返回的是 User
的底层类型 struct
,以及 ptr
类型,ptr
代表指针类型。
值得注意的是,如果传递给 reflect.ValueOf
的是指针类型(user
),需要使用 Elem
方法获取指针指向的值;如果传递给 reflect.ValueOf
的是值类型(*user
),则可以直接得到值。
使用指针类型的好处是可以使用 reflect.Value
提供的 Set<Type>
(如SetInt
,SetString
)方法直接修改 user
字段的值,稍后讲解。
reflect.Value
同样提供了 Type
方法,可以得到 reflect.Type
:
// 以下二者等价
tpy := reflect.ValueOf(user).Type()
fmt.Println(tpy)
tpy1 := reflect.TypeOf(user)
fmt.Println(tpy1)
fmt.Println(reflect.DeepEqual(tpy, tpy1))
这与 reflect.TypeOf
等价。
这段示例代码将得到如下输出:
*main.User
*main.User
true
我们有多种方式可以获取结构体值字段:
nameField := reflect.ValueOf(user).Elem().FieldByName("Name")
ageField := reflect.ValueOf(user).Elem().FieldByIndex([]int{1})
emailField := reflect.ValueOf(user).Elem().Field(2)
-
FieldByName
方法可以通过字段名获取结构体字段。 -
FieldByIndex
方法通过索引切片获取结构体字段。 -
Field
方法通过索引获取结构体字段。
实际上 FieldByIndex
方法内部调用的也是Field
方法。这里的索引是结构体字段按照顺序排序所在位置,即Name
字段索引为 0
,Age
字段索引为 1
,Email
字段索引为 2
。
我们可以使用 NumField
获取结构体字段总个数:
numField := reflect.ValueOf(*user).NumField()
fmt.Println(numField)
拿到结构体字段对象后,可以根据其具体类型获取对应值:
fmt.Println(nameField.String())
fmt.Println(ageField.Int())
fmt.Println(emailField.String())
以上示例代码将得到如下输出:
3
测试
20
111@163.com
因为我们传递给 reflect.ValueOf
函数的是 User
结构体指针,所以可以使用 reflect.Value
提供的 Set<Type>
方法设置结构体字段的值:
nameField.SetString("ceshi") // 设置 Name 字段的值
ageField.SetInt(18) // 设置 Age 字段的值
emailField.SetString("123@163.com") // 设置 Email 字段的值
现在打印 user
对象:
fmt.Println(user)
得到输出:
&{ceshi 18 123@163.com}
如果我们传递给 reflect.ValueOf
函数的不是 User
结构体指针,而是结构体对象:
nameField := reflect.ValueOf(*user).FieldByName("Name")
现在去设置字段值:
nameField.SetString("ceshi")
程序会直接 panic
:
panic: reflect: reflect.Value.SetString using unaddressable value
此外,我们还可以总结一个规律,使用指针时,就需要通过 Elem
方法获取指针指向的值,不使用指针就不需要调用 Elem
方法。
reflect.Type 常用方法
现在我们再来简单介绍下 reflect.Type
的几个常用方法。
reflect.Type
同样提供了如下几个方法,与 reflect.Value
对应:
nameField, _ := reflect.TypeOf(user).Elem().FieldByName("Name")
ageField := reflect.TypeOf(user).Elem().FieldByIndex([]int{1})
emailField := reflect.TypeOf(user).Elem().Field(2)
我们来输出下这几个对象的值:
fmt.Printf("%+v\n", nameField)
fmt.Printf("%+v\n", ageField)
fmt.Printf("%+v\n", emailField)
得到如下输出:
{Name:Name PkgPath: Type:string Tag:json:"name" Offset:0 Index:[0] Anonymous:false}
{Name:Age PkgPath: Type:int Tag:json:"age" Offset:16 Index:[1] Anonymous:false}
{Name:Email PkgPath: Type:string Tag: Offset:24 Index:[2] Anonymous:false}
这里打印了结构体每个字段的信息。
-
Name
对应字段名。 -
PkgPath
是包路径。 -
Type
是结构体字段类型。 -
Tag
即为字段标签。 -
Offset
是字段偏移量。结构体内存对齐可能用到该偏移量。 -
Index
是字段索引位置。 -
Anonymous
表示是否为匿名字段。比如如下结构体:
type User struct {Name string `json:"name"`Age int `json:"age"`Email stringstring
}
这个结构体定义中,最后一个字段就是匿名字段。
现在我们想获取结构体字段 JSON Tag
,可以这样做:
tag := nameField.Tag
fmt.Printf("%+v\n", tag)
fmt.Printf("%+v\n", tag.Get("json"))
将得到如下输出:
json:"name"
name
reflect
基础语法就讲解到这里,更多使用方法需要我们在以后的的实践中去探索。
应用一:使用 reflect 实现 encoding/json
接下来就看看,如何使用 reflect
自己实现一个简易版本的 encoding/json
。
示例程序目录结构如下:
$ tree
.
├── encoding
│ └── json
│ ├── decode.go
│ └── encode.go
├── go.mod
└── main.go
-
encoding/json/encode.go
用于实现序列化功能。 -
encoding/json/decode.go
用于实现反序列化功能。 -
main.go
用来验证这个简易版的encoding/json
功能。
序列化
首先是实现序列化的代码:
package jsonimport ("fmt""reflect""strconv""strings"
)// Marshal 序列化
func Marshal(v any) (string, error) {// 拿到对象 v 的 reflect.Value 和 reflect.Typeval := reflect.ValueOf(v)if val.Kind() != reflect.Struct {return "", fmt.Errorf("only structs are supported")}typ := val.Type()// 用来保存 JSON 字符串jsonBuilder := strings.Builder{}// NOTE: 三步走拼接 JSON 字符串// 1. JSON 左花括号jsonBuilder.WriteString("{")// 2. key/valuefor i := 0; i < val.NumField(); i++ {fieldVal := val.Field(i)fieldType := typ.Field(i)// 获取 JSON 标签// 与类型相关的信息都在reflect.Type对象中tag := fieldType.Tag.Get("json")if tag == "" {tag = fieldType.Name}jsonBuilder.WriteString("\"" + tag + "\"")// 根据字段类型转换,仅支持 string/intswitch fieldVal.Kind() {case reflect.String:jsonBuilder.WriteString(`"` + fieldVal.String() + `"`)case reflect.Int:jsonBuilder.WriteString(strconv.FormatInt(fieldVal.Int(), 10))default:return "", fmt.Errorf("unsupported field type: %s", fieldVal.Kind())}if i < val.NumField()-1 {jsonBuilder.WriteString(",")}}// 3. JSON 右花括号jsonBuilder.WriteString("}")return jsonBuilder.String(), nil
}
这段代码中没有新的 reflect
语法,我们都在前文中介绍了,这里捋一下代码逻辑。
所谓序列化操作,就是 Go
结构体转 JSON
字符串的操作。
这里函数名参考 encoding/json
同样被定义为 Marshal
。
首先我们拿到对象 v
的 reflect.Value
和 reflect.Type
,待后续使用。
接着使用strings.Builder
构造了一个用来保存JSON
字符串信息的对象 jsonBuilder
。
构造 JSON
字符串分三步走:
先写入JSON
左花括号{
内容到 jsonBuilder
。
根据结构体字段和值,构造 JSON
字符串的键值对 key/value
并写入 jsonBuilder
。
最后写入 JSON
右花括号}
内容到 jsonBuilder
。
函数最终返回 jsonBuilder.String()
即为 JSON
字符串。
这里面主要逻辑都在步骤2
中。
首先会遍历结构体每个字段,并使用如下方式获取每个字段对应的 JSON Tag
:
tag := fieldType.Tag.Get("json")
if tag == "" {tag = fieldType.Name
}
当 JSON Tag
不存在,则默认使用结构体字段名作为 JSON
字符串的 key
,比如 User.Email
字段。
将JSON key
和 :
写入 jsonBuilder
:
jsonBuilder.WriteString(`"` + tag + `":`)
然后根据结构体字段类型转换成对应的 JSON
数据类型,写入 jsonBuilder
:
switch fieldVal.Kind() {
case reflect.String:jsonBuilder.WriteString(`"` + fieldVal.String() + `"`)
case reflect.Int:jsonBuilder.WriteString(strconv.FormatInt(fieldVal.Int(), 10))
default:return "", fmt.Errorf("unsupported field type: %s", fieldVal.Kind())
}
每次循环末尾,判断是否为结构体最后一个字段,如果不是,则写入分隔符 ,:
if i < val.NumField()-1 {jsonBuilder.WriteString(",")
}
至此,序列化代码逻辑大功告成。
我们可以使用如下示例代码进行测试:
user := User{Name: "测试",Age: 20,Email: "111@163.com",
}jsonData, err := simplejson.Marshal(user)
if err != nil {fmt.Println("Error marshal to JSON:", err)return
}fmt.Printf("JSON data: %s\n", jsonData)
执行示例代码,得到如下输出:
$ go run main.go
JSON data: {"name":"测试","age":20,"Email":"111@163.com"}
没有任何问题,与原生的 encoding/json
中的 Marshal
方法表现一致。
反序列化
接下来是实现反序列化的代码:
package jsonimport ("errors""fmt""reflect""strconv""strings"
)// Unmarshal 反序列化
func Unmarshal(data []byte, v interface{}) error {// 将json字节数组转为map[string]string key:json中的key,value:json中的valueparsedData, err := parseJSON(string(data))if err != nil {return err}if reflect.TypeOf(v).Kind() != reflect.Ptr{return errors.New("args v not ptr")}val := reflect.ValueOf(v).Elem()typ := val.Type()for i := 0; i < val.NumField(); i++ {fieldVal := val.Field(i)fieldType := typ.Field(i)// 获取 JSON 标签tag := fieldType.Tag.Get("json")if tag == "" {tag = fieldType.Name}// 从解析的数据中获取值if value, ok := parsedData[tag]; ok {switch fieldVal.Kind() {case reflect.String:fieldVal.SetString(value)case reflect.Int:intValue, err := strconv.Atoi(value)if err != nil {return err}fieldVal.SetInt(int64(intValue))default:return fmt.Errorf("unsupported field type: %s", fieldVal.Kind())}}}return nil
}
这段代码中同样没有新的 reflect
语法。
所谓反序列化操作,就是 JSON
字符串转Go
结构体的操作。
这里函数名参考 encoding/json
同样被定义为 Unmarshal
,并且函数签名也保持一致。
反序列化操作首先使用 parseJSON
函数解析传递进来的 JSON
数据,得到 parsedData
。
parsedData
类型为 map[string]string
,map
的 key
为 JSON
字符串中的 key
,map
的 value
即为 JSON
字符串中的 value
。
接下来核心逻辑是遍历结构体每个字段,并获取字段对应的 JSON Tag
:
tag := fieldType.Tag.Get("json")
if tag == "" {tag = fieldType.Name
}
当 JSON Tag
不存在,则默认使用结构体字段名作为 JSON
字符串的 key
,比如 User.Email
字段。
然后根据JSON Tag
从解析后的parsedData
数据中获取 key/value
:
if value, ok := parsedData[tag]; ok {switch fieldVal.Kind() {case reflect.String:fieldVal.SetString(value)case reflect.Int:intValue, err := strconv.Atoi(value)if err != nil {return err}fieldVal.SetInt(int64(intValue))default:return fmt.Errorf("unsupported field type: %s", fieldVal.Kind())}
}
这里根据结构体字段的类型,将 parsedData
中对应的字符串 value
转换成对应类型。并使用 reflect.Value
提供的 SetString
和 SetInt
方法设置字段的值。
现在,我们唯一没有讲解的逻辑就只剩下 parseJSON
函数了。
parseJSON
函数定义如下:
// 简易版 JSON 解析器,仅支持 string/int 且不考虑嵌套
func parseJSON(data string) (map[string]string, error) {result := make(map[string]string)data = strings.TrimSpace(data)if len(data) < 2 || data[0] != '{' || data[len(data)-1] != '}' {return nil, errors.New("invalid JSON")}data = data[1 : len(data)-1]parts := strings.Split(data, ",")for _, part := range parts {kv := strings.SplitN(part, ":", 2)if len(kv) != 2 {return nil, errors.New("invalid JSON")}k := strings.Trim(strings.TrimSpace(kv[0]), `"`)v := strings.Trim(strings.TrimSpace(kv[1]), `"`)result[k] = v}return result, nil
}
parseJSON
实现了一个简易版本的JSON
字符串解析器,能够将JSON
字符串的key/value
解析出来,并保存到 map[string]string
中。
我们可以使用如下示例代码进行测试Unmarshal
代码逻辑是否正确:
jsonData := `{"name": "测试", "age": 20, "Email": "111@163.com"}`var user User
err := simplejson.Unmarshal([]byte(jsonData), &user)
if err != nil {fmt.Println("Error unmarshal from JSON:", err)return
}fmt.Printf("User struct: %+v\n", user)
执行示例代码,得到如下输出:
$ go run main.go
User struct: {Name:测试 Age:20 Email:111@163.com}
没有任何问题,与原生的 encoding/json
中的 Unmarshal
方法表现一致。
应用二:使用Tag实现字段级别的访问控制
与其他语言对比的话,虽然 Go
的 struct tag
在某种程度上类似于 Java
的注解或C#
的属性,但 Go
的tag
更加简洁,并且主要通过反射机制在运行时被访问。
结构体 tag
在 Go
语言中常见用途,平时最常见有如下这些。
JSON/XML 序列反序列化
如前面的介绍的案例中,通过 encoding/json
或者其他的库如 encoding/xml
库,tag
可以控制如何将结构体字段转换为 JSON
或 XML
,或者如何从它们转换回来。
数据库操作
在ORM(对象关系映射)库中,tag 可以定义数据库表的列名、类型或其他特性。
如我们在使用 Gorm
时,会看到这样的定义:
type User struct {gorm.ModelName string `gorm:"type:varchar(100);unique_index"`Age int `gorm:"index:age"`Active bool `gorm:"default:true"`
}
结构体 tag
可用于定义数据库表的列名、类型或其他特性。
数据验证
在一些库中,tag
用于验证数据,例如,确保一个字段是有效的电子邮件地址。
如下是 govalidator
使用结构体上 tag
实现定义数据验证规则的一个案例。
type User struct {Email string `valid:"email"`Age int `valid:"range(18|99)"`
}
在这个例子中,valid tag
定义了字段的验证规则,如 email
字段值是否是有效的 email
,age
字段是否满足数值在 18
到 99
之间等。
我们只要将类型为 User
类型的变量交给 govalidator
,它可以根据这些规则来验证数据,确保数据的正确性和有效性。
示例如下:
valid, err := govalidator.ValidateStruct(User{Email: "test@example.com", Age: 20})
返回的 valid
为 true
或 false
,如果发生错误,err
提供具体的错误原因。
tag 行为自定义
前面展示的都是利用标准库或三方库提供的能力,如果想自定义 tag
该如何实现?毕竟有些情况下,如果默认提供的tag
提供的能力不满足需求,我们还是希望可以自定义tag
的行为。
这需要了解与理解 Go
的反射机制,它为数据处理和元信息管理提供了强大的灵活性。
如下的示例代码:
type Person struct {Name string `mytag:"MyName"`
}t := reflect.TypeOf(Person{})
field, _ := t.FieldByName("Name")
fmt.Println(field.Tag.Get("mytag")) // 输出: MyName
在这个例子中,我们的 Person
的字段 Name
有一个自定义的 tag - mytag
,我们直接通过反射就可以访问它。
这只是简单的演示如何访问到 tag
。如何使用它呢?
这就要基于实际的场景了,当然,这通常也离不开与反射配合。下面我们来通过一个实际的例子介绍。
案例:结构体字段访问控制
让我们考虑一个实际的场景:一个结构访问控制系统。
这个系统中,我们可以根据用户的角色(如 admin
、user
)或者请求的来源(admin
、web
)控制对结构体字段的访问。具体而言,假设我定义了一个包含敏感信息的结构体,我可以使用自定义 tag
来标记每个字段的访问权限。
是不是想到,这或许可用在 API
接口范围字段的控制上,防止泄露敏感数据给用户。
接下来,具体看看如何做吧?
定义结构体
我们首先定义一个UserProfile
结构体,其中包含用户的各种信息。每个信息字段都有一个自定义的 access tag
,用于标识字段访问权限(admin
或user
)。
type UserProfile struct {Username string `access:"user"` // 所有用户可见Email string `access:"user"` // 所有用户可见PhoneNumber string `access:"admin"` // 仅管理员可见Address string `access:"admin"` // 仅管理员可见
}
其中,PhoneNumber
和Address
是敏感字段,它只对 admin
角色可见。而 UserName
和 Email
则是所有用户可见。
到此,结构体UserProfile
定义完成。
实现权限控制
接下来就是要实现一个函数,实现根据 UserProfile
定义的 access tag
决定字段内容的可见性。
假设函数名称为 FilterFieldsByRole
,它接受一个 UserProfile
类型变量和用户角色,返回内容一个过滤后的 map
(由 fieldname
到 fieldvalue
组成的映射),其中只包含角色有权访问的字段。
func FilterFieldsByRole(profile UserProfile, role string) map[string]string {result := make(map[string]string)val := reflect.ValueOf(profile)typ := val.Type()for i := 0; i < val.NumField(); i++ {field := typ.Field(i)accessTag := field.Tag.Get("access")if accessTag == "user" || accessTag == role {// 获取字段名称fieldName := strings.ToLower(field.Name) // 获取字段值fieldValue := val.Field(i).String() // 组织返回结果 resultresult[fieldName] = fieldValue}}return result
}
权限控制的重点逻辑部分,就是 if accessTag == "user" || accessTag == role
这段判断条件。当满足条件之后,接下来要做的就是通过反射获取字段名称和值,并组织目标的 Map
类变量 result
了。
使用演示
让我们来使用下FilterFieldsByRole
函数,检查下是否满足按角色访问特定的用户信息的功能。
示例代码如下:
func main() {profile := UserProfile{Username: "johndoe",Email: "johndoe@example.com",PhoneNumber: "123-456-7890",Address: "123 Elm St",}// 假设当前用户是普通用户userInfo := FilterFieldsByRole(profile, "user")fmt.Println(userInfo)// 假设当前用户是管理员adminInfo := FilterFieldsByRole(profile, "admin")fmt.Println(adminInfo)
}
输出:
map[username:johndoe email:johndoe@example.com]
map[username:johndoe email:johndoe@example.com phonenumber:123-456-7890 address:123 Elm St]
这个场景,通过自定义结构体 tag
,给予指定角色,很轻松地就实现了一个基于角色的权限控制。
毫无疑问,这个代码更加清晰和可维护,而且具有极大灵活性、扩展性。如果想扩展更多角色,也是更加容易。
不过还是要说明下,如果在API
层面使用这样的能力,还是要考虑反射可能带来的性能影响。
总结
这篇博文介绍了Go
语言中结构体 tag
的基础知识,如是什么,如何使用。另外,还介绍了它们在不同场景下的应用。通过简单的例子和对比,我们看到了Go
中结构体tag
的作用。
reflect
最常用的两个方法分别是 reflect.ValueOf
和 reflect.TypeOf
,调用这两个方法分别可以得到 reflect.Value 结构体
和 reflect.Type接口
类型。
有了这两个类型及其方法,我们可以获取任意一个 Go
对象的类型信息、值的详细信息和操作值,可见反射之强大。
文章的最后,通过两个实际案例,演示了如何使用 struct tag
使我们代码更加灵活强大。 struct tag
的使用不仅非常直观,而且正确地利用这些 tag
可以极大提升我们程序的功能和效率。
总结遍历操作结构体的常用代码:
// 获取结构体的Value和Typeval := reflect.ValueOf(s)typ := val.Type()// 遍历结构体字段for i := 0; i < val.NumField(); i++ {// 获取到字段对应的Value和StructField(包含字段的类型信息,如字段名、Tag,类型,路径,是否匿名等)fieldVal := val.Field(i)fieldType := typ.Field(i)//进行相应的后续处理//如 xxxTag := fieldType.Tag.Get("xxx")}
相关文章:
122. Go反射中与结构体相关的常用方法与应用
文章目录 encoding/jsonreflect 简介reflect.Value 常用方法reflect.Type 常用方法 应用一:使用 reflect 实现 encoding/json序列化反序列化 应用二:使用Tag实现字段级别的访问控制tag 行为自定义案例:结构体字段访问控制 总结 在使用 Go 语言…...
Java入门、进阶、强化、扩展、知识体系完善等知识点学习、性能优化、源码分析专栏分享
场景 作为一名Java开发者,势必经历过从入门到自学、从基础到进阶、从学习到强化的过程。 当经历过几年企业级开发的磨炼,再回头看之前的开发过程、成长阶段发现确实是走了好多的弯路。 作为一名终身学习的信奉者,秉承Java体系需持续学习、…...
Spring-bean销毁
bean销毁(找到销毁的bean) 在bean的声明周期中,存在一个记录bean销毁方法的阶段,以备于spring关闭的时候可以执行bean的销毁方法(单例bean) v1.0 registerDisposableBeanIfNecessary protected void registerDisposableBeanIfNec…...
【4】BlazorUI库
【4】BlazorUI库 一、Blazorise二、Ant Design Blazor三、Radzen Blazo四、Radzen Blazo 一、Blazorise Blazorise Blazorise 是一个广泛使用的 UI 框架,提供了丰富的组件库和多个主题支持,如 Bootstrap、Bulma、Material 和 AntDesign。 二、Ant Desig…...
树与二叉树【下】
目录 三. 哈夫曼树3.1 带权路径长度3.2 哈夫曼树的定义3.3 哈夫曼树的构造3.4 哈夫曼编码(经常考察) 四. 并查集4.1 如何表示“集合”关系?4.2 “并查集”的代码实现4.3 “并查集”的优化4.4 “并查集”的进一步优化 \quad 三. 哈夫曼树 \qua…...
ElementPlus 中el-select自定义指令实现触底加载请求options数据
1) 背景: 老项目翻新时,发现一个下拉框数据非常多,客户呢,希望全部数据一起展示,意思就是全部数据一起返回给前端用于展示。但这会造成明显的卡顿。~~明显的不合理! QAQ!~~ 于是压力给到前端,查询资料,各种…...
基于Selenium实现操作网页及操作windows桌面应用
Selenium操作Web页面 Why? 通常情况下,网络安全相关领域,更多是偏重于协议和通信。但是,如果协议通信过程被加密或者无法了解其协议构成,是无法直接通过协议进行处理。此时,可以考虑模拟UI操作,进而实现相…...
科普文:linux系列之操作系统内存管理简介
概叙 操作系统内存管理是计算机系统中的核心技术之一,页式管理、段式管理和段页式管理各有优缺点。页式管理通过固定大小的页框减少了外部碎片,但可能导致内部碎片;段式管理符合程序逻辑,提供了灵活的内存保护,但可能…...
【已解决】关于MyBatis的collection集合中只能取到一条数据的问题
一、问题 在涉及多表查询的时候,使用collection元素来映射集合属性时,出现了只能查询到一条数据的情况,但用sql语句在数据库中查询会有多条记录。 二、原因 如果两表联查,主表和明细表的主键都是id的话,明细表的多条…...
前端的学习-CSS(弹性布局-flex)
一:什么是弹性布局-Flex flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。 语法: .box{display: flex; } .box{display: inline-flex; } 注意,设为 Flex 布局以后࿰…...
vue3集成LuckySheet实现导入本地Excel进行在线编辑,以及导出功能
第一步:克隆或者下载下面的代码 git clone https://github.com/dream-num/Luckysheet.git第二步:安装依赖 npm install npm install gulp -g 第三步:运行 npm run dev效果如下图所示 第四步:打包 打包执行成功后,…...
【征求意见】同济大学--城镇给水厂碳排放核算与评价方法
城镇给水厂保障城镇居民正常生活,是社会经济良性发展的重要基础性设施,对于我国双碳战略目标的实现至关重要。 随着城镇化的发展,城镇供水量不断升高,加上 水资源与生态环境问题不断涌现,人们对水的安全和品质的需求日…...
【Python】后台开发返回方法和状态码类的实现
Python 后台开发中,获取返回的类方法,以及状态码类的实现 代码备份 Code - response.py """ Response class for quick generate response """ from loguru_logger import get_loggerlogger get_logger(__name__)clas…...
opencloudosV8.6和openEuler 24安装 k8s
在三台机器上部署 Kubernetes 集群 1.环境准备2.在所有节点上进行以下步骤1. 更新系统和安装必要的软件包2. 禁用交换分区3. 禁用防火墙和SElinux4.系统主机名5.设置主机名与IP地址解析6.配置内核转发及网桥过滤7. 配置 Docker Cgroup 驱动8. 添加 Kubernetes 仓库并安装 kubea…...
Tensor安装和测试
1: 打开git官方 https://github.com/NVIDIA/TensorRT 2: 下载得到:TensorRT-10.2.0.19.Linux.x86_64-gnu.cuda-11.8.tar.gz 3: 下载后配置环境变量,上面地址记得改成真实地址。 4: 如果想python使用tensorrt,那么 解压后目录,…...
ELK对业务日志进行收集
ELK对业务日志进行收集 下载httpd 进到文件设置收集httpd的文件进行 设置 编辑内容 用于收集日志的内容 将日志的内容发送到实例当中 input {file{path > /etc/httpd/logs/access_logtype > "access"start_position > "beginning"}file{path &g…...
新质生产力
新质生产力”是一个相对较新的概念,指的是在数字化、智能化背景下,依托新技术、新业态、新模式,提升生产力质量和效率的一种生产力形态。它强调的是技术和创新对生产力的提升作用,尤其是在人工智能、大数据、互联网等新兴技术的推…...
《LeetCode热题100》---<5.②普通数组篇五道>
本篇博客讲解LeetCode热题100道普通数组篇中的六道题 第三道:轮转数组(中等) 第四道:除自身以外数组的乘积(中等) 第三道:轮转数组(中等) 方法一:使用额外的数…...
【面试题】【C语言】寻找两个正序数组的中位数
寻找两个正序数组的中位数 仅供学习 题目 算法时间复杂度 二分查找算法,时间复杂度为 O(log(min(m, n))),其中 m 和 n 分别是两个数组的长度。 子函数 查找两个数字的最大值 int max(int a, int b) {return a > b ? a : b; }查找两个数字的最小…...
原始的原型链是怎样玩的
带着问题看代码: 1、原始的继承是怎样实现继承的? A类的prototype 属性 B类的实例 2、实现继承后,连B类的中实例的属性(放在了A类的prototype中)和原型链的上的东西都可以用 3、A.prototype.constructor实际上已经指向…...
RabbitMQ高级篇(如何保证消息的可靠性、如何确保业务的幂等性、延迟消息的概念、延迟消息的应用)
文章目录 1. 消息丢失的情况2. 生产者的可靠性2.1 生产者重连2.2 生产者确认2.3 生产者确认机制的代码实现2.4 如何看待和处理生产者的确认信息 3. 消息代理(RabbitMQ)的可靠性3.1 数据持久化3.2 LazyQueue( 3.12 版本后所有队列都是 Lazy Qu…...
正点原子imx6ull-mini-Linux驱动之platform设备驱动实验(14)
我们在前面几章编写的设备驱动都非常的简单,都是对IO进行最简单的读写操作像I2C、 SPI、LCD 等这些复杂外设的驱动就不能这么去写了,Linux 系统要考虑到驱动的可重用性,因此提出了驱动的分离与分层这样的软件思路,在这个思路下诞生…...
z3基础学习
z3基础学习 z3是一个微软出品的开源约束求解器,能够解决很多种情况下的给定部分约束条件寻求一组满足条件的解的问题。 安装:pip install z3-solver 1. 简单使用 from z3 import * x Int(x) #创建名为x的int类型变量 y Int(y) solve(x y10,2*x…...
开发助手专业版,有反编译等多种功能
软件介绍 开发助手能够用来快速调试应用以及查看手机软硬件相关信息,包括:快速打开或关闭开发者选项中的选项。 将原来几十秒的操作缩短为一次点击。包括显示布局边界,显示 GPU 过度绘制。显示布局更新。强制 GPU 渲染 显示 GPU 视图更新&a…...
嵌入式初学-C语言-十一
#接嵌入式初学-C语言-十,以及部分例题# 循环结构 break和continue break 功能: 1. 用在switch中,用来跳出switch的case语句;如果case没有break,可能会产生case穿透。 2. 用在循环中(while、do..while、for..&#…...
浅谈几个常用OJ的注册方式
众所周知,好的OJ是成功的一半,但是有些英文OJ的注册很让人伤脑筋。 CodeForces 点进官网 戳这里 然后就会进入这个页面 在这一页里面里填写好信息即可 最后,一个邮件就会发到你的邮箱上,点击其中的链接即可激活账号 AtCoder …...
Html实现全国省市区三级联动
目录 前言 1.全国省市区的Json数据 2.找到Json数据文件(在此博文绑定资源)之后,放到resource目录下。 3.通过类加载器加载资源文件,读取Json文件 3.1 创建JsonLoader类 3.2 注入JsonLoader实体,解析Json文件 4.构建前端Html页面 5.通过…...
前端构建工具Webpack 与 Vite 大对比
在现代前端开发领域,构建工具扮演着至关重要的角色。它们不仅可以帮助我们管理项目依赖关系,还可以优化我们的代码,使其在生产环境中运行得更快更高效。其中两个最受欢迎的构建工具就是 Webpack 和 Vite。在这篇文章中,我们将深入…...
Ubuntu-22.04环境搭建
安装wget(一般ubuntu会自带) sudo apt-get install wget 更换国内软件源 先备份原来的/etc/apt/source.list⽂件 sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak 防止修改错误 导致无可挽回 将下列国内镜像源 写入原来的/etc/apt/source.list⽂件(注…...
嵌入式学习---DAY17:共用体与位运算
链表剩余的一些内容 一、共用体 union 共用体名 名称首字母大写 { 成员表列; }; union Demo {int i;short s;char c; }; int main(void) {union Demo d;d.i 10;d.s 100;d.c 200;printf("%d\n", sizeof(d)); /…...
医疗器械网站制作/seo关键词是怎么优化的
歌曲和歌词放在同一目录下,再上传到网易云网盘,同时要满足以下条件: 1.歌词和歌名必须完全一致,除了后缀名。 2.歌词为lrc后缀文件,且有时间轴。(没有时间轴就不会滚动歌词,这个我还没试过。&…...
asp.net 网站管理工具/seo综合查询网站源码
MySQL8 创建用户,设置修改密码,授权 MySQL5.7可以 (创建用户,设置密码,授权) 一步到位 👇 GRANT ALL PRIVILEGES ON *.* TO 用户名% IDENTIFIED BY 密码 WITH GRANT OPTION👆这样的语句在MySQL8.0中行不通, 必须 创设和授权 分步执行👇 CR…...
东莞网站建设设计公司/十大品牌营销策划公司
2019独角兽企业重金招聘Python工程师标准>>> 其实想做聚焦时提示文字不消失,光标在提示语前面,就像正常的placeholder属性,但是setSelectionRance()方法不是很好用,后面学习之后再制作 <script type"text/javascript"> (function($,win,do…...
产品的seo是什么意思/seo外链发布软件
1.当自变量的数值确定后,因变量的数值也随之完全确定,这种关系属于()。 A.相关关系 B.函数关系 C.回归关系 D.无关 2.对某地区工业企业职工情况进行研究,统计总体是() A.每个工业企业 B.该地…...
网站301做排名/东莞做网站哪家好
需求:给一个数组arr,给一个数num,将数组中小于num的数放在左边,等于num的数放在中间,大于num的数全部放在右边。 代码: import java.util.Arrays;public class HeLanGuoQi {public static void main(String…...
ASP.NET网站建设实战/百度手机应用市场
锁在日常的开发过程中,为了控制线程的并发肯定会用到锁机制。对于数据库而言,锁机制就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则。当然MySQL也不例外,根据不同的存储引擎,M…...