标准库网络密码学并发编程【免费下载链接】cangjie_stdx仓颉编程语言提供了 stdx 模块该模块提供了网络、安全等领域的通用能力。项目地址https://gitcode.com/Cangjie/cangjie_stdx点击查看免费下载本文围绕stdx.aspect_cj包展开讲解如何用InsertAtEntry、InsertAtExit、ReplaceFuncBody三个注解类定义切面并配合libstdx.collect_aspects与libstdx.weave_aspects两个编译插件完成两遍编译织入。读完本文你可以掌握入口/退出插桩与函数体替换的完整写法、全部规格限制以及从切面收集.annoinfo元数据到 CHIR 中间表示改写weaving的底层实现链路。说明根据 aspect_cj 包概述 的标注该包当前处于开发阶段尚不具备完整功能。包定位注解 编译插件的两阶段 AOP 机制stdx.aspect_cj包提供了仓颉语言中面向切面编程Aspect Oriented Programming, AOP的相关注解配合libstdx.collect_aspects和libstdx.weave_aspects两个编译插件使用可以对函数进行前后插桩以及替换实现。与运行时 AOP 框架不同stdx.aspect_cj的织入发生在编译期注解本身只是“声明切面意图”真正修改函数体的工作由两个 CHIR 插件在两次cjc编译中完成。整体流程如下第一次编译加载--pluginlibstdx.collect_aspects.so插件CollectAspects遍历当前包的所有函数检查注解合法性并把切面信息切面函数身份 连接点匹配条件写入当前包名-目标包名.annoinfo元数据文件第二次编译加载--pluginlibstdx.weave_aspects.so插件WeaveAspects扫描目录中的.annoinfo文件把匹配到的切面以 CHIR IR 指令的形式直接织入目标函数体。两个编译插件以动态库的形式在stdx.aspect_cj中提供不同平台提供不同版本.so/.dll/.dylib。从 构建脚本 可以看到aspect_cj仅在 cjnative 后端下、且未设置CANGJIE_BUILD_WITHOUT_CHIR/CANGJIE_BUILD_WITHOUT_PLUGIN/CANGJIE_BUILD_WITHOUT_ASPECT_CJ等开关时才会参与构建见 src/stdx/CMakeLists.txt 第 3442 行的条件分支并且依赖于chir与plugin两个包。两个插件都使用CHIRPlugin宏完成插件注册。从源码结构看该宏定义在 src/stdx/plugin/register.cj 中它把被标注的类改写为继承CHIRPluginBase的子类基类声明在 src/stdx/plugin/manager/base.cj要求子类实现run(pkg: Package): Bool并在类的static init中生成PluginManager.registerCHIRPlugin(...)调用使插件在插件库加载时自动注册。API 列表三个切面注解类stdx.aspect_cj对外暴露三个注解类源码见 src/stdx/aspect_cj/aspect_cj.cj均带有Annotation标记类名功能InsertAtEntry在注解所指定方法的入口织入对被注解标注的函数的调用InsertAtExit在注解所指定方法的退出点织入对被注解标注的函数的调用ReplaceFuncBody将注解所指定方法的方法体替换为对被注解标注的函数的调用各参数在 类 API 文档 中有完整说明此处汇总如下。InsertAtEntry / InsertAtExit 参数public const init(packageName!: String, className!: String, methodName!: String, isStatic!: Bool, recursive!: Bool, funcTypeStr!: String)参数说明packageName被织入的函数的所属包名如default、std.coreclassName被织入的函数是成员函数时为函数所属类名是全局函数时为空methodName被织入的函数名称如fooisStatic被织入的函数是否为静态成员函数recursive被织入的函数是成员函数时表示是否对子类里的 override 版本也做织入否则应填falsefuncTypeStr被织入的函数的类型字符串不含空格。自定义类型须带包名前缀并用.分隔不含隐式this形参类型如(Int64,std.core.Object)-UnitReplaceFuncBody的const init参数与上表前五项相同但没有funcTypeStr因为被替换函数的类型由原函数决定。注解中的packageName会受到严格校验collect_aspects.cj 的isValidPackageName要求包名非空、不含路径分隔符/、\、..且每个段首字符必须为 ASCII 字母或_其余字符为字母、数字、_或-。规格标注范围、全局变量约束与形参约束官方文档明确给出三条规格的约束且这些约束在收集插件中都有对应的编译期强制检查。标注范围注解暂不支持标注在泛型函数上也不支持织入泛型函数注解只允许标注在 public 函数上注解可以标注在全局函数上支持织入另一个全局函数织入另一个实例成员函数织入另一个静态成员函数注解可以标注在静态成员函数上支持织入另一个全局函数织入另一个实例成员函数织入另一个静态成员函数注解可以标注在实例成员函数上仅支持织入同类型的其它实例成员函数。从源码结构看checkAspectFunction 进一步要求被标注函数必须是 public且具有 external linkage、不能是foreign函数、不能是抽象函数checkAnnotationArgs 则保证实例成员函数不能指向全局/静态函数且其packageName/className必须与被标注函数所属类型一致即“织入同类型的其它实例成员函数”这一条className为空时也不允许recursive: true。全局变量定义约束定义了切面的包中只允许定义基本类型整型、浮点、Rune、Bool字面值的全局变量否则编译报错若需要使用超规格的全局变量需要将其定义在另外的包再导入使用以避免在编译后可能产生的循环依赖。该约束由 globalVarsArePrimitiveLiterals 检查包内每个非导入、非编译器添加的全局变量其类型必须是基本类型且初始化器必须是字面值否则报错Global variables in package ... must be initialized with primitive literal values when AOP annotations are used.形参约束被标注InsertAtEntry/InsertAtExit的函数其返回类型只能是 Unit被标注ReplaceFuncBody的函数其返回类型应和被织入的函数的返回类型相同被标注三类注解的函数如果没有形参那么其织入后总是被无参调用被标注三类注解的函数如果有形参其形参列表应和被织入函数的源码形参列表相同此外特别地如果被织入的函数是实例成员函数那么需要将this参数显式地写在形参中对于被标注ReplaceFuncBody的函数需要额外添加一个形参形参的类型和被织入的函数相同该形参表示被织入函数原始版本的闭包。这些规则在 checkInsertAnnoFuncType 与 checkReplaceBodyAnnoFuncType 中逐条实现前者比较切面函数与funcTypeStr声明的参数类型前缀this形参会被剔除后再比较后者要求ReplaceFuncBody函数至少有一个形参、最后一个形参必须是函数类型即原始函数闭包且闭包返回类型与被标注函数返回类型一致成员函数场景下首个形参还必须是packageName.className类型。另外同一函数不允许同时使用InsertAtEntry/InsertAtExit与ReplaceFuncBody见 checkAndCollect 中的报错分支。两遍编译收集切面与织入切面要实现 AOP 的完整功能除了使用上述注解类来定义切面还需要两个编译插件libstdx.collect_aspects.so.dll/.dyliblibstdx.weave_aspects.so.dll/.dylib应先使用libstdx.collect_aspects在编译时收集所有切面、连接点信息再使用libstdx.weave_aspects进行二次编译把之前收集的切面织入到连接点。Linux 平台的标准命令形如cjc aop_demo.cj -L $CANGJIE_STDX_PATH --import-path $CANGJIE_STDX_PATH -lstdx.aspect_cj --plugin$CANGJIE_STDX_PATH/libstdx.collect_aspects.so -o main # 第一次编译收集切面 cjc aop_demo.cj -L $CANGJIE_STDX_PATH --import-path $CANGJIE_STDX_PATH -lstdx.aspect_cj --plugin$CANGJIE_STDX_PATH/libstdx.weave_aspects.so -o main # 第二次编译织入切面其中CANGJIE_STDX_PATH为扩展库的安装路径可参考 stdx 库总览 中的环境变量设置示例-lstdx.aspect_cj用于链接注解所在的包。收集阶段产出的 .annoinfo 元数据从 writeAnnoInfoToFile 可以确认收集插件按“当前包名-目标包名”的规则常量PACKAGE_NAME_CONNECTOR -、ANNO_INFO_FILE_SUFFIX .annoinfo定义于 aspect_cj.cj生成元数据文件文件中逐行记录切面定义所在包名与其包初始化函数的 mangled name用于后续强制触发切面包的初始化注解类名InsertAtEntry/InsertAtExit/ReplaceFuncBodyInsert信息切面函数所在包名、mangled name、形参数量、是否成员函数、是否静态To信息连接点匹配条件目标包名、类名、方法名、函数类型字符串、是否静态、是否递归匹配子类 override。文件命名带“目标包名”后缀的设计使得织入阶段可以按包精确挑选相关元数据readAnnoInfo 会遍历当前目录只读取形如*-{当前包名}.annoinfo的文件。实战示例以下三个完整示例继承自 AOP 开发示例。InsertAtEntry 入口插桩示例在指定函数入口织入调用package AOP_demo1 import stdx.aspect_cj.* import std.time.DateTime InsertAtEntry[packageName: AOP_demo1, className: , methodName: printCurrentTime, isStatic: false, recursive: false, funcTypeStr: ()-Unit] public func printCurrentTimeImpl() { println(----- ${DateTime.now()} -----) } public func printCurrentTime() { println(hi) println(bye) } main() { printCurrentTime() 0 }linux 平台编译命令cjc aop_demo1.cj -L $CANGJIE_STDX_PATH --import-path $CANGJIE_STDX_PATH -lstdx.aspect_cj --plugin$CANGJIE_STDX_PATH/libstdx.collect_aspects.so -o main # 第一次编译收集切面 cjc aop_demo1.cj -L $CANGJIE_STDX_PATH --import-path $CANGJIE_STDX_PATH -lstdx.aspect_cj --plugin$CANGJIE_STDX_PATH/libstdx.weave_aspects.so -o main # 第二次编译织入切面运行结果可能如下----- 2025-06-03T00:00:18.994507-07:00 ----- hi byeclassName为空表示目标printCurrentTime是全局函数funcTypeStr: ()-Unit表示无参、返回Unit。切面函数printCurrentTimeImpl自身无参因此织入后总是被无参调用且返回类型满足 Unit 约束。InsertAtExit 退出插桩示例在指定函数退出前织入调用package AOP_demo2 import stdx.aspect_cj.* import std.time.DateTime InsertAtExit[packageName: AOP_demo2, className: , methodName: printCurrentTime, isStatic: false, recursive: false, funcTypeStr: ()-std.core.String] public func printCurrentTimeImpl() { println(----- ${DateTime.now()} -----) } public func printCurrentTime() { println(hi) println(bye) done } main() { println(printCurrentTime()) 0 }linux 平台编译命令cjc aop_demo2.cj -L $CANGJIE_STDX_PATH --import-path $CANGJIE_STDX_PATH -lstdx.aspect_cj --plugin$CANGJIE_STDX_PATH/libstdx.collect_aspects.so -o main # 第一次编译收集切面 cjc aop_demo2.cj -L $CANGJIE_STDX_PATH --import-path $CANGJIE_STDX_PATH -lstdx.aspect_cj --plugin$CANGJIE_STDX_PATH/libstdx.weave_aspects.so -o main # 第二次编译织入切面运行结果可能如下hi bye ----- 2025-06-03T00:04:59.996469-07:00 ----- done注意这里funcTypeStr声明为()-std.core.String与printCurrentTime的真实返回类型一致——这也是recursive/funcTypeStr参与连接点精确匹配的原因见下文meets一节。ReplaceFuncBody 替换函数体示例将指定函数的函数体整体替换并保留原始实现闭包package AOP_demo3 import stdx.aspect_cj.* import std.time.DateTime ReplaceFuncBody[packageName: AOP_demo3, className: , methodName: printCurrentTime, isStatic: false, recursive: false] public func printCurrentTimeImpl(original: (String) - String) { println(----- ${DateTime.now()} -----) println(original(abc)) println(----- end -----) def } public func printCurrentTime(x: String): String { println(x) 456 } main() { println(printCurrentTime(123)) 0 }linux 平台编译命令cjc aop_demo3.cj -L $CANGJIE_STDX_PATH --import-path $CANGJIE_STDX_PATH -lstdx.aspect_cj --plugin$CANGJIE_STDX_PATH/libstdx.collect_aspects.so -o main # 第一次编译收集切面 cjc aop_demo3.cj -L $CANGJIE_STDX_PATH --import-path $CANGJIE_STDX_PATH -lstdx.aspect_cj --plugin$CANGJIE_STDX_PATH/libstdx.weave_aspects.so -o main # 第二次编译织入切面运行结果可能如下----- 2025-06-03T00:04:59.996469-07:00 ----- abc 456 ----- end ----- defprintCurrentTimeImpl的最后一个形参original: (String) - String就是被替换函数原始版本的闭包类型与printCurrentTime完全一致。运行时original(abc)打印出原函数体对入参的处理结果abc并返回原函数体的返回值456最终整个函数返回切面新返回值def——这正是“函数体替换 原始实现可调用”的典型 AOP 拦截模式如装饰、代理、日志包装。源码级织入实现解析连接点匹配meets织入插件并不会盲目替换所有同名函数。meets 逐条核对目标包名一致、外层类型名一致见下、方法源码名一致、静态属性一致并把函数的显式形参类型剔除隐式this与返回类型拼成qualifiedName字符串与注解中的funcTypeStr严格相等才命中。outerTypeNameMeets 中当recursive为true时还会递归遍历子类类型的父类链getSuperTypesRecusively从而实现对子类 override 版本的织入——这解释了recursive参数的实际语义。三种注解的 IR 改写策略weaveFuncBody 根据注解类名选择不同策略全部基于stdx.chir提供的CHIRBuilder/CHIRVisitor对 CHIR 中间表示进行改写InsertAtEntry在目标函数体的入口基本块entryBlock起始位置createApply切面函数调用InsertAtExit由 InsertAtExitVisitor 遍历函数体 IR在每个exit指令之前插入调用跳过 lambda 内部因此函数无论走哪条返回路径都会先执行切面ReplaceFuncBody新建一个与原函数同签名的 wrapper 函数替换原函数wrapper 体中先allocate返回值槽位再把原函数f本身作为最后一个实参prepareArguments 在替换场景下会把函数f追加进实参列表传给切面最后store结果并exit原函数的 identifier 被重命名为原标识符 .original保留在包中即示例里original闭包的来源。参数透传与切面包初始化prepareArguments会把目标函数的形参按序作为实参透传给切面参数个数必须与收集阶段记录的paramsNum一致替换场景减一否则报错Parameter count mismatch。这就是“形参列表应和被织入函数的源码形参列表相同”在运行语义上的体现——切面可以读到目标函数的全部入参。另外callUpstreamPkgInitFunc 会在当前包的初始化函数开头插入对切面所在包初始化函数的调用复用已有声明避免重复 CHIR 标识符保证即使目标包并未显式import切面包切面包的静态初始化如插件注册、全局字面量初始化也会被触发源码注释也说明了包初始化被假设为幂等重复调用是安全的。小结与使用注意stdx.aspect_cj提供InsertAtEntry、InsertAtExit、ReplaceFuncBody三个切面注解能力分别是入口插桩、退出插桩和函数体替换替换时保留原始函数闭包使用方式固定为两遍编译先libstdx.collect_aspects收集切面元数据.annoinfo再libstdx.weave_aspects完成 IR 织入规格约束public、非泛型、实例成员仅同类型、返回类型/形参匹配、切面包全局变量必须为基本类型字面值会在收集阶段以编译期错误强制拦截建议先读懂 overview 中的规格 再编写切面该包目前处于开发阶段且构建上依赖 cjnative 后端与chir/plugin包使用与扩展前请以当前仓库源码src/stdx/aspect_cj中的实现为准。赞分享标准库网络密码学并发编程【免费下载链接】cangjie_stdx仓颉编程语言提供了 stdx 模块该模块提供了网络、安全等领域的通用能力。项目地址https://gitcode.com/Cangjie/cangjie_stdx点击查看免费下载相关推荐stdx.aspect_cj 注解类 API仓颉语言 AOP 函数插桩与替换实战指南stdx.aspect_cj 注解类 API仓颉语言 AOP 函数插桩与替换实战指南 stdx.aspect_cj 包是 cangjie_stdx 中提供面向标准库网络密码学并发编程Hyperf AOP 面向切面编程实战切面定义、方法增强与代理类缓存机制Hyperf AOP 面向切面编程实战切面定义、方法增强与代理类缓存机制 AOPAspect Oriented Programming面向切面编程是 H后端Web框架微服务RPC框架异步编程Hyperf AOP 面向切面编程实战指南切面定义、代理类生成与缓存原理Hyperf AOP 面向切面编程实战指南切面定义、代理类生成与缓存原理 导读 本文基于 Hyperf 框架的 AOPAspect Oriented Pro后端微服务创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?