ITPub博客

首页 > 应用开发 > C/C++ > 技术解读:HarmonyOS 开发实战:用 util.Aspect 玩转 AOP 切面编程

技术解读:HarmonyOS 开发实战:用 util.Aspect 玩转 AOP 切面编程

C/C++ 作者:简枢Node 时间:2026-10-19 09:58:22 0 删除 编辑
日志埋点、参数校验、耗时统计……这些和业务无关却又散落各处的代码,正在悄悄拖垮你的项目可维护性。HarmonyOS 提供的 util.Aspect 类,用三个接口就能把这些"横切关注点"从业务代码里干净地剥离出来。

一、为什么需要 AOP

class
OrderService
{
createOrder
(
params: OrderParams
) {
console
.
info
(
`[Log] createOrder called, params:
${
JSON
.stringify(params)}
`
);
const
start =
Date
.
now
();
if
(!params.
userId
) {
throw
new
Error
(
'userId is required'
);
    }
console
.
info
(
`[Log] createOrder cost:
${
Date
.now() - start}
ms`
);
}
}

日志、校验、性能统计这些逻辑和业务本身毫无关系,却被迫和业务代码搅在一起。这类问题在编程领域被称为 横切关注点(Cross-cutting Concerns) ,而 AOP(Aspect Oriented Programming,面向切面编程)就是专门解决它的范式:不修改业务源码,在方法执行前后动态插入通用逻辑。

HarmonyOS 在 ArkTS 中通过 运行时插桩机制 实现 AOP,核心就是 @kit.ArkTS 里 util 模块下的 Aspect 类 (API 11 起支持,API 12 起可在元服务中使用)。

import
{ util }
from
'@kit.ArkTS'
;

二、util.Aspect 的三个核心接口

util.Aspect 一共提供三个静态接口,对应三种插桩方式:

| 接口 | 作用 |
| --- | --- |
| addBefore() | 在原方法执行 前 插入自定义函数 |
| addAfter() | 在原方法执行 后 插入自定义函数,可拿到原方法返回值 |
| replace() | 直接 替换 原方法的实现 |

2.1 addBefore:方法前插桩

static
addBefore
(
targetClass
: Object,
methodName
:
string
,
isStatic
:
boolean
,
before
: Function):
void
| 参数 | 说明 |
| --- | --- |
| targetClass | 目标类对象(注意是类本身,不是实例) |
| methodName | 原方法名, 不支持 read-only 方法 |
| isStatic | 原方法是否为静态方法, true 静态 / false 实例方法 |
| before | 要插入的函数。第一个参数是 this 对象(实例方法为实例、静态方法为类对象),其余参数是原方法的参数 |

看一个完整示例,同时覆盖实例方法和静态方法两种场景:

import
{ util }
from
'@kit.ArkTS'
;

class
MyClass
{
msg
:
string
=
'msg000'
;

foo
(
arg
:
string
):
string
{
console
.
info
(
'foo arg is '
+ arg);
return
this
.
msg
;
  }

static
data
:
string
=
'data000'
;

static
bar
(
arg
:
string
):
string
{
console
.
info
(
'bar arg is '
+ arg);
return
MyClass
.
data
;
  }
}

util.
Aspect
.
addBefore
(
MyClass
,
'foo'
,
false
,
(
instance: MyClass, arg:
string
) =>
{
console
.
info
(
'[Before] arg is '
+ arg);
  instance.
msg
=
'msg111'
;
});

util.
Aspect
.
addBefore
(
MyClass
,
'bar'
,
true
,
(
target:
Object
, arg:
string
) =>
{
console
.
info
(
'[Before] arg is '
+ arg);
Reflect
.
set
(target,
'data'
,
'data111'
);
});

let
asp =
new
MyClass
();
let
result = asp.
foo
(
'123'
);
console
.
info
(
'result is '
+ result);

let
res =
MyClass
.
bar
(
'456'
);
console
.
info
(
'res is '
+ res);
  • 插入函数先于原方法执行 ,所以在 addBefore 里修改成员变量,会直接影响原方法读到的值。

  • 静态方法插桩时拿到的是类对象,修改静态成员需要用 Reflect.set() 。

2.2 addAfter:方法后插桩

static
addAfter
(
targetClass
: Object,
methodName
:
string
,
isStatic
:
boolean
,
after
: Function):
void

参数和 addBefore 基本一致,区别在于插入函数 after 的参数列表: 第二个参数是原方法的返回值 (原方法无返回值时为 undefined ),之后的参数才是原方法的参数。

⚠️ 最大的坑在这里: addAfter 插桩后,方法的最终返回值 = 插入函数的返回值。 如果你只是想做些记录工作,一定要记得把原返回值透传回去,否则调用方会拿到 undefined 。

import
{ util }
from
'@kit.ArkTS'
;
import
{
WebHandler
}
from
'./someThirdParty'
;

util.
Aspect
.
addAfter
(
WebHandler
,
'getWebAddrHttps'
,
false
,
(
instance: WebHandler, ret:
string
) =>
{
if
(!ret.
startsWith
(
'https'
)) {
throw
new
Error
(
"Handler's method 'getWebAddrHttps': return value does not start with 'https'"
);
  }
return
ret;
});

new
WebHandler
().
getWebAddrHttps
();

2.3 replace:替换方法实现

static
replace
(
targetClass
: Object,
methodName
:
string
,
isStatic
:
boolean
,
instead
: Function):
void

replace 更激进——原方法不会执行,直接由 instead 函数取而代之。适合测试桩、功能降级、Hook 第三方库行为等场景:

class
Test
{
name
:
string
=
'HarmonyOS'
;

getName
():
string
{
return
this
.
name
;
  }
}

util.
Aspect
.
replace
(
Test
,
'getName'
,
false
,
(
instance: Test
) =>
{
  instance.
name
=
'被替换了'
;
return
'replace 后的返回值'
;
});

let
test =
new
Test
();
console
.
info
(test.
getName
());

三、四个实战场景

场景一:方法耗时与调用次数统计

性能监控是 AOP 最经典的应用。 addBefore + addAfter 组合使用,给任意方法加上耗时统计,业务代码零改动:

import
{ util }
from
'@kit.ArkTS'
;

let
begin
:
number
=
0
;
let
count
:
number
=
0
;
let
totalCost
:
number
=
0
;

function
addTimePrinter
(
targetClass:
Object
, methodName:
string
, isStatic:
boolean
) {
util.
Aspect
.
addBefore
(targetClass, methodName, isStatic,
() =>
{
    begin =
new
Date
().
getTime
();
  });
util.
Aspect
.
addAfter
(targetClass, methodName, isStatic,
(
instance:
Object
, ret:
Object
) =>
{
    count +=
1
;
    totalCost +=
new
Date
().
getTime
() - begin;
console
.
info
(
`[Perf]
${methodName}
第
${count}
次调用,累计耗时
${totalCost}
ms`
);
return
ret;
});
}

addTimePrinter
(
DownloadImage
,
'instanceMethod'
,
false
);
DownloadImage
().
instanceMethod
(
this
.
url
);

这也是华为官方"加载时间及次数统计"示例的实现思路,给关键路径方法批量挂上统计,一份数据支撑性能优化。

场景二:方法调用计数

只想统计某个方法被调用了多少次?一行 addBefore 就够:

let
countFoo =
0
;

util.
Aspect
.
addBefore
(
Test
,
'foo'
,
false
,
() =>
{
  countFoo++;
});

new
Test
().
foo
();
console
.
info
(
'countFoo = '
+ countFoo);

场景三:成员变量 / 参数合法性校验

addBefore 回调的第一个参数是 this 对象,可以直接读取实例的成员变量做防御性校验,异常时直接抛错阻断方法执行:

export
class
Person
{
name
:
string
;
age
:
number
;

constructor
(
n:
string
, a:
number
) {
this
.
name
= n;
this
.
age
= a;
  }

getInfo
():
string
{
return
'name: '
+
this
.
name
+
', age: '
+
this
.
age
.
toString
();
  }
}

util.
Aspect
.
addBefore
(
Person
,
'getInfo'
,
false
,
(
instance: Person
) =>
{
if
(instance.
name
.
length
===
0
) {
throw
new
Error
(
'empty name'
);
  }
if
(instance.
age
<
0
) {
throw
new
Error
(
'invalid age'
);
  }
});

new
Person
(
'c'
, -
1
).
getInfo
();

校验逻辑集中在一处,业务类里完全看不到防御代码的影子。

场景四:对系统 SDK 接口插桩(封装代理)

想监控 window.createWindow() 、 rcp.createSession() 这类系统接口的调用?直接插桩会失败—— util.Aspect 只能作用于 ArkTS 层的类或对象方法,无法作用于 native 层实现的系统模块 。

官方的解法是:先封装一层 ArkTS 包装类(代理),再对包装类插桩:

import
{
window
}
from
'@kit.ArkUI'
;
import
{ util }
from
'@kit.ArkTS'
;

export
class
WindowWrap
{
private
static
instance
:
WindowWrap
=
new
WindowWrap
();

private
constructor
(
) {}

public
static
getInstance
(
) {
return
WindowWrap
.
instance
;
  }

public
createWindow
(
) {
let
config
:
window
.
Configuration
= {
name
:
'test'
,
windowType
:
window
.
WindowType
.
TYPE_DIALOG
};
window
.
createWindow
(config,
() =>
{
});
  }
}

util.
Aspect
.
addBefore
(
WindowWrap
,
'createWindow'
,
false
, (
instance
:
WindowWrap
):
void
=>
{
console
.
info
(
'[Aspect] createWindow 被调用'
);
});

WindowWrap
.
getInstance
().
createWindow
();

同理,如果想在应用拉起时记录目标包名、监控 rcp 网络会话创建,都可以套用这个"代理 + 插桩"模式。

四、避坑指南

  • isStatic 传错是最常见错误。 实例方法传 false 、静态方法传 true ,传反了插桩不生效或行为异常。

  • addAfter 必须 return 原返回值。 插入函数的返回值会覆盖原方法的返回值,忘记 return ret 会让调用方拿到 undefined ,这类 bug 排查起来很隐蔽。

  • 不支持 read-only 方法。 对只读方法插桩不会有效果。

  • 重复插桩的执行顺序:后插桩的先执行。 对同一个方法连续调用多次 addBefore ,后插入的函数会先执行(类似栈结构): util. Aspect . addBefore ( Test , 'printData' , true , () => console . info ( 'before 1' )); util. Aspect . addBefore ( Test , 'printData' , true , () => console . info ( 'before 2' )); Test . printData ();

  • 无法插桩 native 层方法。 系统 SDK 中很多接口(如 rcp、window)是 native 实现,必须通过封装代理类间接插桩。

  • 插桩是全局生效的运行时行为。 一旦执行 addBefore / addAfter / replace ,对该类的所有实例都生效,且没有官方提供的"取消插桩"接口。建议在应用初始化阶段统一注册,避免在页面生命周期里反复插桩导致逻辑叠加。

  • 元服务场景需 API 12+。 普通应用 API 11 起可用,元服务中从 API version 12 开始支持。

五、总结

util.Aspect 用三个接口覆盖了 AOP 的核心诉求:

  • addBefore :方法前拦截——参数校验、调用计数、执行前状态准备;

  • addAfter :方法后增强——返回值校验、耗时统计、结果记录(记得透传返回值);

  • replace :方法替换——测试打桩、行为 Hook。

它不能替代所有 AOP 框架(比如没有切面取消机制、无法插桩 native 方法),但对于日志埋点、性能监控、防御性校验这类高频需求,不需要引入任何三方库,几十行代码就能让业务代码回归纯粹。下次再想在业务方法里写 Date.now() 的时候,不妨想想:这段逻辑,是不是该交给 Aspect?

  • 华为官方文档:@ohos.util(util 工具函数)- Aspect

  • 华为最佳实践:应用切面编程设计

  • 华为 FAQ:ArkTS 中如何使用切面编程

  • 华为示例:加载时间及次数统计展示


转载声明:本文为转载整理,原文版权归原作者及原站点所有。

原文来源:Juejin HarmonyOS

原创地址:https://juejin.cn/post/7696791074135982143

原文作者:Keya;如有侵权,请联系管理员删除。

上一篇: 没有了~
下一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2023-03-27

  • 博文量
    1
  • 访问量
    173