什么是 AOP

Aspect Oriented Programming 的缩写,即面向切面编程,通过 CLR 预编译和运行时动态代理的方式,来对程序中某一种特殊且前后需要相同处理的方法加上一个共同的业务处理,且不影响到原有逻辑,如事务、缓存、日志等等。

AOP 是对 OOP 思想的延续。利用 AOP 可以对不同业务相同逻辑处理的代码封装,进行代码解耦与重用,以提高开发效率。

AOP 示意图:

Aop示意图

编程思想的发展

POP -》 OOP -》 AOP

简单说明下:

  • POP 即 Procedure Oriented Programming,面向过程编程,是一种以过程为中心的编程思想。面向过程无法处理复杂业务。

  • OOP 即 Object Oriented Programming,面向对象编程。特点:封装、继承、多态,万物皆对象。

  • AOP 即 Aspect Oriented Programming,面向切面编程。是对 OOP 的一种补充,在不修改原始类的情况下,给程序动态添加统一功能的一种技术。

综上,其实一般项目中都使用到了这三种思想,三种思想是相互补充的,首先以 OOP 思想为指导来设计程序,然后根据 POP 的流程来实现代码,最后使用 AOP 来提取通用模块。

AOP 是横向的对程序扩展,而 OOP 是以纵向(继承为主)的思想来设计。

NetCore 3.1 中的实现

基于上文 IOC 的使用,接着使用 AOP。

首先通过 Nuget 安装包依赖,安装 AspectCore.Extensions.Autofac

然后在 Startup 中的 ConfigureContainer 方法中,添加语句 完整代码如下:

        public void ConfigureContainer(ContainerBuilder builder)
        {
            builder.RegisterModule<AutofacModule>();

            //配置AOP代理
            builder.RegisterDynamicProxy();
        }

然后新建 LogInterceptorAttribute 日志记录 Aop,

using AspectCore.DynamicProxy;
using System;
using System.Threading.Tasks;

namespace Demo.IocCommon.AopCore
{
    public class LogInterceptorAttribute: AbstractInterceptorAttribute
    {
        public override async Task Invoke(AspectContext context, AspectDelegate next)
        {
            try
            {
                //var logger = context.ServiceProvider.GetService<ILogger>();
                //logger.LogInformation("日志记录");
                Console.WriteLine($"Before method call");
                await next(context);
            }
            catch (Exception)
            {
                Console.WriteLine("method threw an exception!");
                throw;
            }
            finally
            {
                Console.WriteLine("After method call");
            }
        }
    }
}

然后在需要使用的地方,加上该属性,由于是使用的通用注册方式,不是使用的接口的方式,因此方法一定得加 virtual 不然没效果

logAop aopResult

以上就是执行效果图。

aop 的几种使用

该 Aop 都是继承AbstractInterceptorAttribute来实现切面代理,可以通过构造函数进行传值,也可以在 Aop 中从容器中获取某一个 service

常用方式:

  • 属性 AOP:使用这种方式无法配合全局的 AOP 代理设置处理。在需要切面编程的地方自行添加

  • 全局使用和不使用 AOP:

    不使用 AOP 一般是配合全局注册进行使用,当然你也可以在使用属性的时候 再 加上不使用的,这样也会没效果,那肯定没人这样用吧

    不使用的 method or service,在方法名上需要加上 [NonAspect] 属性。这样就不会执行到切面的代码了。

    全局使用的相关代码

        builder.RegisterDynamicProxy(config =>
            {
                //全局使用AOP  这里由于不是使用的接口的方式,需要在要使用AOP的方法上加 virtual 关键字
                config.Interceptors.AddTyped<LogInterceptorAttribute>();
                config.Interceptors.AddServiced<LogInterceptorAttribute>();
                // 带有Service后缀当前方法会被拦截
                config.Interceptors.AddTyped<LogInterceptorAttribute>(method => method.Name.EndsWith("Service"));
                // 使用 通配符 的特定全局拦截器
                config.Interceptors.AddTyped<LogInterceptorAttribute>(Predicates.ForService("*Service"));
    
                //Demo.Data命名空间下的Service不会被代理
                config.NonAspectPredicates.AddNamespace("Demo.Data");
    
                //最后一级为Data的命名空间下的Service不会被代理
                config.NonAspectPredicates.AddNamespace("*.Data");
    
                //ICustomService接口不会被代理
                config.NonAspectPredicates.AddService("ICustomService");
    
                //后缀为Service的接口和类不会被代理
                config.NonAspectPredicates.AddService("*Service");
    
                //命名为FindUser的方法不会被代理
                config.NonAspectPredicates.AddMethod("FindUser");
    
                //后缀为User的方法不会被代理
                config.NonAspectPredicates.AddMethod("*User");
            });
    

事务 AOP 代码

using System;
using System.Threading.Tasks;
using AspectCore.DynamicProxy;
using Microsoft.Extensions.DependencyInjection;

namespace Demo.IocCommon.AopCore
{
 public class DbTransactionInterceptorAttribute : AbstractInterceptorAttribute
    {
        public override async Task Invoke(AspectContext context, AspectDelegate next)
        {
            var dbContext = context.ServiceProvider.GetService<DemoDbContext>();
            //先判断是否已经启用了事务
            if (dbContext.Database.CurrentTransaction == null)
            {
                await dbContext.Database.BeginTransactionAsync();
                try
                {
                    await next(context);
                    dbContext.Database.CommitTransaction();
                }
                catch (Exception ex)
                {
                    dbContext.Database.RollbackTransaction();
                    throw ex;
                }
            }
            else
            {
                await next(context);
            }
        }
    }
}

缓存 AOP 代码

若只想缓存接口返回值,可以只要MVC自带的过滤器来实现。

using System;
using System.Linq;
using System.Threading.Tasks;
using AspectCore.DynamicProxy;
using AspectCore.DynamicProxy.Parameters;
using Microsoft.Extensions.DependencyInjection;

namespace Demo.IocCommon.AopCore
{
 public class CacheInterceptorAttribute : AbstractInterceptorAttribute
    {
        /// <summary>
        /// 缓存秒数
        /// </summary>
        private int ExpireSeconds { get; set; }

        public CacheInterceptorAttribute(int expireSeconds)
        {
            this.ExpireSeconds = expireSeconds;
        }

        public override async Task Invoke(AspectContext context, AspectDelegate next)
        {
            //判断是否是异步方法
            bool isAsync = context.IsAsync();
            //if (context.ImplementationMethod.GetCustomAttribute(typeof(AsyncStateMachineAttribute)) != null)
            //{
            //    isAsync = true;
            //}
            //先判断方法是否有返回值,无就不进行缓存判断
            var methodReturnType = context.GetReturnParameter().Type;
            if (methodReturnType == typeof(void) || methodReturnType == typeof(Task) || methodReturnType == typeof(ValueTask))
            {
                await next(context);
                return;
            }
            var returnType = methodReturnType;
            if (isAsync)
            {
                //取得异步返回的类型
                returnType = returnType.GenericTypeArguments.FirstOrDefault();
            }
            //获取方法参数名
            string param = context.Parameters.ToJson();
            //获取方法名称,也就是缓存key值
            string key = "Methods:" + context.ImplementationMethod.DeclaringType.FullName + "." + context.ImplementationMethod.Name;
            var cache = context.ServiceProvider.GetService<ICacheHelper>();
            //如果缓存有值,那就直接返回缓存值
            if (cache.HashExists(key, param))
            {
                //反射获取缓存值,相当于cache.HashGet<>(key,param)
                var value = typeof(ICacheHelper).GetMethod(nameof(ICacheHelper.HashGet)).MakeGenericMethod(returnType).Invoke(cache, new[] { key, param });
                if (isAsync)
                {
                    //判断是Task还是ValueTask
                    if (methodReturnType == typeof(Task<>).MakeGenericType(returnType))
                    {
                        //反射获取Task<>类型的返回值,相当于Task.FromResult(value)
                        context.ReturnValue = typeof(Task).GetMethod(nameof(Task.FromResult)).MakeGenericMethod(returnType).Invoke(null, new[] { value });
                    }
                    else if (methodReturnType == typeof(ValueTask<>).MakeGenericType(returnType))
                    {
                        //反射构建ValueTask<>类型的返回值,相当于new ValueTask(value)
                        context.ReturnValue = Activator.CreateInstance(typeof(ValueTask<>).MakeGenericType(returnType), value);
                    }
                }
                else
                {
                    context.ReturnValue = value;
                }
                return;
            }
            await next(context);
            object returnValue;
            if (isAsync)
            {
                returnValue = await context.UnwrapAsyncReturnValue();
                //反射获取异步结果的值,相当于(context.ReturnValue as Task<>).Result
                //returnValue = typeof(Task<>).MakeGenericType(returnType).GetProperty(nameof(Task<object>.Result)).GetValue(context.ReturnValue);

            }
            else
            {
                returnValue = context.ReturnValue;
            }
            cache.HashSet(key, param, returnValue);
            if (ExpireSeconds > 0)
            {
                cache.SetExpire(key, TimeSpan.FromSeconds(ExpireSeconds));
            }

        }
    }
}

缓存删除 AOP 代码

在一般类似于 权限信息等 修改时,需要将缓存中的权限信息删除,在下一次请求中时获取新值及设置

using System;
using System.Threading.Tasks;
using AspectCore.DynamicProxy;
using Microsoft.Extensions.DependencyInjection;

namespace Demo.IocCommon.AopCore
{
    public class CacheDeleteInterceptorAttribute : AbstractInterceptorAttribute
    {
        private readonly Type[] _types;
        private readonly string[] _methods;

        /// <summary>
        /// 需传入相同数量的Types跟Methods,同样位置的Type跟Method会组合成一个缓存key,进行删除
        /// </summary>
        /// <param name="types">传入要删除缓存的类</param>
        /// <param name="methods">传入要删除缓存的方法名称,必须与Types数组对应</param>
        public CacheDeleteInterceptorAttribute(Type[] types, string[] methods)
        {
            if (types == null || methods == null)
            {
                throw new Exception("不能传入空值");
            }
            if (types.Length != methods.Length)
            {
                throw new Exception("Types必须跟Methods数量一致");
            }
            _types = types;
            _methods = methods;
        }

        public override async Task Invoke(AspectContext context, AspectDelegate next)
        {
            var cache = context.ServiceProvider.GetService<ICacheHelper>();
            await next(context);
            for (int i = 0; i < _types.Length; i++)
            {
                var type = _types[i];
                var method = _methods[i];
                string key = "Methods:" + type.FullName + "." + method;
                cache.Delete(key);
            }
        }
    }
}

AOP 个人理解

AOP的思想就是在方法的前后加上一段通用逻辑,通过预编译或者动态代理的方式实现的 切面编程,实现代码逻辑类似于下面的代码。

抽象类的形式

// 个人理解
public abstract class MyUnderstand
{
    /// <summary>
    /// 在开始前加上该段逻辑
    /// </summary>
    public void BeforeAop()
    {
        // Do something
    }

    // 必须实现的方法
    public abstract void CurrentMethod();

    /// <summary>
    /// 在结束时加上该段逻辑
    /// </summary>
    public void AfterAop()
    {
        // Do something
    }

    /// <summary>
    /// 每次调用该方法
    /// </summary>
    public void DoMethod()
    {
        BeforeAop();
        CurrentMethod();
        AfterAop();
    }
}

装饰器的形式

  • 装饰器模式主要是为对象动态的添加功能,通过构造函数传入对象。
    /// <summary>
    /// 接口类
    /// </summary>
    public interface IAopDemo
    {
        void AopMethod();
    }
    /// <summary>
    /// 实际实现类
    /// </summary>
    public class RealizeDemo : IAopDemo
    {
        public void AopMethod()
        {
            Console.WriteLine("实现方法");
        }
    }
    /// <summary>
    /// 预编译生成的类  Aop实际调用类
    /// </summary>
    public class AopRealizeDemo : IAopDemo
    {
        private IAopDemo Demo { get; set; }
        public AopRealizeDemo(IAopDemo demo)
        {
            this.Demo = demo;
        }

        /// <summary>
        /// 外部调用方法
        /// </summary>
        public void AopMethod()
        {
            BeforeAop();
            this.Demo.AopMethod();
            AfterAop();
        }
        /// <summary>
        /// 在开始前加上该段逻辑
        /// </summary>
        private void BeforeAop()
        {
            // Do something
        }

        /// <summary>
        /// 在结束时加上该段逻辑
        /// </summary>
        private void AfterAop()
        {
            // Do something
        }
    }

代码地址

代码地址:github 地址

Copyright © levywang123@gmail.com 2020 all right reserved,powered by Gitbook该文章修订时间: 2020-08-12 11:07:20

results matching ""

    No results matching ""