鸿蒙架构之AOP

零、主要内容

  • 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(): voidstart(): voidstop(): voidrelease(): void
}export class PlayManager {player?: IPlayerinit(): 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()// 替换成MediaPlayerplayer = 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,但是会有编译warningoringalFoo.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.mzph.cn/bicheng/46480.shtml

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

相关文章

最值得推荐的10款Windows软件!

AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频播放量破百万https://aitools.jurilu.com/1.音乐播放器——Dopamine Dopamine是一款音乐播放器&#xff0c;设计简洁美观。它支持多种音频格式&#xff0c;包括wav、mp3、ogg…

亚马逊IP关联是什么?要怎么解决呢?

亚马逊不仅提供了广泛的商品和服务&#xff0c;也是许多企业和个人选择的电子商务平台。然而&#xff0c;与亚马逊相关的IP关联问题&#xff0c;特别是在网络安全和运营管理方面&#xff0c;经常成为使用亚马逊服务的用户和商家关注的焦点。通过了解亚马逊IP关联的含义、可能的…

MMLab-dataset_analysis

数据分析工具 这里写目录标题 数据分析工具dataset_analysis.py数据可视化分析 benchmark.pybrowse_coco_json.pybrowse_dataset.pyOptimize_anchors mmyolo、mmsegmentation等提供了数据集分析工具 dataset_analysis.py 数据采用coco格式数据 根据配置文件分析全部数据类型或…

pico+unity手柄和摄像机控制初级设置

1、摄像头配置 摄像头模式、floor是追踪原点类型&#xff08;将根据设备检测到地面的高度来计算追踪原点&#xff09;&#xff0c; Device 模式时&#xff0c;为通常理解的 Eye 模式&#xff0c;不会将根据设备检测到地面的高度来计算追踪原点 选择floor时&#xff0c;修改相…

K8S ingress 初体验 - ingress-ngnix 的安装与使用

准备环境 先把 google 的vm 跑起来… gatemanMoreFine-S500:~/projects/coding/k8s-s/service-case/cloud-user$ kubectl get nodes NAME STATUS ROLES AGE VERSION k8s-master Ready control-plane,master 124d v1.23.6 k8s-no…

王牌站士Ⅶ--理解大型语言模型LLM的参数

模型的大小并不一定决定其成功 在学习任何大型语言模型 (LLM) 时&#xff0c;您首先会听到的事情之一就是给定模型有多少个参数。如果您查看下面的图表&#xff0c;您会注意到参数大小范围很广 - 一个模型可能有 10 亿或 20 亿个参数&#xff0c;也可能有超过 1.75 万亿个参数。…

了解redis

1.什么是redis&#xff1f; redis是一款高性能的NOSQL系列的非关系型数据库 想了解非关系型数据库概念前往上期(NoSQL Not Only SQL)&#xff0c;意即“不仅仅是SQL”-CSDN博客 Redis是用C语言开发的一个开源的高性能键值对&#xff08;key-value&#xff09;数据库&#x…

CentOS7.X系统部署Zabbix6.0版本(可跟做)

文章目录 一、部署环境说明二、基本环境部署步骤1、环境初始化操作2、部署并配置Nginx3、部署并配置PHP4、测试NginxPHP环境5、部署并配置MariaDB 三、Zabbix-Server部署步骤1、编译安装Zabbix-Server2、导入Zabbix初始化库3、配置Zabbix前端UI4、启动Zabbix-Server5、WEB页面配…

java代码:单链表的实现

1、代码 package LinkList;public class Linklist {//定义节点&#xff0c;内部类只为其外部类使用//要创建嵌套类的对象&#xff0c;并不需要其外围类的对象&#xff0c;直接使用.nextstatic class ListNode{int val;//数据域ListNode next;//指针&#xff0c;指向下一个结点…

GPT-4从0到1搭建一个Agent简介

GPT-4从0到1搭建一个Agent简介 1. 引言 在人工智能领域&#xff0c;Agent是一种能够感知环境并采取行动以实现特定目标的系统。本文将简单介绍如何基于GPT-4搭建一个Agent。 2. Agent的基本原理 Agent的核心是感知-行动循环&#xff08;Perception-Action Loop&#xff09;…

C#与倍福Plc通信——使用仿真软件模拟倍福PLC运行

前言 我们在编写上位机与倍福PLC通信的过程中,有时候我们没有真实的Plc,但是我们又想提前测试与倍福PLC的通信,那么这个时候我们就可以使用倍福的仿真软件模拟PLC,然后我们上位机就可以与仿真PLC进行通信了,下面进行详细介绍: 1、下载并安装倍福PLC编程软件TwinCAT 安…

Android TabLayout+ViewPager2如何优雅的实现联动详解

一、介绍 Android开发过程中&#xff0c;我们经常会遇到滑动导航栏的做法&#xff0c;之前的做法就是我们通过ViewGroup来转动&#xff0c;然后通过大量的自定义来完成&#xff0c;将导航栏item与viewpage 滑动&#xff0c;达到业务需求 二、现实方案 通过介绍&#xff0c;我…

机器人前沿--PalmE:An Embodied Multimodal Language Model 具身多模态大(语言)模型

首先解释这篇工作名称Palm-E&#xff0c;发表时间为2023.03&#xff0c;其中的Palm是谷歌内部在2022.04开发的大语言模型&#xff0c;功能类似ChatGPT&#xff0c;只是由于各种原因没有那样火起来&#xff0c;E是Embodied的首字母&#xff0c;翻译过来就是具身多模态大语言模型…

宠物浮毛克星!最值得买的猫用空气净化器排名

作为用了3年宠物空气净化器的铲屎官来说&#xff0c;为什么铲屎官每到春秋换季就开始疯狂打喷嚏、突然开始全身过敏。其原因是猫毛一到换季就开始疯狂掉毛&#xff0c;相对于可见猫毛&#xff0c;漂浮在空气中的浮毛就是罪灰祸首。微小的浮毛在空气总容易被人体吸入体内&#x…

Qt+ESP32+SQLite 智能大棚

环境简介 硬件环境 ESP32、光照传感器、温湿度传感器、继电器、蜂鸣器 基本工作流程 上位机先运行&#xff0c;下位机启动后尝试连接上位机连接成功后定时上报传感器数据到上位机&#xff0c;上位机将信息进行处理展示判断下位机传感器数据&#xff0c;如果超过设置的阈值&a…

[misc]-流量包-wireshark-icmp

wireshark打开&#xff0c;大部分都是icmp,查看data部分 提取data长度&#xff1a; tshark.exe -r 1.pcapng -T fields -e data.len > length.txt 使用python解析这个文件&#xff0c;剔除异常值&#xff0c;每8个取一个值&#xff0c;得到flag ds [] with open(length.tx…

188家国产大模型:挑战与机遇,未来杀手级AI应用究竟该长什么样子?

未来的杀手级AI应用究竟该长什么样子&#xff1f;这篇文章里&#xff0c;作者梳理了国内外LLMs基础大模型的特征&#xff0c;并于最后发表了自己关于杀手级AI应用的看法和见解&#xff0c;一起来看一下。 摘要&#xff1a; 本文详细列表展示国外18家&#xff0c;国内188家大模…

ReentrantLock的源码实现和原理介绍

目录 一、概述 二、ReentrantLock的整体结构 三、ReentrantLock 和Synchronized相比 四、ReentrantLock 公平锁和非公平锁实现 4.1 ReentrantLock 源码解读 4.1.1 ReentrantLock 类源码解读 4.1.1.1 Lock接口 4.1.1.2 Sync抽象类 4.1.1.3 NonfairSync()和FairSync() 4…

EasyCVR视频技术:城市电力抢险的“千里眼”,助力抢险可视化

随着城市化进程的加速和电力需求的不断增长&#xff0c;电力系统的稳定运行对于城市的正常运转至关重要。然而&#xff0c;自然灾害、设备故障等因素常常导致电力中断&#xff0c;给城市居民的生活和企业的生产带来严重影响。在这种情况下&#xff0c;快速、高效的电力抢险工作…

产品介绍|九芯语音芯片的特点与应用市场

随着物联网与智能家居的普及&#xff0c;越来越多的电子产品有了语音播报的需求。九芯语音芯片集成了语音识别和语音合成技术&#xff0c;能够准确地捕捉并解析人类的语言&#xff0c;同时以清晰、自然的语调进行回应&#xff0c;为各类智能设备注入了强大的语言交互能力。 特点…