news 2026/7/1 18:24:21

HarmonyOS7 AOP 能干嘛?无侵入性能监控和日志埋点实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOS7 AOP 能干嘛?无侵入性能监控和日志埋点实战

文章目录

    • 前言
    • ArkTS 里的 AOP:装饰器就是切面
    • 第一个切面:@LogExecution
    • @TrackTime:性能耗时追踪
    • @CatchError:统一异常捕获
    • 全链路追踪:组合装饰器
    • PerfMonitor 数据收集
    • 踩过的坑
    • 小结

前言

你有没有过这种体验:业务代码写得好好的,PM 说"加个性能监控",于是你在每个方法前后都塞上了Date.now()console.info。代码瞬间变得又长又丑,业务逻辑和监控代码搅在一起,改起来特别痛苦。

这就是典型的横切关注点问题——日志、性能监控、错误捕获这些逻辑,跟业务本身没关系,但又散落在到处都是。AOP(面向切面编程)就是专门搞定这类事的。

ArkTS 里的 AOP:装饰器就是切面

在 Java 世界,AOP 靠的是 Spring AOP 或者 AspectJ,底层用动态代理或字节码织入。ArkTS 没这么复杂——装饰器天然就是 AOP 的实现方式

方法装饰器可以在方法执行前后插入逻辑,完全不需要修改原始代码。这比 Java 那套优雅多了。

第一个切面:@LogExecution

先来个最简单的,自动记录方法调用和参数:

// decorators/LogExecution.etsfunctionLogExecution(target:Object,propertyKey:string,descriptor:PropertyDescriptor):PropertyDescriptor{constoriginalMethod=descriptor.value descriptor.value=function(...args:any[]){conststart=Date.now()console.info(`[Log] 调用${propertyKey},参数:${JSON.stringify(args)}`)try{constresult=originalMethod.apply(this,args)constelapsed=Date.now()-startconsole.info(`[Log]${propertyKey}完成,耗时${elapsed}ms`)returnresult}catch(e){console.error(`[Log]${propertyKey}异常:${e}`)throwe}}returndescriptor}

用法特别简单,往方法上一贴就行:

classUserService{@LogExecutionasyncfetchUser(userId:string):Promise<User>{constresponse=awaithttp.request(`/api/users/${userId}`)returnJSON.parse(response.resultasstring)}}

业务代码干干净净,日志逻辑全在装饰器里。想加就加,想摘就摘,零侵入。

@TrackTime:性能耗时追踪

做性能优化的时候,需要知道每个关键方法的实际耗时。手动埋点太累,用装饰器批量搞定:

// decorators/TrackTime.etsimport{PerfMonitor}from'../monitor/PerfMonitor'functionTrackTime(tag?:string){returnfunction(target:Object,propertyKey:string,descriptor:PropertyDescriptor):PropertyDescriptor{constoriginalMethod=descriptor.valueconstlabel=tag??propertyKey descriptor.value=function(...args:any[]){conststart=Date.now()PerfMonitor.getInstance().markStart(label)constfinalize=()=>{constelapsed=Date.now()-start PerfMonitor.getInstance().record(label,elapsed)if(elapsed>100){console.warn(`[Perf] 慢方法告警:${label}耗时${elapsed}ms`)}}try{constresult=originalMethod.apply(this,args)// 处理异步方法if(resultinstanceofPromise){returnresult.then((val)=>{finalize();returnval},(err)=>{finalize();throwerr})}finalize()returnresult}catch(e){finalize()throwe}}returndescriptor}}

这里有个细节——异步方法需要特殊处理。如果直接finalize(),拿到的是同步耗时,没有意义。判断返回值是不是 Promise,是的话就在.then()里再记录。

@CatchError:统一异常捕获

每个方法都写 try-catch 太烦了。装饰器统一管理:

// decorators/CatchError.etsfunctionCatchError(fallbackValue?:any){returnfunction(target:Object,propertyKey:string,descriptor:PropertyDescriptor):PropertyDescriptor{constoriginalMethod=descriptor.value descriptor.value=asyncfunction(...args:any[]){try{returnawaitoriginalMethod.apply(this,args)}catch(e){// 上报到错误监控平台ErrorReporter.getInstance().report({method:propertyKey,error:e,args,timestamp:Date.now()})// 返回降级值,避免页面崩溃if(fallbackValue!==undefined){console.warn(`[CatchError]${propertyKey}异常,返回降级值`)returnfallbackValue}throwe}}returndescriptor}}

有了这个装饰器,网络请求失败、数据解析出错这些场景都能优雅降级,不会直接白屏:

classProductRepository{@CatchError([])asyncgetProductList():Promise<Product[]>{constdata=awaithttp.request('/api/products')returnJSON.parse(data.resultasstring)}@CatchError(null)asyncgetProductDetail(id:string):Promise<Product|null>{constdata=awaithttp.request(`/api/products/${id}`)returnJSON.parse(data.resultasstring)}}

全链路追踪:组合装饰器

单个装饰器好用,但实际项目中经常需要多个切面叠加。来个全链路追踪的完整实现:

// monitor/TraceContext.etsexportclassTraceContext{privatestaticinstance:TraceContextprivatetraces:Map<string,TraceSpan[]>=newMap()privatecurrentTraceId:string=''staticgetInstance():TraceContext{if(!TraceContext.instance){TraceContext.instance=newTraceContext()}returnTraceContext.instance}beginTrace(traceId:string):void{this.currentTraceId=traceIdthis.traces.set(traceId,[])}addSpan(span:TraceSpan):void{constspans=this.traces.get(this.currentTraceId)if(spans){spans.push(span)}}endTrace():TraceReport{constspans=this.traces.get(this.currentTraceId)??[]consttotal=spans.reduce((sum,s)=>sum+s.duration,0)return{traceId:this.currentTraceId,spans,totalDuration:total,spanCount:spans.length}}}exportinterfaceTraceSpan{name:stringstartTime:numberduration:numberstatus:'success'|'error'}exportinterfaceTraceReport{traceId:stringspans:TraceSpan[]totalDuration:numberspanCount:number}

然后是串联多个装饰器的组合装饰器:

// decorators/Traced.etsfunctionTraced(spanName?:string){returnfunction(target:Object,propertyKey:string,descriptor:PropertyDescriptor):PropertyDescriptor{constoriginalMethod=descriptor.valueconstname=spanName??propertyKey descriptor.value=asyncfunction(...args:any[]){conststart=Date.now()letstatus:'success'|'error'='success'try{constresult=awaitoriginalMethod.apply(this,args)returnresult}catch(e){status='error'ErrorReporter.getInstance().report({method:name,error:e,args})throwe}finally{TraceContext.getInstance().addSpan({name,startTime:start,duration:Date.now()-start,status})}}returndescriptor}}

现在给一个完整的用户下单流程打上追踪:

classOrderService{@Traced('创建订单')asynccreateOrder(items:CartItem[]):Promise<Order>{constorder=awaitthis.buildOrder(items)awaitthis.validateStock(order)awaitthis.submitOrder(order)returnorder}@Traced('构建订单')privateasyncbuildOrder(items:CartItem[]):Promise<Order>{// ...}@Traced('校验库存')@CatchError(null)privateasyncvalidateStock(order:Order):Promise<boolean>{// ...}@Traced('提交订单')privateasyncsubmitOrder(order:Order):Promise<void>{// ...}}

调用createOrder后,就能拿到整条链路上每个步骤的耗时和状态。哪个环节慢了、哪个环节报错了,一目了然。

PerfMonitor 数据收集

前面用到的PerfMonitor简单实现一下:

// monitor/PerfMonitor.etsexportclassPerfMonitor{privatestaticinstance:PerfMonitorprivaterecords:Map<string,number[]>=newMap()staticgetInstance():PerfMonitor{if(!PerfMonitor.instance){PerfMonitor.instance=newPerfMonitor()}returnPerfMonitor.instance}markStart(label:string):void{// 可扩展:记录开始时间戳}record(label:string,duration:number):void{if(!this.records.has(label)){this.records.set(label,[])}this.records.get(label)!.push(duration)}getStats(label:string):{avg:number;max:number;count:number}{constdurations=this.records.get(label)??[]if(durations.length===0)return{avg:0,max:0,count:0}return{avg:durations.reduce((a,b)=>a+b,0)/durations.length,max:Math.max(...durations),count:durations.length}}dumpReport():string{constlines:string[]=['=== 性能报告 ===']this.records.forEach((durations,label)=>{constavg=(durations.reduce((a,b)=>a+b,0)/durations.length).toFixed(1)constmax=Math.max(...durations)lines.push(`${label}: 平均${avg}ms, 最大${max}ms, 调用${durations.length}`)})returnlines.join('\n')}}

踩过的坑

装饰器顺序很重要。多个装饰器叠加时,执行顺序是从下往上(靠近方法的先执行)。@Traced要在@CatchError上面,这样错误先被捕获,追踪记录的 status 才准确。

异步方法的耗时统计。很多人忘了async方法的返回值是 Promise,直接同步记录拿到的不是真实耗时。一定要判断返回值类型,异步的在.then()/.finally()里记录。

生产环境记得关掉详细日志。开发阶段全量记录没问题,上线后只保留慢方法告警和错误上报,不然日志量会很大。

小结

AOP 在 ArkTS 里用装饰器实现,比传统 Java 方案轻量得多。核心思路就是:把横切逻辑(日志、监控、错误处理)从业务代码里抽出来,通过装饰器织入

我自己在项目里的做法是,给所有网络请求方法和关键业务方法统一加上@Traced+@CatchError,一行代码搞定监控和容错。再也不用在业务代码里到处写 try-catch 和Date.now()了。代码干净了,监控也没落下。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/1 18:23:43

华为云Flexus+DeepSeek征文|Flexus X 实例一键部署 Dify + DeepSeek,搭建企业级知识库问答助手

前言 企业落地大模型时&#xff0c;最大的障碍往往不是模型本身的能力天花板&#xff0c;而是从模型能力到业务系统的最后一公里。DeepSeek-V3/R1 的商用推理能力已经足够强悍——编程、数学推理、长文本理解均达到第一梯队——但如果你的业务场景需要回答内部文档中的问题&am…

作者头像 李华
网站建设 2026/7/1 18:17:30

ROG幻16Air Type-C外接显示器休眠唤醒雪花屏问题分析与解决

一、问题现象 环境&#xff1a;ROG幻16Air&#xff08;GU605系列&#xff09;&#xff0c;Windows 11/10&#xff0c;外接显示器通过Type-C&#xff08;雷电4或DP 2.1口&#xff09;连接。症状&#xff1a;电脑进入休眠/睡眠后再次唤醒&#xff0c;外接显示器显示为雪花屏&…

作者头像 李华
网站建设 2026/7/1 18:15:02

高效Zotero笔记管理:用Mdnotes插件将学术文献秒变Markdown

高效Zotero笔记管理&#xff1a;用Mdnotes插件将学术文献秒变Markdown 【免费下载链接】zotero-mdnotes A Zotero plugin to export item metadata and notes as markdown files 项目地址: https://gitcode.com/gh_mirrors/zo/zotero-mdnotes Zotero-mdnotes是一款专为学…

作者头像 李华
网站建设 2026/7/1 18:14:13

HarmonyOS7 左边点类目右边跟着动,怎么做顺?分类页联动实战讲透

文章目录前言分类数据模型整体布局左侧导航列表右侧内容区域左右联动&#xff1a;核心逻辑嵌套滚动处理SectionHeader 吸顶效果完整的数据加载流程几点经验分享前言 分类页面是电商 App 的经典布局&#xff1a;左边一列一级分类&#xff0c;右边是对应的二级分类和商品。看着简…

作者头像 李华