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

前言

上一篇在聊MemoryCache的時候,用到了Autofac提供的攔截器進行面向切面編程,很明顯能體會到其優勢,既然涉及到了,那就趁熱打鐵,一起來探探面向切面編程。

正文

1. 概述

在軟體業,AOPAspect Oriented Programming的縮寫,意為:面向切面編程,通過預編譯方式和運行期間動態代理實現程式功能統一維護的一種技術。AOP是OOP(面向對象程式設計)的延續,是軟體開發中的一個熱點,是函數式編程的一種衍生范型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。

—來自百度百科

總結優點

  • 對業務邏輯的各個部分進行隔離,業務之間耦合度降低;
  • 提高程式的可重用性,同時程式更容易維護;
  • 提高開發效率,不用花大量的時間在業務中增加程式碼,還能降低風險;

其實AOP的本質就是動態代理,何為動態代理呢?

動態代理就是在程式運行時,創建目標對象的代理對象,並對目標對象中的方法進行功能性增強的一種技術。通俗一點來說就是在運行期間對方法的攔截,在方法執行前後進行額外的業務處理,從而在不嵌入原有程式碼邏輯情況下就能增強被攔截方法的業務能力。

理論先到這,一起來看看用程式碼怎麼實現吧?

2. 實踐檢驗真理(到底優不優秀)

先來一個控制台項目,什麼都沒有,從頭開始擼程式碼,先來看看項目結構:

圖片

老案例了,還是假裝在進行用戶維護,模擬對用戶進行增刪改查。這次就直接上程式碼啦啊,根據項目結構依次看看程式碼:

  • 在AopModel中增加User.cs

    public class User
    {
      public string Name { get; set; }
      public int Age { get; set; }
    }
    
  • 在AopService中增加IUser.cs和User.cs

    IUserService.cs

    public interface IUserService
    {
      bool AddUser(User user);
    }
    

    UserService.cs

    public class UserService : IUserService
    {
      public bool AddUser(User user)
      {
          Console.WriteLine("用戶添加成功");
          return true;
      }
    }
    
  • Main方法

    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();
      }
    }
    

這樣項目就正常運行啦,這個就不用我截圖了吧,小夥伴都會吧。

項目運行正常,但需要加一個需求:用戶增加成功之後進行郵件發送通知。

目前有兩種解決方案

  • 直接在增加用戶方法中添加加發送郵件邏輯(相信很多小夥伴是這樣做的,見效快,還簡單);

    如果頻繁在增加用戶前或後添加新需求呢,還繼續加嗎,可能到最後增加用戶的方法變得很複雜,後期也不好維護;如果要去掉某一個功能,又得把程式碼改回來,作為程式設計師是不是又要和產品同事搞架啦(文明人,不動手);當然,如果需求固定,這種方式也不錯。

  • 面向切面實現,即在不影響原有程式碼邏輯的情況,動態的對方法進行攔截,在方法執行前或後添加業務即可。

項目中引入AOP(面向切面編程)思想
  • 原始動態代理實現;

    先來加個代理類,如下:

    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;
          }
      }
    }
    

    然後在Main函數直接使用即可,如下:

    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();
      }
    }
    

    動態代理就實現需求功能啦,可以在用戶增加前或後都進行相關需求處理,運行看效果:

    圖片

  • 第三方庫Castle.Core封裝的美滋滋;

    通過上面演示,原生的動態代理實現面向切面編程顯得相對麻煩,比如強制轉換、傳遞類型等操作;常用的Castle.Core將動態代理進一步封裝,使用就相對方便點啦;這次定義一個攔截器即可:

    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("增加用戶後執行業務");
          }
      }
    }
    

    Main函數中使用攔截器即可,如下:

    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();
          }
      }
    }
    

    運行效果如下:

    圖片

  • Autofac集成了Castle.Core用著也挺不錯;

    Autofac已經集成了Castle.Core啦,在聊MemoryCache的時候就已經用到,使用比較簡單,可以通過特性標註的方式就可以針對某個類或介面的方法進行攔截加強,詳情請參考這篇文章(因MemoryCache鬧了個笑話)。

3. 應用場景

AOP思想是很優秀,但總不能處處都得用吧,需根據業務來評估是否需要;常用應用場景大概有以下幾個:

  • 安全控制:通常在Web開發的時候,會使用過濾器或攔截器進行許可權驗證,這也是AOP思想的落地;對於客戶端程式,通過上述演示的幾種方式可以輕鬆實現許可權的統一管理和驗證;

  • 事務處理:相信小夥伴都寫過資料庫事務程式碼吧,常規做法就是在業務方法中直接開啟事務,執行完成,提交或回滾即可,AOP思想也能很好處理這種情況;

  • 異常處理:統一的異常處理是最好的選擇,除非是特殊的業務;通常Web有異常過濾器,客戶端程式可以用上述幾種方式;

  • 日誌記錄:目前來說日誌記錄應該是作為系統功能的一部分,AOP統一記錄是不錯的選擇;

  • 性能統計:以AOP的思想對方法進行前後監控,從而可以分析其執行性能;

  • 快取處理:快取處理,如上次說到MemoryCache,加上AOP攔截應用,系統效率提升不錯哦

  • 業務輔變主不變:主業務變,但會不定時增加輔助需求的場景,比如增加用戶,後續可能在用戶新增成功之後會增加郵件通知、推送新用戶資訊等功能。

源碼地址://github.com/zyq025/DotNetCoreStudyDemo

總結

先暫時聊這麼多吧,瞌睡啦,小夥伴們晚安嘍!!!

一個被程式搞丑的帥小伙,關注”Code綜藝圈”,跟我一起學~~~