Golang反射

文章目录

  • 基本介绍
  • reflect包
    • reflect.Type
    • reflect.Value
    • reflect.Kind
    • 具体类型、空接口与reflect.Value的相互转换
  • 反射应用场景
    • 修改变量的值
    • 访问结构体的字段信息
    • 调用变量所绑定的方法
    • 实现函数适配器
    • 创建任意类型的变量

基本介绍

基本介绍

  • 在Go中,反射(reflection)是一种机制,其允许程序在运行时检查并操作变量、类型和结构的信息,而不需要提前知道它们的具体定义,使得代码更加灵活和通用。
  • 反射通常用于动态获取获取类型信息、动态创建对象、动态调用函数、动态修改对象等,在实现反射时需要用到reflect包。
  • 需要注意的是,虽然反射的功能强大,但由于其使用了运行时的类型检查和动态调用,在性能上可能会有一定的开销,因此在性能敏感的场景中,应该尽量避免过度依赖反射来实现常规的编程任务。

reflect包

reflect.Type

reflect.Type

  • reflect.Type是reflect包中的一个接口类型,用于表示任意变量的类型信息。
  • 通过reflect包中的TypeOf函数,可以获取指定变量的类型信息。

reflect.TypeOf函数的函数原型如下:

func TypeOf(i interface{}) Type

reflect.Type接口中常用的方法如下:

方法名功能
Kind获取该类型对应的Kind
Size获取该类型的大小
Elem获取该类型的元素的Type
NumField获取结构体类型的字段数
NumMethod获取该类型所绑定的方法数
Field获取结构体类型的第i个字段的信息
Method获取该类型所绑定的第i个方法的信息
FieldByName获取结构体类型的字段中,指定字段名的字段信息
MethodByName获取该类型所绑定的方法中,指定方法名的方法信息
NumIn获取函数/方法类型的参数个数
In获取函数/方法类型的第i个参数的Type
NumOut获取函数/方法类型的返回值个数
Out获取函数/方法类型的第i个返回值的Type

说明一下:

  • reflect.Type接口中的方法不需要用户手动实现,这些方法由反射系统在运行时为每个类型自动生成。
  • reflect.Type接口中的方法不是对所有类型都能使用,每个方法都有其特定的适用范围和前提条件,如果在不满足调用条件的情况下调用了某个方法,则会触发panic异常。比如Elem方法只适用于数组、channel、map、指针和切片类型,NumField和Field方法只适用于结构体类型,NumIn、In、NumOut和Out方法只适用于函数或方法类型。

字段信息

通过reflect.Type接口的Field或FieldByName方法,能够获取结构体中某个字段的字段信息,获取到的字段信息通过StructField结构体进行描述。StructField结构体的定义如下:

type StructField struct {Name string    // field namePkgPath string // package pathType      Type      // field typeTag       StructTag // field tag stringOffset    uintptr   // offset within struct, in bytesIndex     []int     // index sequence for Type.FieldByIndexAnonymous bool      // is an embedded field
}

字段说明:

  • Name:表示该字段的名称。
  • PkgPath:表示该字段所在的包路径,对于可导出的字段,PkgPath为空字符串。
  • Type:表示该字段对应的reflect.Type。
  • Tag:表示该字段的Tag标签信息。
  • Offset:表示该字段在结构体中的偏移量。
  • Index:表示该字段的索引序列。
  • Anonymous:表示该字段是否为匿名字段。

方法信息

通过reflect.Type接口的Method或MethodByName方法,能够获取对应类型所绑定的某个方法的方法信息,获取到的方法信息通过Method结构体进行描述。Method结构体的定义如下:

type Method struct {Name string    // method namePkgPath string // package pathType  Type  // method typeFunc  Value // func with receiver as first argumentIndex int   // index for Type.Method
}

字段说明:

  • Name:表示该方法的名称。
  • PkgPath:表示该方法所在的包路径,对于可导出的方法,PkgPath为空字符串。
  • Type:表示该方法对应的reflect.Type。
  • Func:表示该方法对应的reflect.Value。
  • Index:表示该方法在对应类型的方法集中的索引。

reflect.Value

reflect.Value

  • reflect.Value是reflect包中的一个类型,用于表示任意变量的值。
  • 通过reflect包中的ValueOf函数,可以获取持有指定变量的Value。
  • 通过reflect包中的New函数,可以创建指定类型的变量,并获取持有指向该变量的指针的Value。

reflect.ValueOf函数的函数原型如下:

func ValueOf(i interface{}) Value
func New(typ Type) Value

reflect.Value类型常用的方法如下:

方法名功能
Kind获取所持有的值对应的Kind
Type获取所持有的值对应的Type
Elem获取所持有的接口保管的值的Value封装,或获取所持有的指针指向的值的Value封装
Index获取所持有的值的第i个元素的Value封装
NumField获取所持有的结构体类型值的字段数
NumMethod获取所持有的值所绑定的方法数
Field获取所持有的结构体类型值的第i个字段的Value封装
Method获取所持有的值所绑定的第i个方法的函数形式的Value封装
FieldByName获取所持有的结构体类型值的字段中,指定字段名的字段的Value封装
MethodByName获取所持有的值所绑定的方法中,指定方法名的方法的函数形式的Value封装
Call指定参数调用所持有的函数,返回函数返回值的Value封装
Interface返回所持有的值的interface{}类型值
Int、Float、Bool、String、Pointer返回所持有的值的对应类型值,如果所持有的值不是对应的类型,则会触发panic异常
SetInt、SetFloat、SetBool、SetString、SetPointer设置所持有的值,如果所持有的值不是对应的类型,则会触发panic异常
Set将所持有的值设置为指定Value所持有的值,指定Value所持有值的类型必须与当前所持有值的类型相同,否则会触发panic异常

说明一下:

  • reflect.Value类型的方法不是对所有类型都能使用,每个方法都有其特定的适用范围和前提条件,如果在不满足调用条件的情况下调用了某个方法,则会触发panic异常。比如Elem方法只适用于接口和指针类型,NumField和Field方法只适用于结构体类型,Index方法只适用于数组、channel、切片和字符串类型,Call方法只适用于函数或方法类型。
  • 通过Method或MethodByName方法,获取v所持有的值所绑定的某个方法的函数形式的Value封装时,返回值持有的函数总是使用v所持有的值作为receiver(即第一个参数),因此返回值在调用Call方法时不用手动传入receiver参数。

reflect.Value与reflect.Type

reflect.Value是一个具体的类型,而reflect.Type被设计成了一个接口类型。其原因如下:

  • 类型的信息需要反射系统在运行时为每个类型自动生成,以适应各种未知的类型,将reflect.Type定义成接口类型的目的就是,指明运行时需要为每个类型生成哪些方法。
  • reflect.Value提供了各种访问和操作所持有值的方法,在使用reflect.Value时,通常已经知道所持有值的具体类型,这时通过reflect.Type即可获取到所持有值的类型信息,运行时不需要为其动态的生成任何方法,因此最终将reflect.Value定义成具体类型。

reflect.Kind

reflect.Kind

  • reflect.Kind是reflect包中的一个类型,用于表示类型的类别。
  • reflect.Type和reflect.Value都提供了对应的Kind方法,用于获取类型的Kind。

reflect.Kind本质是一个常量枚举类型。其定义如下:

type Kind uintconst (Invalid Kind = iotaBoolIntInt8Int16Int32Int64UintUint8Uint16Uint32Uint64UintptrFloat32Float64Complex64Complex128ArrayChanFuncInterfaceMapPointerSliceStringStructUnsafePointer
)

说明一下:

  • 类型和类别可能是一对一的,比如int类型对应的Kind是Int,float32类型对应的Kind是Float32。类型和类别也可能是一对多的,比如所有结构体类型对应的Kind都是Struct,所有指针类型对应的Kind都是Pointer。
  • 在获取变量的reflect.Value时,ValueOf函数或提取出接口值中对应的动态类型和动态值,并返回具体类型的Value。如果需要创建一个Kind为Interface的Value,可以先通过ValueOf函数获取一个指向接口的指针的Value,然后通过Value的Elem方法获取Value持有的指针指向的值的Value封装,这时获取到的Value的Kind就是Interface。

具体类型、空接口与reflect.Value的相互转换

具体类型、空接口与reflect.Value的相互转换

在反射过程中,变量的类型经常需要在具体类型、空接口类型和reflect.Value类型之间进行转换。其转换的方式如下:

  • 将变量由具体类型转换为空接口类型时,直接通过变量赋值的方式即可。
  • 将变量由空接口类型转换为reflect.Value类型时,通过调用reflect.ValueOf函数即可。
  • 将变量由reflect.Value类型转换为空接口类型时,通过调用reflect.Value的Interface方法即可。
  • 将变量由空接口类型转为具体类型时,需要借助类型断言。

转换示意图如下:

在这里插入图片描述

转换案例如下:

package mainimport ("fmt""reflect"
)type Student struct {Name stringAge  int
}func Reflect(iVal interface{}) { // 具体类型->interface{}rVal := reflect.ValueOf(iVal) // interface{}->reflect.ValueiVal2 := rVal.Interface() // reflect.Value->interface{}switch val := iVal2.(type) { // interface{}->具体类型case Student:fmt.Printf("type = %T, value = %v\n", val, val)case int:fmt.Printf("type = %T, value = %v\n", val, val)case float64:fmt.Printf("type = %T, value = %v\n", val, val)default:fmt.Printf("unknown type: %T\n", val)}
}func main() {var stu = Student{"Alice", 14}Reflect(stu) // type = main.Student, value = {Alice 14}Reflect(1)   // type = int, value = 1Reflect(1.2) // type = float64, value = 1.2
}

反射应用场景

修改变量的值

修改变量的值

通过反射可以修改变量的值,具体步骤如下:

  1. 通过reflect.ValueOf函数,获取指向该值的指针的Value封装v1。
  2. 通过Value的Elem方法,获取v1所持有的指针指向的值的Value封装v2。
  3. 通过Value的Set系列方法,设置v2所持有的值,完成对变量的修改。

案例如下:

package mainimport ("fmt""reflect"
)func Reflect(iVal interface{}) {rVal := reflect.ValueOf(iVal) // 获取指向该值的指针的Value封装switch val := iVal.(type) {case *int:rVal.Elem().SetInt(20) // 获取所持有的指针指向的值的Value封装,并设置所持有的值default:fmt.Printf("unknown type: %T\n", val)}
}func main() {var a = 10Reflect(&a) // 传入的是指向变量的指针fmt.Printf("a = %d\n", a) // a = 20
}

说明一下:

  • 通过反射修改变量的值时,需要通过指向对应变量的指针来修改,这样反射内部才能找到需要被修改的变量并对其进行修改。在修改变量的值时,需要先通过Value的Elem方法获取所持有的指针指向的值的Value封装(可以理解成对指针解引用),然后再调用Set系列方法修改变量的值。
  • 除了通过Set系列方法修改变量的值外,也可以使用Set方法将当前Value所持有的值设置为另一个Value所持有的值。

访问结构体的字段信息

访问结构体的字段信息

通过反射可以访问结构体的字段信息,具体步骤如下:

  1. 通过reflect.ValueOf和reflect.TypeOf函数,分别获取结构体变量的Value和Type。
  2. 通过Value或Type的NumField方法,获取结构体的字段数。
  3. 通过Value的Field方法,获取指定索引字段的Value。
  4. 通过Type的Field方法,获取指定索引字段的各种信息。

案例如下:

package mainimport ("fmt""reflect"
)type Student struct {Name string `json:"name"`Age  int    `json:"age"`
}func Reflect(iVal interface{}) {rVal := reflect.ValueOf(iVal)rType := reflect.TypeOf(iVal)rKind := rType.Kind()if rKind != reflect.Struct { // 确保传入的变量是结构体类型return}// 访问结构体的字段信息num := rType.NumField() // 获取结构体的字段数for i := 0; i < num; i++ {fieldInfo := rType.Field(i) // 获取结构体第i个字段的信息filedValue := rVal.Field(i) // 获取结构体第i个字段的Value封装fmt.Printf("field[%d] name = %s\ttype = %v\ttag = %s\tvalue = %v\n",i, fieldInfo.Name, fieldInfo.Type, fieldInfo.Tag, filedValue)}
}func main() {var stu = Student{"Alice", 14}Reflect(stu)
}

程序的运行结果如下:

在这里插入图片描述

说明一下:

  • 上述代码中通过对变量的Kind进行判断,以确保传入的变量是结构体类型。
  • json.Marshal函数在对结构体变量进行JSON序列化时,在函数内部就是通过反射来获取结构体字段的Tag标签的。

调用变量所绑定的方法

调用变量所绑定的方法

通过反射可以调用变量所绑定的方法,具体步骤如下:

  1. 通过reflect.ValueOf和reflect.TypeOf函数,分别获取变量的Value和Type。
  2. 通过Value或Type的NumMethod方法,获取变量对应的类型所绑定的方法数。
  3. 通过Value的Method方法,获取指定索引方法的Value。
  4. 通过Type的Method方法,获取指定索引方法的各种信息。
  5. 通过Value的Call方法,调用所持有的方法。

案例如下:

package mainimport ("fmt""reflect"
)type Student struct {Name string `json:"name"`Age  int    `json:"age"`
}func (stu Student) Study() {fmt.Printf("Study: student %s is studying...\n", stu.Name)
}func (stu *Student) UpdateAge(age int) {stu.Age = agefmt.Printf("UpdateAge: update %s age = %d...\n", stu.Name, stu.Age)
}func (stu Student) StuInfo() {fmt.Printf("StuInfo: name = %s, age = %d...\n", stu.Name, stu.Age)
}func Reflect(iVal interface{}) {rVal := reflect.ValueOf(iVal)rType := reflect.TypeOf(iVal)switch val := iVal.(type) {case *Student, Student:fmt.Printf("------type = %v------\n", rType)// 调用变量对应的类型所绑定的方法num := rType.NumMethod() // 获取该类型所绑定的方法数fmt.Printf("method num = %d\n", num)for i := 0; i < num; i++ {methodVal := rVal.Method(i)         // 获取该类型所绑定的第i个方法的Value封装methodInfo := rType.Method(i)       // 获取该类型所绑定的第i个方法的信息if methodInfo.Name == "UpdateAge" { // 调用时需要传参var args []reflect.Valueargs = append(args, reflect.ValueOf(18))methodVal.Call(args) // 调用方法} else {methodVal.Call(nil) // 调用方法}}default:fmt.Printf("unknown type: %T\n", val)}
}func main() {var stu1 = Student{"Alice", 14}Reflect(&stu1)fmt.Printf("stu1 = %v\n", stu1) // stu1 = {Alice 18}var stu2 = Student{"Bob", 14}Reflect(stu2)fmt.Printf("stu2 = %v\n", stu2) // stu2 = {Bob 14}
}

程序的运行结果如下:

在这里插入图片描述

说明一下:

  • 通过反射获取变量对应的类型所绑定的方法数,以及获取指定索引方法的Value封装或方法信息时,如果变量的类型是type,则只能访问到receiver为type的方法,如果变量的类型是*type,则能同时访问到receiver为type*type的方法。
  • 因为receiver为*type的方法中可能会对变量的值进行修改,为了让反射内部能够找到需要被修改的变量并对其进行修改,这就要求变量的类型必须是*type,因此如果变量的类型是type,那就无法访问到receiver为*type的方法。
  • Value的Call方法接收一个类型为[]Value的参数,表示在调用Value所持有的函数或方法时,需要传入的各个参数的Value封装,如果被调用的函数或方法无需传入任何参数,则调用Call方法时传入nil即可。同时Call方法会返回一个[]Value类型的返回值,表示调用Value所持有的函数或方法得到的各个返回值的Value封装。

实现函数适配器

实现函数适配器

通过反射可以实现函数适配器,具体步骤如下:

  1. 通过reflect.ValueOf函数,获取函数的Value。
  2. 对用户传入的用于调用函数的参数进行Value封装,并放到Value切片中。
  3. 通过Value的Call方法,指定参数调用所持有的函数,并返回函数调用的返回值。

案例如下:

package mainimport ("errors""fmt""reflect"
)func AddTwo(num1 int, num2 int) int {return num1 + num2
}func AddThree(num1 int, num2 int, num3 int) int {return num1 + num2 + num3
}func Bridge(f interface{}, args ...interface{}) (ret int, err error) {rVal := reflect.ValueOf(f)rKind := rVal.Kind()if rKind != reflect.Func {err = errors.New("the first arg is not a function")return}// 对传入的参数进行Value封装,并放到Value切片中num := len(args)argVals := make([]reflect.Value, num)for i := 0; i < num; i++ {argVals[i] = reflect.ValueOf(args[i])}retVals := rVal.Call(argVals) // 调用函数ret = int(retVals[0].Int())return
}func main() {ret, err := Bridge(AddTwo, 10, 20)if err != nil {fmt.Printf("err = %v\n", err)} else {fmt.Printf("ret = %d\n", ret) // ret = 30}ret, err = Bridge(AddThree, 10, 20, 30)if err != nil {fmt.Printf("err = %v\n", err)} else {fmt.Printf("ret = %d\n", ret) // ret = 60}
}

创建任意类型的变量

创建任意类型变量

通过反射可以创建任意类型的变量,具体步骤如下:

  1. 通过reflect.ValueOf和reflect.TypeOf函数,分别获取二级指针的Value和Type,并继续通过Value和Type的Elem方法,分别获取二级指针指向的一级指针的Value和Type。
  2. 再次通过Type的Elem方法,继续获取一级指针指向的元素的Type,即需要创建的变量的类型。
  3. 通过reflect.New函数,创建指定Type的变量,并获取持有指向该变量的指针的Value封装elemVal。
  4. 通过Value的Set方法,将一级指针所持有的值设置为elemVal所持有的值,让一级指针指向创建的变量。

案例如下:

package mainimport ("fmt""reflect"
)type Student struct {Name stringAge  int
}func CreateObj(iVal interface{}) {rType := reflect.TypeOf(iVal).Elem() // 获取二级指针指向的一级指针的TyperVal := reflect.ValueOf(iVal).Elem() // 获取二级指针指向的一级指针的ValuerKind := rType.Kind()if rKind != reflect.Ptr { // 确保传入的是二级指针(该类型指向的是一个指针类型)return}elemType := rType.Elem()         // 获取一级指针指向的元素的TypeelemVal := reflect.New(elemType) // 创建elemType类型的变量,并获取持有指向该变量的指针的ValuerVal.Set(elemVal)                // 将一级指针所持有的值设置为elemVal所持有的值
}func main() {var p1 *Studentfmt.Printf("p1 = %v\n", p1) // p1 = <nil>CreateObj(&p1)fmt.Printf("p1 = %v\n", p1) // p1 = &{ 0}var p2 *intfmt.Printf("p2 = %v\n", p2) // p2 = <nil>CreateObj(&p2)fmt.Printf("p2 = %v\n", p2) // p2 = 0xc00000e0f8
}

说明一下:

  • 在创建变量时需要提供一个*type类型的指针,然后根据指针的类型创建一个type类型的变量,并让该指针指向这个变量,完成变量的创建。由于最终需要修改所给指针变量的指向,因此在调用CreateObj函数时需要传入该指针的地址(二级指针)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/19792.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

错误 0x80070570:文件或目录损坏且无法读取/无法访问[拒绝访问]-解决方法

1.起因&#xff1a;在挪动&#xff35;盘文件时&#xff0c;出现无法移动的报错提示&#xff1a; and无法访问[拒绝访问]: 2.原因&#xff3b;大多是胡乱拔出&#xff35;盘&#xff3d; &#xff3b;来自0x80070570 文件或目录损坏且无法读取 CHKDSK 修复方法-CSDN博客&#…

iOS AVFoundation 音视频源码分享

引言 在现代移动开发中&#xff0c;音视频处理是一个不可忽视的重要领域。iOS 提供了强大的 AVFoundation 框架&#xff0c;使开发者能够轻松实现音视频录制、播放、编辑等功能。无论是创建高效的视频播放器&#xff0c;还是实现复杂的音频处理&#xff0c;AVFoundation 都能提…

Leecode---买卖股票最大利润问题

121—题目&#xff08;只能买卖一次&#xff09;&#xff1a; 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所…

python-pytorch编写transformer模型实现问答0.5.00--训练和预测

python-pytorch编写transformer模型实现问答0.5.00--训练和预测 背景代码训练预测效果背景 代码写不了这么长,接上一篇 https://blog.csdn.net/m0_60688978/article/details/139360270 代码 # 定义解码器类 n_layers = 6 # 设置 Decoder 的层数 class Decoder(nn.Module)…

【JavaEE进阶】——带你详细了解Spring日志以及配置日志

目录 &#x1f6a9;Spring日志的认识 &#x1f6a9;Spring日志的作用 &#x1f6a9;观察日志 &#x1f6a9;使用日志 &#x1f388;在程序中得到日志对象 &#x1f388;使⽤⽇志对象输出要打印的内容 &#x1f6a9;日志框架的介绍 &#x1f388;门面模式(外观模式&…

Unity实现简单的第一人称控制

先看效果 实现方式 1.首先创建一个脚本 2.编辑脚本内容 付上脚本代码 private float RotationX 0;public float speed 2f;//移动速度// Use this for initializationvoid Start(){Cursor.lockState CursorLockMode.Locked;//锁定鼠标到中心点Cursor.visible false;//隐藏鼠…

(CPU/GPU)粒子继承贴图颜色发射

GetRandomInfo节点(复制贴进scratch pad Scripts) Begin Object Class/Script/NiagaraEditor.NiagaraClipboardContent Name"NiagaraClipboardContent_22" ExportPath/Script/NiagaraEditor.NiagaraClipboardContent"/Engine/Transient.NiagaraClipboardConten…

uni-app+php 生成微信二维码 分销海报

主要代码如下&#xff0c;可直接复制调试参数&#xff1a; //查询当前用户是否有分销海报public function user_poster(){$this->checkAuth();//查询会员信息$user $this->getUserInfoById($this->user_id);if(!empty($user[distribution_img])){$result[data] $use…

深入解析力扣170题:两数之和 III - 数据结构设计(哈希表与双指针法详解及模拟面试问答)

在本篇文章中&#xff0c;我们将详细解读力扣第170题“两数之和 III - 数据结构设计”。通过学习本篇文章&#xff0c;读者将掌握如何设计一个数据结构来支持两种操作&#xff0c;并了解相关的复杂度分析和模拟面试问答。每种方法都将配以详细的解释和ASCII图解&#xff0c;以便…

头歌数据结构与算法课程设计易 - 青蛙跳台阶

从前有一只青蛙想跳台阶去等峰&#xff0c;若该青蛙一次可以跳上1级台阶、也可以跳上2级、还可以跳3级。那么改青蛙从第0级台阶出发&#xff0c;在跳上第n级台阶且在第m级台阶停留过时有多少种跳法。 输入描述&#xff1a; 第一行两个正整数&#xff0c;n和m m<n 输出描述&a…

kubernetes镜像下载页,离线安装k8s的资源

kubernetes-apt-pool安装包下载_开源镜像站-阿里云 (aliyun.com) 【Kubernetes】Kubernetes各大版本的最新版本下载地址_kubet软件下载-CSDN博客

单位职员尤其女性,若你有文才那将前途无量!

单位职员尤其女性&#xff0c;若你有文才那将前途无量&#xff01; 公司职员尤其女性&#xff0c;若文才出众&#xff0c;恭喜你&#xff1a;提拔重用你是早晚的事&#xff01;不信看我给你分析-- 再说机关、企事业单位的职员&#xff0c;尤其是体制内职工&#xff0c;你若会写…

C# List

C# List 创建 List:添加元素:使用 AddRange 方法添加多个元素&#xff1a;插入元素:访问元素:移除元素:使用 Remove 方法移除一个元素&#xff1a;使用 RemoveAt 方法移除指定索引的元素&#xff1a;使用 RemoveAll 方法移除满足条件的所有元素&#xff1a; 查找元素:使用 Cont…

Goby 漏洞发布|万户ezEIP企业管理系统 /member/success.aspx 命令执行漏洞

漏洞名称&#xff1a;万户ezEIP企业管理系统 /member/success.aspx 命令执行漏洞 English Name&#xff1a;Wanhu-ez-EIP /member/success.aspx Command Execution Vulnerability CVSS core: 9.0 影响资产数&#xff1a;6175 漏洞描述&#xff1a; 万户ezEIP是一种企业资源…

在CentOS7下构建TeamSpeak服务器并增加网易云点歌插件

文章目录 部署TeamSpeak创建一个新用户下载并解压服务端下载解压 启动服务端同意许可协议启动与配置开放端口设置开机自启 客户端连接 部署TS3AudioBot并添加网易云插件安装ffmpeg下载TS3AudioBot本体与插件并解压配置TS3AudioBot启动设置开机自启 部署网易云API安装git安装Nod…

解读vue3源码-2

提示&#xff1a;看到我 请让滚去学习 vue3编译模版的提升 文章目录 vue3编译模版的提升静态节点提升补丁标志和block的使用附录&#xff1a; template explorer可以将我们的源模版转化成渲染函数代码&#xff0c;vue2中就有&#xff0c;而Vue3 template explorer 功能更加丰富…

外汇天眼:ESMA发布针对在投资服务中使用人工智能的公司的指导意见

欧洲证券和市场管理局&#xff08;ESMA&#xff09;&#xff0c;欧盟的金融市场监管机构和监督机构&#xff0c;发布了一份声明&#xff0c;为在向零售客户提供投资服务时使用人工智能技术&#xff08;AI&#xff09;的公司提供初步指导。 尽管人工智能的普及仍处于初期阶段&am…

请描述Vue常用的修饰符

在 Vue 中&#xff0c;修饰符&#xff08;Modifiers&#xff09;常用于自定义指令&#xff08;Directives&#xff09;和事件监听&#xff08;Event Listeners&#xff09;中&#xff0c;以改变指令或事件监听器的默认行为。以下是一些 Vue 中常用的修饰符&#xff1a; 1. 事件…

你认识nginx吗,nginx是做什么的,nginx可以做什么 --2)nginx配置

hello大家今天教大家如何用nginx实验tomcat的负载均衡&#xff0c;同理其他的也可以&#xff0c;如httpd等 首先需要准备一个nginx和tomcat包&#xff0c;这里用到的是版本号为 然后需要准备最少三台linux虚拟机&#xff0c;然后我们开始吧 1.安装tomcat 解包 tar zxf /mnt/…

学习 SSH Key 生成方法

SSH Key 是用于身份验证的一对密钥&#xff0c;包括公钥和私钥。公钥可以放在需要访问的服务器上&#xff0c;私钥则保留在本地。当你使用SSH连接到支持SSH Key认证的服务器时&#xff0c;服务器会用公钥来加密一个随机生成的字符串发送给客户端&#xff0c;客户端用私钥解密并…