鸿蒙架构之AOP

news2025/2/23 3:55:19

零、主要内容

  • AOP 简介
  • ArkTs AOP 实现原理
    • JS 原型链
    • AOP实现原理
  • AOP的应用场景
    • 统计类: 方法调用次数统计、方法时长统计
    • 防御式编程:参数校验
    • 代理模式实现
  • AOP的注意事项

一、AOP简介

对于Android、Java Web 开发者来说, AOP编程思想并不陌生。 AOP的使用核心在于要找到 Aspect(切面),然后再根据自己的需要,对某个“业务操作进”行 前置或者后置的处理,甚至可以替换“该业务操作”。 AOP的操作粒度就是方法级别, 一个方法包括 接收数据、处理数据和返回数据这么三个部分:
在这里插入图片描述
AOP 在这三个阶段都可以添加自己的逻辑处理。 Java中常见的AOP框架有很多:AspectJ、SpringAOP、Javassist、Guice、Byte Buddy等。ArkTs在4.0版本中也支持了AOP,那么ArkTs是如何实现AOP的呢?

二、ArkTs AOP 实现原理

接下来,我们首先要了解一下JS对象的在继承体系中的引用关系,这样才能够精准的选择合适的方法来进行切面编程。 然后我们在了解一下AOP是如何实现的。

2.1 JS 原型链

在这里插入图片描述
如上图所示:
水平维度:类通过prototype 引用着其原型对象, 通过constructor引种着其构造函数; 该类的构造函数中,关联着该类的静态方法;
竖直维度:类的原型对象通过__proto__指向父类原型对象;类的构造函数通过__proto__指向父类的构造函数;类的实例对象通过__proto__指向该类的原型对象;

那么对于实例对象a和对象b来说,其实例方法的定位如下图红色路径所示;对于类A和类B类说,其静态方法的的定位流程如下图蓝色路径所示:
在这里插入图片描述
通过上图,我们可以得出如下结论:
类的原型对象承载着该类对象的实例实例方法(非静态方法),并且通过__proto__ 指向父类的原型对象,通过constructor指向类(也就是类的构造函数,需要额外指出的是 类的静态方法存储在构造函数中)。 类(类的构造函数)通过__proto__指向父类(父类的构造构造函数)。

2.2 AOP实现原理

AOP的实现依赖于 插桩和替换来实现的, 其本质上将回调参数和原方法组合成一个新的函数,再用新的函数替换原方法,具体如下图所示:

“计算机科学中的所有问题都可以通过增加一个额外的间接层来解决”

在这里插入图片描述

2.2.1 AddBefore 原理的伪代码

// addBefore 的伪代码实现
static addBefore(targetClass, methodName , isStatic , before:Function) : void {
    // 根据是否静态方法,获取要插装的对象(是“类” ,还是“类的原型对象”)
    let target = isStatic ? targetClass : targetClass.prototype;
    // 根据方法名,获取原有的方法
    let origin = target[methodName];
    /**
    * 定义新的方法(包装一层),实现优先执行before的逻辑,然后执行原有方法origin,
    * 最后将返回结果给 外层调用者。
    */
    let newFuncs = function(...args) {
        // 先执行before方法,再执行当前方法
        before(this,...args);
        return origin.bind(this)(...args);    
    }
    // 使用新函数生效
    target[methodName] = newFuncs;
}

2.2.2 AddAfter 原理的伪代码

// addAfter 的伪代码实现
static addAfter(targetClass, methodName , isStatic , after:Function) :void {
    let target = isStatic ? targetClass : target.protoType;
    let original = target[methodName];
    let newFuncs = function(...args) {
        let ret = origin.bind(this)(...args);
        return after(this,r,...args); 
    }
}

2.2.3 Repalce 原理的伪代码

static replace(targetClass, methodName , isStatic , instead) :void {
    let target = isStatic ? targetClass : target.protoType;
    let newFuncs = function(...args) {
        return instead(this,...args); 
    }
    target[methodName] = newFuncs;
}

三、AOP的应用场景

  • 统计类: 方法调用次数统计、方法时长统计
  • 防御式编程:参数校验、返回值校验
  • 继承体系中的精确Hook
  • 代理模式和IOC

3.1 统计类

3.1.1 方法调用次数统计

export class Test {
    hello () {
        console.log('hello world')    
    }
}

我们通过Aspect.addBefore实现对Test类 hello方法调用次数的统计。

function main() {
    let countHello = 0;
    util.Aspect.addBefore(Test,'hello',false , ()=> {
        countHello++;
    });
    let h = new Test();
    console.log(`countHello : ${countHello}`)
    h.hello();
    console.log(`countHello : ${countHello}`)
}

3.1.2 方法时长统计

function addTimePrinter(target:Object, methodName:string, isStatic:boolean) {
    let t1 = 0;
    let t2 = 0;
    util.Aspect.addBefore(targetClass, methodName, isStatic, () => {
        t1 = new Date().getTime();
    });
    util.Aspect.addAfter(targetClass, methodName, isStatic, () => {
        t2 = new Date().getTime();
        console.log("t2---t1 = " + (t2 - t1).toString());
    });
}

测试addTimePrinter的功能:

export class View {
    onDraw() {
        // ...             
    }
    
    static cinit() {
        // ... 
    }
}


function main() {
    // 测试静态方法的时长统计
    addTimePrinter(Test,'cinit',true);
    View.cinit();
    // 测试实例方法的时长统计
    addTimePrinter(Test,'onDraw',true);
    new View().cinit();
}

3.2 防御式编程

  • 校验参数
  • 纠正返回值

3.2.1 校验参数

export class P004_View {
  children:P004_View[];

  constructor(children:Array<P004_View>) {
    this.children = children
  }

  getViewByIndex(index:number):P004_View {
    return this.children[index];
  }
}

上述View类的实例方法 getViewByIndex 的入参是一个index, 为了避免索引越界情况,我们可以通过Aspect类addBefore,增加一层”参数校验“的逻辑。

util.Aspect.addBefore(P004_View,"getViewByIndex",false, (view:P004_View, index:number)=> {
  if(view.children) {
    throw Error('view.children is undefined !')
  }

  if(index <= 0) {
    throw Error('index can not be negative !')
  }

  if((view.children as P004_View[]).length <= index) {
    throw Error('index is too big !')
  }
})

3.2.2 纠正返回值

export class P004_Random {        
    static randomSmallerThan50():number {
        return Math.floor(Math.random() * 52);
    }
}

randomSmallerThan50 方法的返回值期望是[0,50], 但是目前返回之返回是[0,51] , 我们可以使用Aspect类的addAfter方法,对返回值进行修正

export function testRandom() {
  util.Aspect.addAfter(P004_Random,'randomSmallerThan50',true,(target:P004_Random,ret:number)=> {
    if(ret > 50) {
      return P004_Random.randomSmallerThan50()
    } else {
      console.log(`P004_Random_randomSmallerThan50_addAfter ${ret}`)
      return ret;
    }
  })
  P004_Random.randomSmallerThan50()
}

3.3 子类实例方法替换

export class AirCraft {
  fly() {
    console.log('fight....')
  }
}

export class USA_AirCraft extends AirCraft{}

export class CN_AirCraft extends AirCraft{}

我们也可以通过Aspect类实现对子类的某个方法的 插桩或者替换。 下面是替换USA_AirCraft类的fly方法的代码:

export function testAirCraft() {
  let cn = new CN_AirCraft()
  let usa = new USA_AirCraft();

  cn.fly()
  usa.fly()

  util.Aspect.replace(USA_AirCraft,"fly",false,()=> {
    console.log('runaway....')
  })

  cn.fly()
  usa.fly();
}

3.4 控制反转(IOC)

AOP 也可以实现 控制反转。 如下图所示, PlayerManager 封装了播放器IPlayer接口,IPlayer 有ijkPlayer和mediaPlayer两个子类。 我们可以通过AOP 替换PlayerManager中的init() start() 等方法,来实现 两种Player对象的切换 。
在这里插入图片描述
上图中UML中的类,对应代码如下:

interface IPlayer {

  init(): void

  start(): void

  stop(): void

  release(): void
}

export class PlayManager {
  player?: IPlayer

  init(): void {
  }

  start(): void {
  }

  stop(): void {
  }

  release(): void {
  }
}

export class IjkPlayer implements IPlayer {
  init(): void {
    console.log('IjkPlayer init ...')
  }

  start(): void {
    console.log('IjkPlayer start ...')
  }

  stop(): void {
    console.log('IjkPlayer stop ...')
  }


  release(): void {
    console.log('IjkPlayer release ...')
  }
}


export class MediaPlayer implements IPlayer {
  init(): void {
    console.log('MediaPlayer init ...')
  }

  start(): void {
    console.log('MediaPlayer start ...')
  }

  stop(): void {
    console.log('MediaPlayer stop ...')
  }


  release(): void {
    console.log('MediaPlayer release ...')
  }
}

接下来,我们通过Aspect的replace方法来实现 player对象的替换:

/*
* 该方法 根据methodName,返回一个函数。该函数中会 当前player的对应的方法,并返回。 
*/
export function providePlayer(methodName: string, playerFetcher: ()=>IPlayer) {
  return (manager: PlayManager) => {
    if (methodName === 'init') {
      return playerFetcher().start()
    } else if (methodName === 'init') {
      return playerFetcher().start()
    } else if (methodName === 'start') {
      return playerFetcher().start()
    } else if (methodName === 'stop') {
      return playerFetcher().start()
    } else if (methodName === 'release') {
      return playerFetcher().release()
    }
  }
}


export function testPlayer() {
  let player:IPlayer = new IjkPlayer()
  // 通过replace, 替换对应的方法。
  util.Aspect.replace(PlayManager, "init", false, providePlayer("init",()=> player))
  util.Aspect.replace(PlayManager, "start", false, providePlayer("start",()=> player))
  util.Aspect.replace(PlayManager, "stop", false, providePlayer("stop",() => player))
  util.Aspect.replace(PlayManager, "release", false, providePlayer("release",()=> player))

  let playManager = new PlayManager()
  playManager.init()
  // 替换成MediaPlayer
  player = new MediaPlayer()
  playManager.start()
}

四、AOP注意事项

1.插桩的目标类通常需要导入进来,对于没有导出的场景,如果有实例,可以通过实例的constructor属性获取目标类。(这里告诉我们导入的类是一个类对象)

 // 类实例对象的constructor ,指向类对象。 
 util.Aspect.addBefore(this.context.constructor, 'startAbility', false,
      (instance: Object, wantParam: Want) => {
        console.info('UIAbilityContext startAbility: want.bundleName is ' + wantParam.bundleName);
      });

2.需要明确插桩的影响范围(可以根据JS原型链去理解)。
3. addBefore 注意事项:

util.Aspect.addBefore(Test, 'foo', false, (instance: Test) => { // 该函数的参数 第一个是一个对象,后续参数 则需要参考 源于函数声明
  ....
});
// 如果想要调用原有的函数,可以使用一个变量进行传递:
let oringalFoo = new Test().foo;
util.Aspect.addBefore(Test, 'foo', false, (instance: Test) => { // 该函数的参数 第一个是一个对象,后续参数 则需要参考 原函数声明
  // 方式一:如果原方法没有使用this,则可以直接调用原方法
  oringalFoo();
  // 方式二:如果原方法中使用了this,应该使用bind绑定instance,但是会有编译warning
  oringalFoo.bind(instance);
});

4.addAfter 注意事项:

util.Aspect.addAfter(Test, 'foo', false, (instance: Test, ret: string) => { // 该函数的参数 第一个是一个对象,第二个参数是 原函数的返回值
  console.log('execute foo');
  return ret;  // 一定要将原方法的返回值 传递出去
});

5.struct 不能插桩和替换; 方法的属性为只读时,不可以插桩和替换; 构造函数也不能被插桩和替换;

五、参考链接

鸿蒙官网-应用切面编程设计
es6的class&继承,揭开静态属性的原理和calss的本质

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

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

相关文章

【前端】包管理器:npm、Yarn 和 pnpm 的全面比较

前端开发中的包管理器&#xff1a;npm、Yarn 和 pnpm 的全面比较 在现代前端开发中&#xff0c;包管理器是开发者必不可少的工具。它们不仅能帮我们管理项目的依赖&#xff0c;还能极大地提高开发效率。本文将详细介绍三种主流的前端包管理器&#xff1a;npm、Yarn 和 pnpm&am…

错位情缘悬疑升级

✨&#x1f525;【错位情缘&#xff0c;悬疑升级&#xff01;关芝芝与黄牡丹的惊世婚约】&#x1f525;✨在这个迷雾重重的剧场&#xff0c;一场前所未有的错位大戏正悄然上演&#xff01;&#x1f440; 你没看错&#xff0c;昔日兄弟的前女友关芝芝&#xff0c;竟摇身一变成了…

axios使用sm2加密数据后请求参数多了双引号解决方法

axios使用sm2加密数据后请求参数多了双引号解决 背景问题描述解决过程 背景 因项目安全要求&#xff0c;需对传给后端的入参加密&#xff0c;将请求参数加密后再传给后端 前期将axios降低到1.6.7后解决了问题&#xff0c;但最近axios有漏洞&#xff0c;安全要求对版本升级&…

通过电压差判定无源晶振是否起振正确吗?

在电子工程中&#xff0c;无源晶振作为许多数字电路的基础组件&#xff0c;其是否成功起振对于系统的正常运行至关重要。然而&#xff0c;通过简单检测晶振两端的电压差来判断晶振是否工作&#xff0c;这一方法存在一定的误区&#xff0c;晶发电子将深入探讨这一话题&#xff0…

【AIGC】一、本地docker启动私有大模型

本地docker启动私有大模型 一、最终效果中英文对话生成代码 二、资源配置三、搭建步骤启动docker容器登录页面首次登录请注册登录后的效果 配置模型尝试使用选择模型选项下载模型选择适合的模型开始下载 试用效果返回首页选择模型中英文对话生成代码 四、附录资源监控 五、参考…

浮点类型使用陷阱

引言 当我们进行条件判断时,经常会遇到两个数是否相等的情况,但如果在程序中进行判断一个可以除尽的小数和数学上除出来所得的数是否相等时,就会神奇的发型居然不相等??! 遇到问题 看如下代码 double num5 2.7;//2.7double num6 8.1 / 3;//接近2.7System.out.println(n…

NAS免费用,鲁大师 AiNAS正式发布,「专业版」年卡仅需264元

7月10日&#xff0c;鲁大师召开新品发布会&#xff0c;正式发布旗下以“提供本地Ai部署和使用能力以及在线NAS功能”并行的复合软件产品&#xff1a;鲁大师 AiNAS。 全新的鲁大师 AiNAS将持续满足现如今大众对于数字化生活的全新需求&#xff0c;将“云存储”的便捷与NAS的大容…

学圣学最终的目的是:达到思无邪的状态( 纯粹、思想纯正、积极向上 )

学圣学最终的目的是&#xff1a;达到思无邪的状态&#xff08; 纯粹、思想纯正、积极向上 &#xff09; 中华民族&#xff0c;一直以来&#xff0c;教学都是以追随圣学为目标&#xff0c;所以中华文化也叫圣学文化&#xff0c;是最高深的上等学问&#xff1b; 圣人那颗心根本…

如何配置yolov10环境?

本文介绍如何快速搭建起yolov10环境&#xff0c;用于后续项目推理、模型训练。教程适用win、linux系统 yolo10是基于yolo8&#xff08;ultralytics&#xff09;的改进&#xff0c;环境配置跟yolo8几乎一模一样。 目录 第1章节&#xff1a;创建虚拟环境 第2章节&#xff1a;…

tesla p100显卡显示资源不足,api调用失败

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

数据结构JAVA

1.数据结构之栈和队列 栈结构 先进后出 队列结构 先进先出 队列 2.数据结构之数组和链表 数组结构 查询快、增删慢 队列结构 查询慢、增删快 链表的每一个元素我们叫结点 每一个结点都是独立的对象

浅谈“不要卷模型,要卷应用”

目录 1.概述 2.AI技术应用场景探索 3.避免超级应用陷阱的策略 3.1.追求DAU的弊端 3.2.平衡用户活跃度与应用实用性的策略 4.个性化智能体开发 4.1. 用户需求分析与数据收集 4.2. 技术选择与开发 4.3. 个性化算法设计 4.4. 安全性与隐私保护 4.5. 多渠道集成与响应机…

《昇思25天学习打卡营第14天|计算机视觉-ShuffleNet图像分类》

FCN图像语义分割&ResNet50迁移学习&ResNet50图像分类 当前案例不支持在GPU设备上静态图模式运行&#xff0c;其他模式运行皆支持。 ShuffleNet网络介绍 ShuffleNetV1是旷视科技提出的一种计算高效的CNN模型&#xff0c;和MobileNet, SqueezeNet等一样主要应用在移动端…

GraphGNSSLib Series[2]:在CLion中不同Node间进行debug

CLion实现Node debug 步骤&#xff1a; 我了解到的node&#xff0c;大多是通过终端运行&#xff0c;但是使用clion不断debug断点进行调试一直使我很苦恼&#xff0c;所以此次记录一下如何通过clion实现node节点之间通过publisher以及subscriber进行节点话题间的发布与通信&…

App H5+ 实现下载、查看功能 前后端实现(SpringBoot)

<!doctype html><html><head><meta charset"utf-8"><title>维修指南</title><meta name"viewport" content"widthdevice-width, initial-scale1.0, minimum-scale0, maximum-scale0.85, user-scalableyes&quo…

TotalSegmentator---针对CT/MRI数据的自动分割

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ&#xff1a;870202403 公众号&#xff1a;VTK忠粉 前言 本文分享开源软件TotalSegmentator&#xff0c;该开源项目针对CT/MRI数据类型&#xff0c;对主要的解剖学结构进行自动分割&#xff0c;并且被集成到MITK中。希望对各…

GB28181设备如何添加

简介 此篇描述视频网关&#xff08;中间件&#xff09;接入大华、海康、ONVIF设备&#xff08;NVR、摄像头&#xff09;、GB28181设备步骤和流程。 阅读本文档之前建议先阅览视频网关&#xff08;中间件&#xff09;用户使用手册。 接入方式和说明 视频网关&#xff08;中间…

为什么Windows操作系统一定要每年重装一次

在日常使用中&#xff0c;我们的电脑保存了无数的数据&#xff0c;安装和卸载程序&#xff0c;在这个过程中变得越来越慢&#xff0c;越来越容易出现问题。除了升级电脑硬件之外&#xff0c;有一个简单的办法可以让我们的电脑焕然一新&#xff1a;每年重新安装一次操作系统。 …

NFS综合项目

现有主机 node01 和 node02&#xff0c;完成如下需求&#xff1a; 1、在 node01 主机上提供 DNS 和 WEB 服务 2、dns 服务提供本实验所有主机名解析 3、web服务提供 www.rhce.com 虚拟主机 4、该虚拟主机的documentroot目录在 /nfs/rhce 目录 5、该目录由 node02 主机提供的NFS…

七人共赢拼团模式的深度剖析与互助精神重塑

在当今电商的浩瀚星海中&#xff0c;七人共赢拼团模式以其创新的合作框架与激励体系&#xff0c;正引领着消费与商业的新潮流。这一模式不仅优化了购物体验&#xff0c;更深刻诠释了互助共赢的核心理念。以下&#xff0c;我们将从直推奖励、自动补齐机制及团队荣耀奖三个方面&a…