美文网首页
AOP(面向切面编程)大概了解一下

AOP(面向切面编程)大概了解一下

作者: Code综艺圈 | 来源:发表于2021-03-12 09:09 被阅读0次

    前言

    上一篇在聊MemoryCache的时候,用到了Autofac提供的拦截器进行面向切面编程,很明显能体会到其优势,既然涉及到了,那就趁热打铁,一起来探探面向切面编程。

    正文

    1. 概述

    在软件业,AOPAspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能统一维护的一种技术。AOP是OOP(面向对象程序设计)的延续,是软件开发中的一个热点,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

    ---来自百度百科

    总结优点

    • 对业务逻辑的各个部分进行隔离,业务之间耦合度降低;

    • 提高程序的可重用性,同时程序更容易维护;

    • 提高开发效率,不用花大量的时间在业务中增加代码,还能降低风险;

    其实AOP的本质就是动态代理,何为动态代理呢?

    动态代理就是在程序运行时,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。通俗一点来说就是在运行期间对方法的拦截,在方法执行前后进行额外的业务处理,从而在不嵌入原有代码逻辑情况下就能增强被拦截方法的业务能力。

    理论先到这,一起来看看用代码怎么实现吧?

    2. 实践检验真理(到底优不优秀)

    先来一个控制台项目,什么都没有,从头开始撸代码,先来看看项目结构:

    图片

    老案例了,还是假装在进行用户维护,模拟对用户进行增删改查。这次就直接上代码啦啊,根据项目结构依次看看代码:

    • 在AopModel中增加User.cs

      <pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c#" cid="n127" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit;"> public class User
      {
      public string Name { get; set; }
      public int Age { get; set; }
      }</pre>

    • 在AopService中增加IUser.cs和User.cs

      IUserService.cs

      <pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c#" cid="n131" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit;"> public interface IUserService
      {
      bool AddUser(User user);
      }</pre>

      UserService.cs

      <pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c#" cid="n133" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit;"> public class UserService : IUserService
      {
      public bool AddUser(User user)
      {
      Console.WriteLine("用户添加成功");
      return true;
      }
      }</pre>

    • Main方法

      <pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c#" cid="n136" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit;"> class Program
      {
      static void Main(string[] args)
      {
      Console.WriteLine("========原始需求=========");
      User user = new User { Name = "Zoe", Age = 18 };
      IUserService userService = new UserService();
      // 模拟增加一个用户
      userService.AddUser(user);
      Console.ReadLine();
      }
      }</pre>

    这样项目就正常运行啦,这个就不用我截图了吧,小伙伴都会吧。

    项目运行正常,但需要加一个需求:用户增加成功之后进行邮件发送通知。

    目前有两种解决方案

    • 直接在增加用户方法中添加加发送邮件逻辑(相信很多小伙伴是这样做的,见效快,还简单);

      如果频繁在增加用户前或后添加新需求呢,还继续加吗,可能到最后增加用户的方法变得很复杂,后期也不好维护;如果要去掉某一个功能,又得把代码改回来,作为程序员是不是又要和产品同事搞架啦(文明人,不动手);当然,如果需求固定,这种方式也不错。

    • 面向切面实现,即在不影响原有代码逻辑的情况,动态的对方法进行拦截,在方法执行前或后添加业务即可。

    项目中引入AOP(面向切面编程)思想
    • 原始动态代理实现;

      先来加个代理类,如下:

      <pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c#" cid="n151" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit;"> using System;
      using System.Collections.Generic;
      using System.Reflection;
      using System.Text;
      namespace Aop
      {
      // 继承DispatchProxy
      public class MyProxy : DispatchProxy
      {
      //具体类型
      public object TargetClass { get; set; }
      protected override object Invoke(MethodInfo targetMethod, object[] args)
      {
      Console.WriteLine("增加用户前执行业务");
      //调用原有方法
      targetMethod.Invoke(TargetClass, args);
      Console.WriteLine("增加用户后执行业务");
      return true;
      }
      }
      }</pre>

      然后在Main函数直接使用即可,如下:

      <pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c#" cid="n153" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit;"> class Program
      {
      static void Main(string[] args)
      {
      //原始需求
      User user = new User { Name = "Zoe", Age = 18 };
      IUserService userService = new UserService();
      userService.AddUser(user);
      Console.WriteLine("========动态代理 实现新需求=========");
      //1. 创建代理对象
      IUserService userService1 = DispatchProxy.Create<IUserService, MyProxy>();
      //2. 因为调用的是实例方法,需要传提具体类型
      ((MyProxy)userService1).TargetClass = new UserService();
      userService1.AddUser(user);
      Console.ReadLine();
      }
      }</pre>

      动态代理就实现需求功能啦,可以在用户增加前或后都进行相关需求处理,运行看效果:

      图片
    • 第三方库Castle.Core封装的美滋滋;

      通过上面演示,原生的动态代理实现面向切面编程显得相对麻烦,比如强制转换、传递类型等操作;常用的Castle.Core将动态代理进一步封装,使用就相对方便点啦;这次定义一个拦截器即可:

      <pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c#" cid="n160" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit;"> using Castle.DynamicProxy;
      using System;
      using System.Collections.Generic;
      using System.Text;

      namespace Aop
      {
      // 自定义拦截器
      public class MyIntercept : IInterceptor
      {
      public void Intercept(IInvocation invocation)
      {
      //执行原有方法之前
      Console.WriteLine("增加用户前执行业务");
      //执行原有方法
      invocation.Proceed();
      //执行原有方法之后
      Console.WriteLine("增加用户后执行业务");
      }
      }
      }</pre>

      Main函数中使用拦截器即可,如下:

      <pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c#" cid="n162" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit;"> using AopModel;
      using AopService;
      using Castle.DynamicProxy;
      using System;
      using System.Reflection;
      using System.Reflection.Metadata;

      namespace Aop
      {
      class Program
      {
      static void Main(string[] args)
      {
      Console.WriteLine("========原始需求=========");
      User user = new User { Name = "Zoe", Age = 18 };
      IUserService userService = new UserService();
      // 模拟增加一个用户
      userService.AddUser(user);
      Console.WriteLine("========动态代理 实现新需求=========");
      //1. 创建代理对象
      IUserService userService1 = DispatchProxy.Create<IUserService, MyProxy>();
      //2. 因为调用的是实例方法,需要传提具体类型
      ((MyProxy)userService1).TargetClass = new UserService();
      userService1.AddUser(user);
      Console.WriteLine("=============Castle.Core方式==============");
      //先实例化一个代理类生成器
      ProxyGenerator generator = new ProxyGenerator();
      //通过代理类生成器创建
      var u = generator.CreateInterfaceProxyWithTarget<IUserService>(new UserService(), new MyIntercept());
      u.AddUser(user);
      Console.ReadLine();
      }
      }
      }</pre>

      运行效果如下:

      图片
    • Autofac集成了Castle.Core用着也挺不错;

      Autofac已经集成了Castle.Core啦,在聊MemoryCache的时候就已经用到,使用比较简单,可以通过特性标注的方式就可以针对某个类或接口的方法进行拦截加强,详情请参考这篇文章(因MemoryCache闹了个笑话)。

    3. 应用场景

    AOP思想是很优秀,但总不能处处都得用吧,需根据业务来评估是否需要;常用应用场景大概有以下几个:

    • 安全控制:通常在Web开发的时候,会使用过滤器或拦截器进行权限验证,这也是AOP思想的落地;对于客户端程序,通过上述演示的几种方式可以轻松实现权限的统一管理和验证;

    • 事务处理:相信小伙伴都写过数据库事务代码吧,常规做法就是在业务方法中直接开启事务,执行完成,提交或回滚即可,AOP思想也能很好处理这种情况;

    • 异常处理:统一的异常处理是最好的选择,除非是特殊的业务;通常Web有异常过滤器,客户端程序可以用上述几种方式;

    • 日志记录:目前来说日志记录应该是作为系统功能的一部分,AOP统一记录是不错的选择;

    • 性能统计:以AOP的思想对方法进行前后监控,从而可以分析其执行性能;

    • 缓存处理:缓存处理,如上次说到MemoryCache,加上AOP拦截应用,系统效率提升不错哦

    • 业务辅变主不变:主业务变,但会不定时增加辅助需求的场景,比如增加用户,后续可能在用户新增成功之后会增加邮件通知、推送新用户信息等功能。

    源码地址:https://github.com/zyq025/DotNetCoreStudyDemo

    总结

    先暂时聊这么多吧,瞌睡啦,小伙伴们晚安喽!!!

    一个被程序搞丑的帅小伙,关注"Code综艺圈",跟我一起学~~~

    相关文章

      网友评论

          本文标题:AOP(面向切面编程)大概了解一下

          本文链接:https://www.haomeiwen.com/subject/qkqpqltx.html