长安链介绍-02
再来说说一些长安链设计上比较中性的地方。这些地方可能不见得好,也不见得坏,可能只是在有多种类似选择的时候,选择了其中一种;也可能是一种新特性,但相比其他产品,并没有太多的进步。
Fabric 中的版本号是设计成BlockNumber + Tx Offset in Block的形式,比如,某个Key当前版本号是(3, 2),表示第3个块中的第2条交易最后写入了这个Key。换句话说,Fabric 的版本号是精确到 Tx 的,同一个 Tx 内发生的写入,版本号是一样的。
而长安链的版本号设计成TxID + Key Offset in WriteSet,有些不可思议,这意味着,同一个 Tx 写入的不同 Key 会有不同的版本号,这与常识的认知不符。因为对单一的 Tx,技术上应该是要满足事务属性,即满足“同时成功,或同时失败”,对于有这样特性的系统(如关系数据库和一些NoSQL数据库),我们通常会把同一个 Tx 内的写入标记为相同的版本。长安链做这样的选择明显是不必要的,可以说是一个设计失误。如果想版本号和 TxID 相关,直接设计成“TxID”即可。
我查了一下代码,发现版本号 KeyVersion 这个类在代码中并没有应用,这个“坏设计”暂时没有启用,应该也没有造成什么伤害。
退一步说,长安链即使是设计成只有“TxID”方式,对比 Fabric 的“BlockNumber + Tx Offset in Block”,也不见得就一定有优势。Fabric 这样的设计可以明显看出两个版本号哪一个更加新一点,因为版本号是单调递增的整数;长安链就不行,TxID是随机生成的,无法用来判断版本新旧。这个特性在追溯某个 Key 的变更历史的时候会很方便。
本来,这个特性会被我归类到下一个章节,至少从目前的分析上来看,这个设计毫无可取之处。但没有这样做的原因是,这个版本号设计并没有在代码中应用,万一,我是说万一,也许后面会改进呢?也许会有更好的我们没有想到的特性呢?
首先要说,长安链的 DAG 和通常理解的区块链里面的 DAG(有向无环图)“不一样”。
通常理解的区块链领域的 DAG 应该是这样,代表项目有 IOTA,ByteBall。DAG 一般表示的是一种区块的组织方式,从数据结构的角度来看,区块链是将区块以链表的形式连接起来的,而 DAG 是将区块以有向无环图的形式连接起来的。这些都是在几年前就提出的概念,不再赘述。
而长安链中的 DAG 则完全不同,它是区块中的一个部分,其作用是整理当前区块中每个交易之间的关系,看是否有前一个交易和后一个交易有依赖关系的情况。当交易之间有依赖关系的时候,调度合约的时候需要按照依赖关系排序执行;如果交易之间彼此没有依赖关系,那么无所谓先后,大家都可以并行执行,可以带来效率的提升。
目前看起来,长安链的这个创新点还是很不错的,应该是原创(不排除有其他项目已经先实现了,只是我不知道),不是上面 Policy 机制、修改链配置那种小幅度的创新。但是,我还是没有把这个特性排到上一章节,因为,不管从实际的运行效果来看,还是设计的对比来看,这个改进对比 Fabric 的 Simulation+Validation 机制,实在很难说更好。
长安链的智能合约运行支持Gas,代码中出现 Gas 的地方很多,比如:
// -- tx_sim_context.go
func (s *txSimContextImpl) CallContract(contractId *commonpb.ContractId, method string, byteCode []byte,
parameter map[string]string, gasUsed uint64, refTxType commonpb.TxType) (*commonpb.ContractResult, commonpb.TxStatusCode) {
......
r, code := s.vmManager.RunContract(contractId, method, byteCode, parameter, s, s.gasUsed, refTxType)
// -- instance.go
func CreateInstance(contextId int64, code exec.Code, method string, contractId *commonPb.ContractId,
gasUsed uint64, gasLimit int64) (*wxvmInstance, error) {
不得不说,这是一个不必要的设计。
Gas 最早应该是出现在以太坊项目中,彼时应该还没有联盟链的概念,所有的区块链都是公有链。以太坊提出了智能合约概念,这个在当时是先进的,但是并没有机制保证所有的智能合约都是“善意的”。例如,如果有一个智能合约,故意写了一个死循环,那么执行这个合约的节点就直接服务宕机了。为了防止这样的情况出现,以太坊引入了 Gas 机制,智能合约中每执行一次操作,都要消耗一定量的 Gas,执行智能合约的时候还需要传入参数 GasLimit,表示此次执行合约所需的 Gas 的上限,如果当使用的 Gas 超出了这个上限,就停止合约执行,将此交易标记为无效。
可见,这个机制是应对公有链网络中的不确定性,而想出来的办法。那在联盟链产品中,这样的特性还是必要的吗?我认为完全不必要。首先,联盟链中的节点相对较少,网络较为封闭,不会对公网开放访问(否则就变成公有链了),在节点受限的情况下,智能合约中有害代码的概率本身就大幅度降低,虽然不可能降低到0。其次,在节点受限的场景下,有很多其他的方案可以作为替代进行合约代码控制,甚至可以加入人为干预的流程,例如,人工智能合约代码审查(不要笑,这个方案现实中很管用);复杂一些的,可以参考 Fabric 的 Endorse 机制和 Chaincode 生命周期管理机制。除此之外,智能合约的引擎中不需要处理 Gas 相关的逻辑,对系统也是一种简化。
我相信,长安链的设计者应该也是认为 Gas 是一个不必要的设计,因为在代码中,所有的 GasLimit 都设置为了一个很大的常数值,
// -- vm_interface.go
const (
GasLimit = 1e10 // invoke user contract max gas
TimeLimit = 1 * 1e9 // 1s
说明,长安链的设计者也认为,不需要使用 Gas 机制来进行控制。Gas 目前还存在在代码里面的原因可能是,智能合约的虚拟机(VM)代码里面本身需要使用 Gas,而VM的代码可能是从已有的公有链代码移植过来的,为了适配旧的VM代码 Gas 机制被留存了。也有另一种可能,将来长安链是不是会有公有链化的可能?这样 Gas 机制就又能用了。
总之,站在联盟链的角度上,Gas 机制无疑是一个坏设计,这是项目中的一个技术债,有可能将来会被解决。项目 v1.0.0 版本就有技术债,感觉不太好。
作为联盟链的一个重要特性——“准入”,长安链在这一点上做的还有很多不足。因为联盟链的非公有属性,导致其必须提供拒绝非法节点接入的特性,而长安链很多地方忽略了这一点。
比如,区块同步的请求的时候,最初是在这里注册(register)同步请求处理方法,代码如下,
// -- blockchain_sync_server.go
if err := sync.net.ReceiveMsg(netPb.NetMsg_SYNC_BLOCK_MSG, sync.blockSyncMsgHandler); err != nil {
return err
}
sync.net.ReceiveMsg 方法实际是完成一个注册功能,当收到 NetMsg_SYNC_BLOCK_MSG 请求的时候,调用 sync.blockSyncMsgHandler 来处理,而 sync.blockSyncMsgHandler 的代码中,
// -- blockchain_sync_server.go
func (sync *BlockChainSyncServer) blockSyncMsgHandler(from string, msg []byte, msgType netPb.NetMsg_MsgType) error {
if atomic.LoadInt32(&sync.start) != 1 {
return commonErrors.ErrSyncServiceHasStoped
}
if msgType != netPb.NetMsg_SYNC_BLOCK_MSG {
return nil
}
var (
err error
syncMsg = syncPb.SyncMsg{}
)
if err = proto.Unmarshal(msg, &syncMsg); err != nil {
sync.log.Errorf("fail to proto.Unmarshal the syncPb.SyncMsg:%s", err.Error())
return err
}
sync.log.Debugf("receive the NetMsg_SYNC_BLOCK_MSG:the Type is %d", syncMsg.Type)
switch syncMsg.Type {
case syncPb.SyncMsg_NODE_STATUS_REQ:
return sync.handleNodeStatusReq(from)
......
进入函数之后就进行一些断言判定,然后反序列化,最后就去执行逻辑功能了。在该接口注册的时候,sync.net.ReceiveMsg 也没有包装一层校验逻辑,来判断请求者的身份是否有“准入”的资格。换言之,长安链在区块同步的时候,没有设置节点接入的门槛,节点上的数据可以被一个模拟的节点,全部同步到链之外的地方。这根本是公有链的性质,而非联盟链。
即使退一步来说,将长安链定位为公有链,那么其共识机制只提供了raft、bft类的机制,也是无法满足公有链的要求。所以,目前长安链实际上处于联盟链和公有链之间的状态,无论作为公有链和联盟链来看,都有较多的不足。
这个问题不止在区块同步的时候有,其他请求也有。当然,这个问题也不是那么难解决,在接入的地方加入身份证书验证即可,这本来就应该是长安链已经提供的 Policy 机制的一部分。
下面几个问题都是和 Policy 机制相关的,先来看看第一个问题,交易的签名到底是一个列表还是单一的对象。先看代码,交易类型的定义是这样,
// -- transaction.pb.go
// a transaction includes request and its result
type Transaction struct {
// header of the transaction
Header *TxHeader `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"`
// payload of the request
RequestPayload []byte `protobuf:"bytes,2,opt,name=request_payload,json=requestPayload,proto3" json:"request_payload,omitempty"`
// signature of request bytes(including header and payload)
RequestSignature []byte `protobuf:"bytes,3,opt,name=request_signature,json=requestSignature,proto3" json:"request_signature,omitempty"`
// result of the transaction, can be marshalled according to tx_type in header
Result *Result `protobuf:"bytes,4,opt,name=result,proto3" json:"result,omitempty"`
}
protobuf 生成的代码中看不出来什么,只知道签名 RequestSignature 是一个 byte 数组。再来看使用这个签名的地方,
// -- transaction.go
// verify transaction sender's authentication (include signature verification, cert-chain verification, access verification)
func verifyTxAuth(t *commonPb.Transaction, ac protocol.AccessControlProvider) error {
var err error
txBytes, err := CalcUnsignedTxBytes(t)
if err != nil {
return err
}
endorsements := []*commonPb.EndorsementEntry{{
Signer: t.Header.Sender,
Signature: t.RequestSignature,
}}
resourceId, err := ac.LookUpResourceNameByTxType(t.Header.TxType)
if err != nil {
return err
}
principal, err := ac.CreatePrincipal(resourceId, endorsements, txBytes)
if err != nil {
return fmt.Errorf("fail to construct authentication principal: %s", err)
}
ok, err := ac.VerifyPrincipal(principal)
if err != nil {
return fmt.Errorf("authentication error, %s", err)
}
if !ok {
return fmt.Errorf("authentication failed")
}
return nil
}
在节点验证签名的时候,签名验证的主入口函数是 verifyTxAuth,这里做了一个非常奇怪的转换,把签名 RequestSignature 转换为只有一个元素的 EndorsementEntry 列表,然后再进行构造身份,身份验证(身份验证用的是之前提到的 Policy 机制)等逻辑处理。
这里我很谨慎的做一个判断:长安链的交易签名只有1个,之前提到的 Policy 机制在这种情况下,几乎无法使用,可能只有 SELF、ANY 能勉强用一下。我做出这个判断的时候我自己也吓了一跳,毕竟长安链引入 Policy 的机制其实也挺麻烦的,但引入之后却没有去用这个机制,这于情于理都无法解释。但在我仔细查找了代码之后,我还是做出了这个判断。
实际上,Policy 机制的实现代码还是很完善的,对不同的 ALL、MAJORITY、ANY、阈值、分数等规则都有处理,但是调用的地方只有一个签名。这说明,长安链在规划中,是有计划将 Policy 机制应用好的,但是在客户端提交交易前构建签名列表的时候,暂时还没有加入多签名的机制。这直接导致了 Policy 机制的残缺,因此只能将这个问题归类到坏设计里面。
还是签名的问题,再看一下交易的定义,
// -- transaction.pb.go
// a transaction includes request and its result
type Transaction struct {
// header of the transaction
Header *TxHeader `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"`
// payload of the request
RequestPayload []byte `protobuf:"bytes,2,opt,name=request_payload,json=requestPayload,proto3" json:"request_payload,omitempty"`
// signature of request bytes(including header and payload)
RequestSignature []byte `protobuf:"bytes,3,opt,name=request_signature,json=requestSignature,proto3" json:"request_signature,omitempty"`
// result of the transaction, can be marshalled according to tx_type in header
Result *Result `protobuf:"bytes,4,opt,name=result,proto3" json:"result,omitempty"`
}
注释说的很清楚,签名 RequestSignature 这个字段存的是对请求的签名(包括 Header和RequestPayload)。为什么是对请求的签名,而不是对交易结果的签名?
Fabric 中的 Policy 机制是对交易结果(而非交易请求)进行签名,在 Fabric 中,交易结果的主要数据结构是读写集(RwSet),这个结果是由不同节点的智能合约执行得到的共同结果,节点通过对结果签名,表示对此结果的背书(Endorsement)。因此,同一条交易才会有多个签名,也因此,才会需要有背书的 Policy 机制来进行验证。对比一下,
- 长安链对交易
请求数据进行签名;Fabric 对交易结果数据进行签名;- 长安链由
交易发起方来签名;Fabric 由交易执行方来签名;- 长安链的签名只有
1个;Fabric 的签名可以多个;
正因为长安链设计成对交易请求进行签名,所以只能由请求方来签名;正是因为由请求方来签名,而请求方通常只有一方,所以才导致了签名只有1个。通常业务场景中,请求方多数是一方的时候居多。例如,写入订单的场景,发起者就是下单的人,这个操作的请求方只有1个;也有一些需要两方请求的场景,如转账(即使是这个场景也是一方请求,很少双方共同请求);需要三方或以上请求的业务场景就非常罕见了。
这样分析的话,似乎就找到了交易中只有一个签名的原因:长安链在设计 Policy 机制的时候,选择了对交易请求进行签名。也因此导致了其 Policy 机制残缺的现状。
当然,我并不是说 Policy 机制只有像 Fabric 中这样用才是对的,其他用法只要逻辑自洽自然也完全可以,但目前长安链的用法实在很难自圆其说。
综上,长安链应该只是把 Fabric 的 Policy 机制硬套在了其技术架构上面,签名既签错了数据,也由错误的成员来签名,导致了 Policy 机制在长安链里几乎没发挥什么作用。
这个问题要说清楚会比较长,先跳过。
最后说一个密码相关的问题吧,还是交易签名。先对比下 Fabric 有关签名的代码,
// -- transaction.pb.go
type SignatureHeader struct {
// Creator of the message, a marshaled msp.SerializedIdentity
Creator []byte `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty"`
// Arbitrary number that may only be used once. Can be used to detect replay attacks.
Nonce []byte `protobuf:"bytes,2,opt,name=nonce,proto3" json:"nonce,omitempty"`
// -- txutils.go
paylBytes := MarshalOrPanic(
&common.Payload{
Header: MakePayloadHeader(payloadChannelHeader, payloadSignatureHeader),
Data: data,
},
)
var sig []byte
if signer != nil {
sig, err = signer.Sign(paylBytes)
if err != nil {
return nil, err
}
}
最后的 sig, err = signer.Sign(paylBytes) 这一行会计算出签名 sig,签名的数据 paylBytes 包括3个部分,ChannelHeader、SignatureHeader和Data。Data是数据,无需多说;ChannelHeader包括一些链的基本信息,链名、消息版本、时间戳等等,非重点;SignatureHeader包含2个信息,证书和nonce。
再看一下长安链的实现代码,这里需要再次请出交易结构的代码,
// -- transaction.pb.go
// a transaction includes request and its result
type Transaction struct {
// header of the transaction
Header *TxHeader `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"`
// payload of the request
RequestPayload []byte `protobuf:"bytes,2,opt,name=request_payload,json=requestPayload,proto3" json:"request_payload,omitempty"`
// signature of request bytes(including header and payload)
RequestSignature []byte `protobuf:"bytes,3,opt,name=request_signature,json=requestSignature,proto3" json:"request_signature,omitempty"`
// result of the transaction, can be marshalled according to tx_type in header
Result *Result `protobuf:"bytes,4,opt,name=result,proto3" json:"result,omitempty"`
}
// -- request.pb.go
// header of the request
type TxHeader struct {
// blockchain identifier
ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"`
// sender identifier
Sender *accesscontrol.SerializedMember `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"`
// transaction type
TxType TxType `protobuf:"varint,3,opt,name=tx_type,json=txType,proto3,enum=common.TxType" json:"tx_type,omitempty"`
// transaction id set by sender, should be unique
TxId string `protobuf:"bytes,4,opt,name=tx_id,json=txId,proto3" json:"tx_id,omitempty"`
// transaction timestamp, in unix timestamp format, seconds
Timestamp int64 `protobuf:"varint,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
// expiration timestamp in unix timestamp format
// after that the transaction is invalid if it is not included in block yet
ExpirationTime int64 `protobuf:"varint,6,opt,name=expiration_time,json=expirationTime,proto3" json:"expiration_time,omitempty"`
}
// -- member.pb.go
// Serialized member of blockchain
type SerializedMember struct {
// organization identifier of the member
OrgId string `protobuf:"bytes,1,opt,name=org_id,json=orgId,proto3" json:"org_id,omitempty"`
// member identity related info bytes
MemberInfo []byte `protobuf:"bytes,2,opt,name=member_info,json=memberInfo,proto3" json:"member_info,omitempty"`
// use cert compression
// todo: is_full_cert -> compressed
IsFullCert bool `protobuf:"varint,3,opt,name=is_full_cert,json=isFullCert,proto3" json:"is_full_cert,omitempty"`
}
如上所说,长安链的交易签名 RequestSignature 这个字段存的是请求的签名(包括 Header和RequestPayload)。RequestPayload是数据,无需多言;Header中包括链名、签名证书、交易ID、时间戳等信息。签名证书的结构是 SerializedMember,使用了证书压缩机制,前面提到过。
大致上可以说,长安链中交易包含的信息和 Fabric 是差不多的,唯一的显著区别是,Fabric 中有nonce,而长安链中没有。nonce是密码学中一次数,每次需要用nonce的时候会随机生成一个,由随机算法保证每次生成的数足够随机,以至于不会碰到2个相同的nonce。
为什么长安链中没有nonce? 这个设计有些不太合理。
?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------
//1.验证返回状态码是否是200pm.test("Statuscodeis200",function(){pm.response.to.have.status(200);});//2.验证返回body内是否含有某个值pm.test("Bodymatchesstring",function(){pm.expect(pm.response.text()).to.include("string_you_want_to_search");});//3.验证某个返回值是否是100pm.test("Yourtestname",function(){varjsonData=pm.response.json
1.在Python3中,下列关于数学运算结果正确的是:(B)a=10b=3print(a//b)print(a%b)print(a/b)A.3,3,3.3333...B.3,1,3.3333...C.3.3333...,3.3333...,3D.3.3333...,1,3.3333...解析: 在Python中,//表示地板除(向下取整),%表示取余,/表示除(Python2向下取整返回3)2.如下程序Python2会打印多少个数:(D)k=1000whilek>1: print(k)k=k/2A.1000 B.10C.11D.9解析: 按照题意每次循环K/2,直到K值小于等
目录H2数据库入门以及实际开发时的使用1.H2数据库的初识1.1H2数据库介绍1.2为什么要使用嵌入式数据库?1.3嵌入式数据库对比1.3.1性能对比1.4技术选型思考2.H2数据库实战2.1H2数据库下载搭建以及部署2.1.1H2数据库的下载2.1.2数据库启动2.1.2.1windows系统可以在bin目录下执行h2.bat2.1.2.2同理可以通过cmd直接使用命令进行启动:2.1.2.3启动后控制台页面:2.1.3spring整合H2数据库2.1.3.1引入依赖文件2.1.4数据库通过file模式实际保存数据的位置2.2H2数据库操作2.2.1Mysql兼容模式2.2.2Mysql模式
为什么需要服务网关传统的单体架构中只需要开放一个服务给客户端调用,但是微服务架构中是将一个系统拆分成多个微服务,如果没有网关,客户端只能在本地记录每个微服务的调用地址,当需要调用的微服务数量很多时,它需要了解每个服务的接口,这个工作量很大。有了网关之后,网关作为系统的唯一流量入口,封装内部系统的架构,所有请求都先经过网关,由网关将请求路由到合适的微服务。使用网关的好处1)简化客户端的工作。网关将微服务封装起来后,客户端只需同网关交互,而不必调用各个不同服务;(2)降低函数间的耦合度。一旦服务接口修改,只需修改网关的路由策略,不必修改每个调用该函数的客户端,从而减少了程序间的耦合性(3)解放开发
我发现python的细节自动完成很好RubyonRails有类似的方法描述吗? 最佳答案 有篇不错的文章"UsingVIMasacompleteRubyonRailsIDE"其中引用rails.vim.这似乎是RailsforVIM的实际标准。(不过,我还没有使用过它,但很快就会尝试。)这允许你做很多与Rails相关的任务,但对自动完成没有帮助。还有一篇"RubyAutocompleteinVim"(遗憾的是不再可用)这就是您要搜索的内容。我不知道,理解Rails的所有插件魔法和元编程的东西是否足够聪明。它至少在vim的配置中提到了
前阵InfoQ社区看到腾讯云腾讯云区块链服务平台(TBaaS)长安链体验活动,一顿操作猛如虎报了个名,体验完用一个字概括:强。非要再加几个字的话,总体感受下来装配模式灵活高效,配套工具完整辩解。话不多说开始主题本文目录结构分为区块链分类和TBaaS平台介绍、TBaaS平台上链教程三个部分一、区块链分类:大体上来说,区块链可分为公链,联盟链,私有链三种:公有链(PublicBlockchain)公有链是指任何人都能参与的区块链。公有链是去中心化程度最高的区块链,不受机构控制,整个账本对所有人公开透明。任何人都能在公有链上查询交易、发送交易、参与记账。加入公有链不需要任何人授权,可以自由加入或者离
防火墙防火墙分类第一代防火墙:包过滤防火墙包过滤防火墙的缺点第二代防火墙:代理防火墙第三代防火墙:状态防火墙第四代防火墙:UTM防火墙第五代防火墙:下一代防火墙华为防火墙介绍安全策略防火墙的会话表防火墙分类第一代防火墙:包过滤防火墙属于第一代防火墙技术,在没有专用防火墙设备时,一般由路由器实现该功能。将网络上传送数据包的IP首部以及TCP/UDP首部,获取发送源的IP地址和端口号,以及目的地的IP地址和端口号,并将这些信息作为过滤条件,决定是否将该分组转发至目的地网络分组过滤的执行需要设置访问控制列表。访问控制列表也可以称为安全策略(简称策略)或安全规则(简称规则)。类似于进站检票的做法,符合
内容来自Qt样式表之QSS语法介绍-3YL的博客Qt样式表是一个可以自定义部件外观的十分强大的机制,可以用来美化部件。Qt样式表的概念、术语和语法都受到了HTML的层叠样式表(CascadingStyleSheets, CSS教程)的启发,不过与CSS不同的是,Qt样式表应用于部件的世界。类型选择器QPushButton匹配QPushButton及其子类的实例ID选择器QPushButton#okButton匹配所有objectName为okButton的QPushButton实例。 CSS常用样式1CSS文字属性注:px:相对长度单位,像素(Pixel)。pt:绝对长度单位,点(Point
简介:我们都知道在Android开发中,当我们的程序在与用户交互时,用户会得到一定的反馈,其中以对话框的形式的反馈还是比较常见的,接下来我们来介绍几种常见的对话框的基本使用。前置准备:(文章最后附有所有代码)我们首先先写一个简单的页面用于测试这几种Dialog(对话框)代码如下,比较简单,就不做解释了一、提示对话框(即最普通的对话框)首先我们给普通对话框的按钮设置一个点击事件,然后通过AlertDialog.Builder来构造一个对象,为什么不直接Dialog一个对象,是因为Dialog是一个基类,我们尽量要使用它的子类来进行实例化对象,在实例化对象的时候,需要将当前的上下文传过去,因为我这