C# AOP编程
文章目录
前言
切面编程(Aspect-Oriented Programming,AOP)是一种编程范式,其实就是通过将横切关注点(如日志记录、事务管理、安全控制等)与业务逻辑分离,把重复的代码提出来以提高代码的模块化、简化维护,并提高代码的复用性。
AOP的基本概念
AOP的核心要素
- 切面(Aspect):包含一组影响多个类的功能,如日志记录、事务管理等。
- 连接点(JoinPoint):程序执行过程中能够插入切面定义的方法调用点。
- 通知(Advice):切面在特定连接点上执行的代码段。
- 切点(Pointcut):定义通知应该应用的哪个连接点上的表达式。
- 目标对象(Target):被通知增强的对象。
AOP的应用场景和优势
应用场景:
就像前言中所说的AOP主要应用还是在日志记录、权限管理(像MVC的filter)、性能检测(比如记录一段代码的执行用时)、事务处理(比如某个操作之前或者之后进行另一个操作)
优势:
- 集中管理某个功能的代码,提高代码复用性,减少代码冗余
- 提高代码可读性和方便后期维护。
AOP面临的挑战
尽管切面编程提供了显著的优势,但在实际应用中也面临着一些挑战和注意事项。特别是在切面的设计与管理、性能开销等方面。不恰当的切面设计可能会导致代码逻辑混乱,增加系统的复杂度。此外,切面的织入过程可能会增加运行时的性能开销,尤其是在大型复杂系统中需要慎重考虑。摘录自:https://worktile.com/kb/p/1807238
AOP的实现方法
AOP的实现方法大致可以分为以下几类:动态代理(Dynamic Proxy)
、IL 编织(IL Weaving)
、使用反射和事件也能实现简单的AOP、静态代理
其中动态代理
和IL编制
用的比较多,使用反射和事件这种方式比较轻量级能实现的功能也比较有限,静态代理用起来也不够灵活所以在实际开发中也很少用。
静态代理
public interface IBase
{
void Run(string str);
}
public class Subklasse : IBase
{
public void Run(string str)
{
Console.WriteLine(str);
}
}
public class Log:IBase
{
private readonly IBase Base;
public Log(IBase @base)
{
Base = @base;
}
public void Start()
{
Console.WriteLine("开始");
}
public void End()
{
Console.WriteLine("执行结束");
}
public void Run(string str)
{
Start();
Base.Run(str);
End();
}
}
static void Main(string[] args)
{
IBase test = new Log(new Subklasse());
test.Run("方法执行");
Console.Read();
}
执行效果
上述的代码是在控制台中进行的,案例中的IBase是基类,Subklasse 是子类方法,Log方法就是采用切面编程的方式在子类方法执行之前和之后进行日志记录。首先会先调用日志记录的带参构造,对Base进行赋值,之后会调用Log中的Run方法,Run方法里就开始调用该方法的Star方法,下一步会调用子类(Subklasse )中的Run方法。最近会调用End方法。在执行方法前和执行方法后让它做一些我们需要的功能就能够很明显的体现出AOP的特征了。
如果有多个类都继承自IBase,我们只要需要记录日志就可以采用这种方式去进行操作,也就可以避免一直重复的写Log中的代码了。
它的执行顺序如下:
在这里给大家扩展一个小知识:
在23种设计模式种,装饰器模式其实也是采用的切面编程,装饰器模式学习文章
动态代理
动态代理我们一般都是采用网上写好的包进行的,C#中AOP编程实现动态代理的包有:
- Castle.Core
- Microsoft.Extensions.DependencyInjection
- Moq
- NSubstitute
- DotNetCore.DI.Interception
- AspectCore Framework
下面我就用Castle.Core给大家举个例子
Castle.Core
首先安装Castle.Core.AsyncInterceptor,这个包对Castle.Core进行了包装,实现了现代化异步的切面,对于现代编程更为适用。
github地址: https://github.com/JSkimming/Castle.Core.AsyncInterceptor
基于接口实现AOP
测试用例
public interface IServeRepository
{
void DoSomeThing(string name);
}
/// <summary>
/// 目标对象
/// </summary>
public class DoRepository : IServeRepository
{
public void DoSomeThing(string name) => Console.WriteLine($"执行输出:{name}");
}
/// <summary>
/// 切面类
/// </summary>
public class LoggerInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
var methodName = invocation.Method.Name;
//通知
Console.WriteLine($"{methodName} 执行前");
//连接点 也就是调用的ProductRepository 中的DoSomeThing
invocation.Proceed();
//通知
Console.WriteLine($"{methodName} 执行完毕");
}
}
class Program
{
static void Main(string[] args)
{
ProxyGenerator generator = new ProxyGenerator();
IInterceptor loggerIntercept = new LoggerInterceptor();
IServeRepository DoRepo = new DoRepository();
IServeRepository proxy = generator.CreateInterfaceProxyWithTarget(DoRepo, loggerIntercept);
proxy.DoSomeThing("测试Castle切面编程");
Console.Read();
}
}
执行效果
基于类实现AOP
测试用例
/// <summary>
/// 切面类
/// </summary>
public class LoggerInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
var methodName = invocation.Method.Name;
//通知
Console.WriteLine($"{methodName} 执行前");
//连接点 也就是调用的ProductRepository 中的DoSomeThing
invocation.Proceed();
//通知
Console.WriteLine($"{methodName} 执行完毕");
}
}
public class DoClass
{
public virtual void DoSomeThing(string name) => Console.WriteLine($"输出:{name}");
}
class Program
{
static void Main(string[] args)
{
ProxyGenerator generator = new ProxyGenerator();
IInterceptor loggerIntercept = new LoggerInterceptor();
DoClass proxy1 = generator.CreateClassProxyWithTarget(new DoClass(), loggerIntercept);
// 使用 CreateClassProxy 泛型方法可以省去实例化代码
DoClass proxy2 = generator.CreateClassProxy<DoClass>(loggerIntercept);
proxy1.DoSomeThing("调用CreateClassProxyWithTarget实现基于类实现AOP");
proxy2.DoSomeThing("调用CreateClassProxy实现基于类实现AOP");
Console.ReadLine();
}
}
执行效果
异步AOP
测试用例
using Castle.DynamicProxy;
public interface IServeRepository
{
Task DoSomeThing(string STR);
Task<string?> GetData(string Key);
}
/// <summary>
/// 目标对象
/// </summary>
public class DoRepository : IServeRepository
{
public Task DoSomeThing(string STR)
{
return Task.Run(() =>
{
Thread.Sleep(1000);
Console.WriteLine($"异步无返回值:{STR}");
});
}
public Task<string?> GetData(string Key)
{
return Task.Run(() =>
{
string rel = string.Empty;
Thread.Sleep(1000);
Console.WriteLine($"获取产品");
Dictionary<string,string> pairs=new Dictionary<string, string>();
pairs.Add("测试1", "qweasdasdqw");
pairs.TryGetValue(Key,out rel);
return rel;
});
}
}
/// <summary>
/// 切面类
/// </summary>
public class LoggerInterceptor : IAsyncInterceptor
{
public void InterceptAsynchronous(IInvocation invocation)
{
var methodName = invocation.Method.Name;
Console.WriteLine($"{methodName} 没有返回值的异步执行前");
invocation.Proceed();
Console.WriteLine($"{methodName} 没有返回值的异步执行完毕");
}
public async void InterceptAsynchronous<TResult>(IInvocation invocation)
{
var methodName = invocation.Method.Name;
Console.WriteLine($"{methodName}带有返回值的异步执行前");
invocation.Proceed();
var task = (Task<TResult>)invocation.ReturnValue;
await task;
Console.WriteLine($"{methodName} 带有返回值的异步执行完毕");
}
public void InterceptSynchronous(IInvocation invocation)
{
var methodName = invocation.Method.Name;
Console.WriteLine($"{methodName} 同步执行前");
invocation.Proceed();
Console.WriteLine($"{methodName} 同步执行完毕");
}
}
class Program
{
static void Main(string[] args)
{
IServeRepository Repo = new DoRepository();
ProxyGenerator generator = new ProxyGenerator();
IAsyncInterceptor loggerIntercept = new LoggerInterceptor();
IServeRepository proxy = generator.CreateInterfaceProxyWithTarget(Repo, loggerIntercept);
proxy.DoSomeThing("测试");
proxy.GetData("测试1");
Console.ReadLine();
}
}
注意这里继承的是IAsyncInterceptor
接口
接口内有三个方法:
- InterceptAsynchronous:截取
没有返回值异步方法
的方法 - InterceptSynchronous:截取
同步
方法 - InterceptAsynchronous:截取
带有返回值
的异步方法
执行效果
Autofac实现AOP
Autofac.Extras.DynamicProxy 是一个 Autofac 扩展,可与 Castle 一起提供 AOP 拦截。
基于接口的拦截器
static void Main(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();
//注册拦截器
builder.RegisterType<LoggerInterceptor>().AsSelf();
//注册要拦截的服务
builder.RegisterType<DoRepository>().AsImplementedInterfaces()
.EnableInterfaceInterceptors() //启用接口拦截
.InterceptedBy(typeof(LoggerInterceptor)); //指定拦截器
IContainer container = builder.Build();
IServeRepository Repo = container.Resolve<IServeRepository>();
Repo.DoSomeThing("测试");
}
基于类的拦截器
ContainerBuilder builder = new ContainerBuilder();
//注册拦截器
builder.RegisterType<LoggerInterceptor>().AsSelf();
//注册要拦截的服务
builder.RegisterType<DoRepository>()
.EnableClassInterceptors() //启用类拦截
.InterceptedBy(typeof(LoggerInterceptor)); //指定拦截器
IContainer container = builder.Build();
DoRepository Repo = container.Resolve<DoRepository>();
Repo.DoSomeThing("测试");
Castle.Core.AsyncInterceptor 中,IAsyncInterceptor
接口并不集成IInterceptor
接口,而 Autofac.Extras.DynamicProxy
是绑定 Castle 的,所以按上面同步拦截的写法是会报错的。 IAsyncInterceptor
提供了 ToInterceptor()
扩展方法来进行类型转换。
public class LoggerInterceptor : IInterceptor
{
readonly LoggerAsyncInterceptor interceptor;
public LoggerInterceptor(LoggerAsyncInterceptor interceptor)
{
this.interceptor = interceptor;
}
public void Intercept(IInvocation invocation)
{
this.interceptor.ToInterceptor().Intercept(invocation);
}
}
public class LoggerAsyncInterceptor : IAsyncInterceptor
{
public void InterceptAsynchronous(IInvocation invocation)
{
//...
}
public void InterceptAsynchronous<TResult>(IInvocation invocation)
{
//...
}
public void InterceptSynchronous(IInvocation invocation)
{
//...
}
}
static void Main(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();
//注册拦截器
builder.RegisterType<LoggerInterceptor>().AsSelf();
builder.RegisterType<LoggerAsyncInterceptor>().AsSelf();
//注册要拦截的服务
builder.RegisterType<ProductRepository>().AsImplementedInterfaces()
.EnableInterfaceInterceptors() //启用接口拦截
.InterceptedBy(typeof(LoggerInterceptor)); //指定拦截器
var container = builder.Build();
IProductRepository productRepo = container.Resolve<IProductRepository>();
productRepo.Get();
}
本段落摘抄自:https://cloud.tencent.com/developer/article/1793476
MVC中间件实现AOP
这里可以参考我之前写的一篇文章:.Net Core 中间件验签
这里切面编程主要体现在请求进入之前进行验签操作这里。
/// <summary>
/// 验签中间件
/// </summary>
public class SignatureMiddleware
{ /// <summary>
/// 用户服务
/// </summary>
public UserService _userService { get; set; }
private readonly RequestDelegate _next;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="next">上下文</param>
/// <param name="userService">用户服务注入</param>
public SignatureMiddleware(RequestDelegate next, UserService userService)
{
_next = next;
_userService = userService;
}
/// <summary>
/// 管道委托
/// </summary>
/// <param name="context">请求</param>
/// <returns></returns>
public async Task Invoke(HttpContext context)
{
if (context.Request.Path.Value.StartsWith("/api/Order"))
{
// 验证签名
var isValidSignature = await ValidateSignatureAsync(context);
if (isValidSignature.Item1)
{
await _next(context);
}
else
{
context.Response.StatusCode = 403;
await context.Response.WriteAsJsonAsync(AlwaysResult.Error(isValidSignature.Item2));
}
}
else
{
await _next(context);
}
}
private async Task<(bool, string)> ValidateSignatureAsync(HttpContext context)
{
context.Request.EnableBuffering();//倒带
string Postbody = string.Empty;
string sign = context.Request.Headers[GlobalContext.SystemConfig.OpenApiSettings.SignName].ParseToString();
string timestamp = context.Request.Headers[GlobalContext.SystemConfig.OpenApiSettings.Timestamp].ParseToString();
string appkey = context.Request.Headers[GlobalContext.SystemConfig.OpenApiSettings.Appkey].ParseToString();
//先根据Appkey查询这个账户的状态:
UserExtend user = await _userService.GetForm(appkey);
if (user != null)
{
context.Request.Body.Position = 0;
var readResult = await context.Request.BodyReader.ReadAsync();
context.Request.BodyReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
Postbody = Encoding.UTF8.GetString(readResult.Buffer.FirstSpan);
string checksign = DESEncrypt.MD5(appkey + timestamp + Postbody + user.F_Account + appkey).ToLower();
if (!checksign.Equals(sign))
{
return (false, "验签失败!");
}
else
{
context.Request.Body.Position = 0;//指针回拨
return (true, "");
}
}
else
{
return (false, "非法签名!");
}
}
}
AOP编程的优缺点
优点
-
模块化横切关注点:AOP允许将通用的行为(如日志、事务、权限等)模块化,使得业务逻辑更为清晰。
-
减少重复代码:不同的模块可以复用相同的横切关注点,从而减少了重复代码。
-
灵活性:AOP允许在运行时动态地增加、删除、修改横切关注点。
-
抽出通用功能(切面),有利于软件设计的模块化,降低软件架构的复杂度。也就是说通用的功能都是一个单独的模块,在项目的主业务里面是看不到这些通用功能的设计代码的。
缺点
-
学习曲线:AOP是一种新的编程范式,需要开发者花费时间去学习和理解。
-
复杂性:AOP增加了系统的复杂性,需要管理AOP的实现和配置。
-
性能开销:在运行时,AOP可能会对性能产生影响,因为它可能会增加额外的处理开销。
总结
AOP是希望能够将通用需求功能从不相关的类当中分离出来,能够使得很多类共享一个行为,一旦发生变化,不必修改很多类,而只需要修改这个行为即可。AOP是使用切面(aspect)将横切关注点模块化,OOP是使用类将状态和行为模块化。在OOP的世界中,程序都是通过类和接口组织的,使用它们实现程序的核心业务逻辑是十分合适。但是对于实现横切关注点(跨越应用程序多个模块的功能需求)则十分吃力比如日志记录,权限验证,异常拦截等。
摘抄自:https://www.cnblogs.com/xlxr45/p/7635297.html