• aop 阶段性概况


    前言

    对aop进行一个阶段性的总结。

    正文

    首先什么是aop呢?

    那么首先看aop的解决什么样的问题。

    public class Program
    {
        public static void Main(string[] args)
        {
            
        }
    
        public void ChangePosition1()
        {
            // your operation
            SavePosition();
        }
    
        public void ChangePosition2()
        {
            // your operation
            SavePosition();
        }
    
        public void SavePosition()
        {
        }
    }
    

    看上面这个位置:
    ChangePosition1 和 ChangePosition2

    他们做完一些操作之后,需要做相同的操作,如上面所述,需要保存位置。

    只有两个方法调用,那么不是啥子问题。

    但是如果有大量的方法去调用这个东西,那么问题就出现了。

    第一:重复性的工作
    第二:出错率,每次都要写一遍,可能出现忘记的情况
    第三:看着不优雅

    那么在如此多的案例的情况下,人们都发现了规律。

    比如说,在某个行为前做什么,在某个行为后做什么。

    那么我们可以进行扩展:

    public void Dosomething()
    {
      // your operaion
    }
    
    aspect logging
    {
       befor(dosomething is called)
       {
          Log.Write("enter dosomething")
       }
    
       after(dosomething is called)
       {
          Log.Write("after dosomething")
       }
    }
    
    aspect verification()
    {
        befor(dosomething is called)
        {
          // your verification
        }
    }
    

    比如我们的验证和日志,可以通过这些aop去处理。

    aop 全称是aspect-oriented programming 中文大多翻译过来是面向切面编程。

    oriented 是面向,比如日志、验证,这些是aspect。

    所以取了asepct-oriented programming 这个名字。

    好的,那么现在了解了aop是什么东西,也了解了他的由来。

    下面了解一下aop滥用的情况。

    什么情况aop会滥用呢?

    比如说:

    public void setsomething()
    {
    }
    
    aspect shutup
    {
      after(something is called)
      {
        // shutup
      }
    }
    

    这种aop 就是反模式,不被推荐的。

    原因是,比如我执行了setsomething之后,我setsomething 的意思是设置某一些东西,而没有shutup的含义。

    但是最后却运行了该操作,这样难以维护和理解。

    那么为什么logging 和 verification 能够被人所接受呢?

    因为其没有破坏我们该代码的逻辑,的确setsomething做的事情就是它应该做的事情,不对执行逻辑造成影响。

    深入一下aop是如何实现的呢?其实aop的原理很简单,但是优雅实现的却有一点点复杂。

    • 字节码操作

      • 优点:字节码操作可以在更细粒度的层面上操作代码,可以实现更灵活和精细的AOP功能。可以在编译期或运行期动态修改字节码,对目标类进行修改。
      • 缺点:实现相对复杂,需要对字节码结构有一定的了解。可能会影响代码的可读性和维护性。
    • 代理技术

      • 优点:代理技术相对简单易懂,可以快速实现AOP功能。可以通过代理对象来实现横切关注点的功能,不需要直接操作字节码。
      • 缺点:代理技术通常在运行时动态创建代理对象,可能会引入性能开销。对于一些高性能要求的场景,可能不太适合。

    下面对这个分别进行举例:

    先从好理解的代理开始:

    public class LoggingAspect
    {
        public void LogBefore(string methodName)
        {
            Console.WriteLine($"Logging before {methodName} execution");
        }
    }
    

    然后创建相应的代理:

    public class UserServiceProxy<T> : DispatchProxy
    {
        private T _decorated;
        
        private LoggingAspect _loggingAspect;
        
        protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
        {
            _loggingAspect.LogBefore(targetMethod.Name);
            
            return targetMethod.Invoke(_decorated, args);
        }
        
        public static T Create(T decorated)
        {
            object proxy = Create>();
            ((UserServiceProxy)proxy)._decorated = decorated;
            ((UserServiceProxy)proxy)._loggingAspect = new LoggingAspect();
            
            return (T)proxy;
        }
    }
    

    解释一下,这个create创建了什么,这个create 创建了两个类型的继承类。

    也就是创建了动态类型。

    public class UserService : IUserService
    {
        public void Login()
        {
            Console.WriteLine("User logged in");
        }
    }
    

    现在是我们的代码实现了。

    那么看下是怎么调用的。

    public static void Main(string[] args)
    {
        IUserService userService = new UserService();
        IUserService proxy = UserServiceProxy.Create(userService);
        proxy.Login();
    }
    

    这样就调用完成了。

    看下效果。

    这样就完成了相应的code。

    至于为什么我们在使用框架的时候进行属性标记即可,那是因为框架帮我们把事情做了。

    例如:

    1. 安装PostSharp:首先需要安装PostSharp NuGet包。

    2. 定义切面类

    [Serializable]
    public class LoggingAspect : OnMethodBoundaryAspect
    {
        public override void OnEntry(MethodExecutionArgs args)
        {
            Console.WriteLine($"Logging before {args.Method.Name} execution");
        }
    }
    
    1. 应用切面:在需要应用AOP的方法上添加切面标记。
    public class UserService
    {
        [LoggingAspect]
        public void Login()
        {
            Console.WriteLine("User logged in");
        }
    }
    

    至于这个框架是怎么实现的,原理就是利用MSBuilder。

    MSBuild工具可以通过自定义任务(Custom Tasks)来实现预处理操作。通过编写自定义任务,可以在MSBuild构建过程中执行特定的预处理逻辑。以下是一个简单的示例,演示如何在MSBuild中使用自定义任务进行预处理:

    1. 创建自定义任务
    using Microsoft.Build.Framework;
    using Microsoft.Build.Utilities;
    
    public class CustomPreprocessTask : Task
    {
        public override bool Execute()
        {
            // 在这里编写预处理逻辑
            Log.LogMessage("Custom preprocessing task executed.");
            return true;
        }
    }
    
    1. 在项目文件中引用自定义任务

    在项目文件(.csproj)中添加以下内容,引用自定义任务:

    <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <UsingTask TaskName="CustomPreprocessTask" AssemblyFile="Path\\To\\CustomTask.dll" />
      
      <Target Name="CustomPreprocessTarget" BeforeTargets="Build">
        <CustomPreprocessTask />
      Target>
    Project>
    
    1. 执行预处理操作

    在构建项目时,MSBuild会执行自定义任务中定义的预处理逻辑。可以在Execute方法中编写任何需要的预处理代码,例如生成文件、修改配置等操作。

    通过编写自定义任务并在项目文件中引用,可以利用MSBuild进行预处理操作。这样可以在构建过程中执行特定的逻辑,实现更灵活的构建流程。

    代理模式,就是利用了msbuilder 预处理逻辑,在编译前进行了预处理。

    那么字节码模式就是在msbuilder 编译后进行预处理。

    using Mono.Cecil;
    using Mono.Cecil.Cil;
    
    public class LoggingAspect
    {
        public void LogBefore()
        {
            Console.WriteLine("Logging before method execution");
        }
    }
    
    public class Program
    {
        public static void Main()
        {
            AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly("YourAssembly.dll");
            ModuleDefinition module = assembly.MainModule;
    
            TypeDefinition type = module.Types.Single(t => t.Name == "UserService");
            MethodDefinition method = type.Methods.Single(m => m.Name == "Login");
    
            ILProcessor processor = method.Body.GetILProcessor();
            Instruction firstInstruction = method.Body.Instructions.First();
    
            // 在方法开头插入日志记录代码
            processor.InsertBefore(firstInstruction, processor.Create(OpCodes.Call, typeof(LoggingAspect).GetMethod("LogBefore")));
    
            assembly.Write("YourModifiedAssembly.dll");
        }
    }
    

    就是在我们程序编译完成后,进行在相应的位置进行注入程序。

    下一节oop,关于aop解决一些实际问题的例子后续补齐。

  • 相关阅读:
    java计算机毕业设计智能办公管理系统源程序+mysql+系统+lw文档+远程调试
    教程六 在Go中使用Energy创建跨平台GUI - 应用下载事件
    Linux下查看并关闭一个进程(用于Qt的QProcess)
    基于Hardhat和Openzeppelin开发可升级合约(一)
    [论文笔记]DSSM
    集训杂记 7/17
    SeamlessM4T—Massively Multilingual & Multimodal Machine Translation
    Go gRPC 入门
    CSS 什么是伪类?什么是伪元素?
    ChatGPT研究报告:AIGC带来新一轮范式转移
  • 原文地址:https://www.cnblogs.com/aoximin/p/18134418