Unity应用架构设计(12)——AOP思想的实践

简介:

想象一下,当程序所有的业务逻辑都完成的时候,你可能还来不及喘口气,紧张的测试即将来临。你的Boss告诉你,虽然程序没问题,但某些方法为什么执行这么慢,性能堪忧。领会了Boss的意图之后,漫长的排查问题开始了。你会写日志,或者是其他工具来追踪原因。那么如何以一种优雅的形式,并且不侵入业务代码的形式来跟踪呢?这正是本文的内容。

跟踪问题

通过观察,你发现方法Do执行缓慢,可能有性能问题,因为这是一个线上的版本,你无法进行Debug,所以你通过日志的形式来追踪执行步骤:

class Foo1
{
    void Do()
    {
        //日志记录开始
        //性能监控开始
        DoSomething();
        //日志记录结束
        //性能监控结束
    }
}

看起来不错,解决问题之后,测试又发现另一个方法Handle貌似也有问题,然后你一样画葫芦,虽然麻烦了一点,但总归是有解决方案:

class Foo2
{
    void Handle()
    {
        //日志记录开始
        //性能监控开始
        DoSomething();
        //日志记录结束
        //性能监控结束
    }
}

上述两段代码,虽然看起来很丑,但毕竟是能解决问题,但是代码的风格不怎么让人舒服:

  • 代码没有重用,比如日志记录,性能监控,它们实则是做了一样的事情
  • 日志记录,性能监控代码侵入了核心的业务逻辑,造成了混乱

知道了问题之后,第二种风格的代码出现了:

class Bar
{
    void Do()
    {
        common.BeginLog();
            common.BeginWatch();
                common.BeginTransaction();
                    foo.Do();
                common.Commit();
            common.EndWatch();
        common.EndLog();
    }
}

看似是个不错的方案,但实际上还是没解决本质问题。虽然将日志,监控放到了Common中,但每个方法还是要写这一大堆和业务无关的代码,这压根什么也没解决,这个方法的层次结构如下图所示:

AOP面向切面编程的引入

什么是AOP?

  • 名称来源->『Aspect Oriented Programming』的缩写,中文翻译即『面向切面编程』
  • 应用场景->为日志记录,性能监控,安全控制,事务处理,异常处理等与具体业务逻辑无关,却又需要在全局范围执行的功能提供了一种良好的重用并且和业务逻辑解耦
  • 核心理念->AOP的思想是围绕着切面进行的,所谓的『切面』就是对目标对象的某种操作进行拦截,在系统其他部分调用目标对象的某种操作时拦截这些调用,并在进行真正的调用前、后执行一段中间逻辑
  • 实现方式->AOP的实现方式被分为『静态织入』和『动态织入』。采用『静态织入』方式通过扩展编译器对代码的中间语言IL插入代码,从而对目标对象进行拦截。『动态织入』则在运行时创建动态代理对象来拦截

如果你是第一次接触『面向切面编程』,可能这些概念太过复杂和笼统,我建议先翻阅相关书籍、博客,最好对AOP有一定的了解。

什么是『切面』?

『面向切面编程』总共6个字,想必最难理解的还是『切面』两字吧。

  • 在现实生活中,一个大西瓜,一刀切下去,一分为二,这破坏了它的整体性,瓜瓤完全暴露在你面前,然后你可以随心所欲的吃它,但不切开,你肯定吃不了它。
  • 在计算机世界,一个类(对象)包裹了若干方法,它像西瓜那样也是一个整体,你将方法作为一个切入点,一刀切下去,方法完全暴露在你面前,你可以按照你所需的要求进行拦截,但不切开,你肯定拦截不了。当然,计算机世界里肯定不会让你拿一把刀来切一个对象,这是一个更加抽象的概念,下面会阐述

所以『切面』是一种横向的拦截,而非纵向继承的机制。

使用纵向继承的方式来拦截方法:

class Order
{
    public virtual void Add()
    {
        Console.WriteLine("新增订单");
    }
}

class OrderExt : Order
{
    public override void Add()
    {
        //开启事务
        BeginTransaction();
        base.Add();
        //提交事务
        Commit();
    }
    void BeginTransaction() { }
    void Commit() { }
}

缺点上面已经提到过了,不再重复。

使用AOP横向拦截方法,通过动态代理类来实现:

class Order
{
    public virtual void Add()
    {
        Console.WriteLine("新增订单");
    }
}

class TransactionUtility
{
    public void BeginTransaction() { }
    public void Commit() { }
}

class OrderProxy
{
    public Order o;
    public TransactionUtility u;
    public void Add()
    {
        u.BeginTransaction();
        o.Add();
        u.Commit();
    }
}

当然这个OrderProxy一般是通过框架(比如Spring)在运行时动态创建的,所以叫动态代理对象。客户端不知道真正调用的对象其实是OrderProxy。整个过程如下所示:

1.) Target:目标类,需要被代理的类
2.) Join Point:连接点,指那些可能被拦截到的方法。
3.) Point Cut:切入点,已经被增强的连接点
4.) 切面类:提供了事务管理,日志记录,性能监控等公共操作的类
5.) Advice:通知点,用来增强代码
6.) Weaving:织入,将Advice应用到目标对象Target,是创建新的代理对象Proxy的过程。
7.) Proxy:代理类
8.) Aspect:切面,是切入点PointCut和通知Advice的结合,2点确定一条线,多条线组合成面

在Unity中使用AOP思想

很遗憾,在Unity中没有好的AOP框架,虽然.NET有很多AOP框架,但考虑到Unity的跨平台,很多技术并不兼容。所以我以另一种形式间接的实现了AOP。

理解了AOP之后,实际上我只关注两点:

  • 既然没有框架来动态创建代理对象,那我只能自己创建
  • 代理对象是用来拦截方法的,你需要告诉代理对象怎么来拦截方法,所以通过委托的形式来指定策略

定义一个Proxy类,指定需要被代理的类的方法,以及拦截策略:

public class Proxy
{
    public static Proxy Instance = new Proxy();

    private IInvocationHandler _invocationHandler;
    private object _target;
    private string _method;
    private object[] _args;

    private Proxy()
    {
    }

    public Proxy SetInvocationHandler(IInvocationHandler invocationHandler)
    {
        _invocationHandler = invocationHandler;
        return this;
    }

    public Proxy SetTarget(object target)
    {
        _target = target;
        return this;
    }

    public Proxy SetMethod(string method)
    {
        _method = method;
        return this;
    }

    public Proxy SetArgs(object[] args)
    {
        _args = args;
        return this;
    }

    public object Invoke()
    {
        var methodInfo = _target.GetType().GetMethod(_method);
        return _invocationHandler.Invoke(_target, methodInfo, _args);
    }
}

拦截策略是个公共接口,提供策略:

public interface IInvocationHandler
{
    void PreProcess();
    object Invoke(object proxy, MethodInfo method, object[] args);
    void PostProcess();
}

实现接口,就可以自定义拦截方法,一个日志的策略如下所示:

public class LogInvocationHandler:IInvocationHandler
{
    public void PreProcess()
    {
        LogFactory.Instance.Resolve().Log("Pre Process");
    }

    public object Invoke(object target, MethodInfo method, object[] args)
    {
        PreProcess();
        var result= method.Invoke(target, args);
        PostProcess();
        return result;
    }

    public void PostProcess()
    {
        LogFactory.Instance.Resolve().Log("Post Process");
    }
}

假设你需要对repository对象的Test方法进行拦截,即执行前后进行日志的打印,你可以这样来使用:

 Proxy.Instance.SetTarget(repository)
    .SetMethod("Test")
    .SetArgs(new object[] {})
    .SetInvocationHandler(new LogInvocationHandler())
    .Invoke();

小结

AOP思想是非常重要的重构手段,以不侵入的形式解耦业务逻辑和拦截方法。本质上是以横向扩展的形式替换了传统的纵向继承方式来实现。遗憾的是,在Unity中并没有好的AOP框架,我按照AOP的思想,简化了实现模式,以曲线的形式实现对方法的拦截。
源代码托管在Github上,点击此了解

本博客为 木宛城主原创,基于 Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 木宛城主(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。

本文转自木宛城主博客园博客,原文链接:http://www.cnblogs.com/OceanEyes/p/aop_in_action.html,如需转载请自行联系原作者
相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
目录
相关文章
|
7月前
|
人工智能 JavaScript 前端开发
GenSX (不一样的AI应用框架)架构学习指南
GenSX 是一个基于 TypeScript 的函数式 AI 工作流框架,以“函数组合替代图编排”为核心理念。它通过纯函数组件、自动追踪与断点恢复等特性,让开发者用自然代码构建可追溯、易测试的 LLM 应用。支持多模型集成与插件化扩展,兼具灵活性与工程化优势。
543 6
|
7月前
|
人机交互 开发工具 vr&ar
使用Unity引擎开发Rokid主机应用的模型交互操作
本文介绍如何使用Unity引擎结合Rokid OpenXR Plugin开发空间计算应用,实现射线交互、模型操作等功能。涵盖环境配置、Demo导入、UI搭建与脚本编写,助力开发者快速构建AR交互应用。
|
8月前
|
人工智能 Cloud Native 中间件
划重点|云栖大会「AI 原生应用架构论坛」看点梳理
本场论坛将系统性阐述 AI 原生应用架构的新范式、演进趋势与技术突破,并分享来自真实生产环境下的一线实践经验与思考。
|
8月前
|
机器学习/深度学习 人工智能 vr&ar
H4H:面向AR/VR应用的NPU-CIM异构系统混合卷积-Transformer架构搜索——论文阅读
H4H是一种面向AR/VR应用的混合卷积-Transformer架构,基于NPU-CIM异构系统,通过神经架构搜索实现高效模型设计。该架构结合卷积神经网络(CNN)的局部特征提取与视觉Transformer(ViT)的全局信息处理能力,提升模型性能与效率。通过两阶段增量训练策略,缓解混合模型训练中的梯度冲突问题,并利用异构计算资源优化推理延迟与能耗。实验表明,H4H在相同准确率下显著降低延迟和功耗,为AR/VR设备上的边缘AI推理提供了高效解决方案。
1370 0
|
7月前
|
机器学习/深度学习 自然语言处理 算法
48_动态架构模型:NAS在LLM中的应用
大型语言模型(LLM)在自然语言处理领域的突破性进展,很大程度上归功于其庞大的参数量和复杂的网络架构。然而,随着模型规模的不断增长,计算资源消耗、推理延迟和部署成本等问题日益凸显。如何在保持模型性能的同时,优化模型架构以提高效率,成为2025年大模型研究的核心方向之一。神经架构搜索(Neural Architecture Search, NAS)作为一种自动化的网络设计方法,正在为这一挑战提供创新性解决方案。本文将深入探讨NAS技术如何应用于LLM的架构优化,特别是在层数与维度调整方面的最新进展,并通过代码实现展示简单的NAS实验。
346 0
|
9月前
|
Web App开发 Linux 虚拟化
Omnissa Horizon 8 2506 (8.16) - 虚拟桌面基础架构 (VDI) 和应用软件
Omnissa Horizon 8 2506 (8.16) - 虚拟桌面基础架构 (VDI) 和应用软件
453 0
Omnissa Horizon 8 2506 (8.16) - 虚拟桌面基础架构 (VDI) 和应用软件
|
9月前
|
机器学习/深度学习 数据采集 存储
技术赋能下的能源智慧管理:MyEMS 开源系统的架构创新与应用深化
在全球能源转型与“双碳”战略推动下,MyEMS作为基于Python的开源能源管理系统,凭借模块化架构与AI技术,助力重点用能单位实现数字化、智能化能源管理。系统支持多源数据采集、智能分析、设备数字孪生与自适应优化控制,全面满足国家级能耗监测要求,并已在制造、数据中心、公共建筑等领域成功应用,助力节能降碳,推动绿色可持续发展。
261 0
|
8月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
756 0
|
7月前
|
监控 Java Spring
AOP 切面编程
AOP(面向切面编程)通过动态代理在不修改源码的前提下,对方法进行增强。核心概念包括连接点、通知、切入点、切面和目标对象。常用于日志记录、权限校验、性能监控等场景,结合Spring AOP与@Aspect、@Pointcut等注解,实现灵活的横切逻辑管理。
1847 6
AOP 切面编程
|
7月前
|
XML Java 数据格式
《深入理解Spring》:AOP面向切面编程深度解析
Spring AOP通过代理模式实现面向切面编程,将日志、事务等横切关注点与业务逻辑分离。支持注解、XML和编程式配置,提供五种通知类型及丰富切点表达式,助力构建高内聚、低耦合的可维护系统。

热门文章

最新文章