The ideas of this paper refer to: https://golangbot.com/reflection/ , the content of this article is not just a simple translation of the original text. Please see the content below ~!
Prepare to take you to understand and master reflection knowledge points by using reflection as an example of a general SQL constructor. After reading an example written by a foreign blogger, I think the idea is very good. I improved it to enrich the implementation of the constructor.
What is reflection?
A: reflection is that in the running state, all properties and methods of any class can be known, and any method and property of any object can be called. This dynamically obtained information and the function of dynamically calling the method of the object
Why reflection?
Golang reflective understanding - Go language Chinese network - golang Chinese community
When learning reflection, the first question everyone will think of is "why do we check the type of variables at run time? Don't we have specified the type of variables in the program when they are defined?" it's true, but it's not always the case. When you see this, you may think, brother, what are you talking about, em... Let's write a simple program first and explain it.
package main import ( "fmt" ) func main() { a := 100 fmt.Printf("%d %T", a, a) }
In the above program, the type of variable a is known at compile time. We print its value and type on the next line.
Let's understand "the need to know the type of variable at run time". Suppose we want to write a simple function that takes a structure as a parameter and uses this parameter to create an SQL insert statement.
package main import ( "fmt" ) type order struct { ordId int customerId int } func main() { o := order{ ordId: 20211103000001, customerId: 567, } fmt.Println(o) }
We need to write an SQL statement that receives the structure o defined above as a parameter and returns an insert into order values (202111030000001, 567). This function definition is easy to write, such as the following.
package main import ( "fmt" ) type order struct { ordId int customerId int } func createQuery(o order) string { i := fmt.Sprintf("INSERT INTO order VALUES(%d, %d)", o.ordId, o.customerId) return i } func main() { o := order{ ordId: 20211103000001, customerId: 567, } fmt.Println(createQuery(o)) }
The createQuery in the above example uses the parameter o Create SQL using the OrderID and customerId fields of.
Now let's define our SQL creation function more abstractly. Let's use the program to illustrate it. For example, we want to generalize our SQL creation function to make it applicable to any structure.
package main type order struct { ordId int customerId int } type employee struct { name string id int address string salary int country string } func createQuery(q interface{}) string { }
Now our goal is to transform the createQuery function so that it can accept any structure as a parameter and create INSERT based on the structure field Statement. For example, if the parameter passed to createQuery is no longer an order type structure, but an employee type structure
e := employee { name: "Naveen", id: 565, address: "Science Park Road, Singapore", salary: 90000, country: "Singapore", } ### The INSERT statement it should return should be INSERT INTO employee (name, id, address, salary, country) VALUES("Naveen", 565, "Science Park Road, Singapore", 90000, "Singapore")
Because createQuery The function needs to apply to any structure, so it needs a For the sake of simplicity, let's assume that the createQuery function only deals with parameters containing string s and int The structure of the type field.
The only way to write this createQuery function is to check the type of parameters passed to it at run time, find its fields, and then create SQL. Here is where reflection is needed. In the next steps, we will learn how to use the reflection package of Go language to achieve this.
Go language reflection package
The reflection package of Go language implements the function of reflection at runtime. This package can help identify the specific type and value of an interface {} type variable. After receiving an interface {} type argument, our createQuery function needs to create and return an INSERT statement according to the underlying type and value of the argument, which is the function of the reflection package.
Before writing our general SQL generator function, we need to understand several types and methods we will use in the reflect package. Next, we will learn them one by one.
reflect.Type and reflect.Value
After reflection, the underlying specific type of a variable of interface {} type is represented by reflect. Type, and the underlying value is represented by reflect. Value. There are two functions in the reflect package, reflect.TypeOf() And reflect.ValueOf() Variables of interface {} type can be converted to reflect.Type and reflect.Value respectively. These two types are the basis for creating our SQL generator function.
package main import ( "fmt" "reflect" ) type order struct { ordId int customerId int } func createQuery(q interface{}) { t := reflect.TypeOf(q) v := reflect.ValueOf(q) fmt.Println("Type ", t) fmt.Println("Value ", v) } func main() { o := order{ ordId: 456, customerId: 56, } createQuery(o) } Run output: Type main.order Value {456 56}
In the above program, the createQuery function receives an interface {} type argument and passes the argument to reflect.Typeof and reflect.Valueof From the output, we can see that the program outputs the underlying specific types and values corresponding to the interface {} type arguments.
Three principles of Go language reflection
The three principles of reflection are:
-
The reflection object can be reflected from the interface value.
-
The interface value can be reflected from the reflection object.
-
To modify a reflective object, its value must be settable.
The first rule of reflection is that we can convert the interface type variables in Go into reflection objects, such as reflect.TypeOf and reflect.ValueOf The second refers to that we can convert the variable of reflection type back to the interface type, and the last relates to whether the reflection value can be changed.
Next, let's continue to understand the reflection knowledge required to complete our SQL generator.
reflect.Kind
There is also a very important type in the reflect package, reflect.Kind.
The types of reflect.Kind and reflect.Type may look very similar. In terms of naming, Kind and Type can be used interchangeably in some phrases in English, but they are quite different in reflection, which can be clearly seen from the following program.
package main import ( "fmt" "reflect" ) type order struct { ordId int customerId int } func createQuery(q interface{}) { t := reflect.TypeOf(q) k := t.Kind() fmt.Println("Type ", t) fmt.Println("Kind ", k) } func main() { o := order{ ordId: 456, customerId: 56, } createQuery(o) } Run output: Type main.order Kind struct
The output makes us clear the difference between the two. reflect.Type Represents the actual type of the interface, i.e. main.order in this example Kind indicates the type to which the type belongs, that is, main.order is a "struct" type, and kind of map[string]string should be "map".
Method for obtaining structure field by reflection
We can obtain the type attribute of the field under the structure through the method of type reflect.StructField. reflect.StructField can be obtained through the following two methods provided by reflect.Type.
// Gets the number of fields in a structure NumField() int // Get the type object of the field in the structure according to the index Field(i int) StructField // Gets the type object of the field in the structure according to the field name FieldByName(name string) (StructField, bool)
reflect.structField is a struct type. Through it, we can know the basic type, Tag, exported or not of the field in reflection.
type StructField struct { Name string Type Type // field type Tag StructTag // field tag string ...... }
Corresponding to the method of obtaining Field information provided by reflect.Type, reflect.Value also provides the method of obtaining Field value.
We need to pay attention to this, otherwise it is easy to be confused. Let's try to get the field name and value of the order structure type through reflection
package main import ( "fmt" "reflect" ) type order struct { ordId int customerId int } func createQuery(q interface{}) { t := reflect.TypeOf(q) if t.Kind() != reflect.Struct { panic("unsupported argument type!") } v := reflect.ValueOf(q) for i:=0; i < t.NumField(); i++ { fmt.Println("FieldName:", t.Field(i).Name, "FiledType:", t.Field(i).Type, "FiledValue:", v.Field(i)) } } func main() { o := order{ ordId: 456, customerId: 56, } createQuery(o) } Run output: FieldName: ordId FiledType: int FiledValue: 456 FieldName: customerId FiledType: int FiledValue: 56
In addition to obtaining the name and value of the structure field, you can also obtain the Tag of the structure field.
Convert reflect.Value to actual value
Now we are still one step away from completing our SQL generator, that is, we need to convert reflect.Value into a value of actual type. reflect.Value implements a series of Int(), String(), Float() to complete the conversion to the actual type value.
Reflection is an SQL generator
Above, we have learned all the necessary knowledge points before writing this SQL generator function. Next, we will string them to complete the createQuery function.
The complete implementation and test code of SQL generator is as follows:
package main import ( "fmt" "reflect" ) type order struct { ordId int customerId int } type employee struct { name string id int address string salary int country string } func createQuery(q interface{}) string { t := reflect.TypeOf(q) v := reflect.ValueOf(q) if v.Kind() != reflect.Struct { panic("unsupported argument type!") } tableName := t.Name() // Extract the SQL table name through the structure type sql := fmt.Sprintf("INSERT INTO %s ", tableName) columns := "(" values := "VALUES (" for i := 0; i < v.NumField(); i++ { // Note that reflect.Value also implements numfield and kind methods // v.Field(i).Kind() here is equivalent to t.Field(i).Type.Kind() switch v.Field(i).Kind() { case reflect.Int: if i == 0 { columns += fmt.Sprintf("%s", t.Field(i).Name) values += fmt.Sprintf("%d", v.Field(i).Int()) } else { columns += fmt.Sprintf(", %s", t.Field(i).Name) values += fmt.Sprintf(", %d", v.Field(i).Int()) } case reflect.String: if i == 0 { columns += fmt.Sprintf("%s", t.Field(i).Name) values += fmt.Sprintf("'%s'", v.Field(i).String()) } else { columns += fmt.Sprintf(", %s", t.Field(i).Name) values += fmt.Sprintf(", '%s'", v.Field(i).String()) } } } columns += "); " values += "); " sql += columns + values fmt.Println(sql) return sql } func main() { o := order{ ordId: 456, customerId: 56, } createQuery(o) e := employee{ name: "Naveen", id: 565, address: "Coimbatore", salary: 90000, country: "India", } createQuery(e) }
Output the corresponding standard SQL insert statement
Clear is better than clever. Reflection is never clear.
Reflection is a very powerful and advanced concept in Go and it should be used with care. It is very difficult to write clear and maintainable code using reflection. It should be avoided wherever possible and should be used only when absolutely necessary.