介绍
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,用于将横切关注点(cross-cutting concerns)从业务逻辑中分离出来。
核心思想: 在不修改原有业务代码的前提下,通过”切入”的方式为程序动态添加额外行为。
常见的横切关注点:
- 日志记录
- 性能监控
- 事务管理
- 权限校验
- 缓存处理
- 异常处理
- 重试机制
核心概念:
| 概念 | 说明 |
| Aspect(切面) | 横切关注点的模块化,如”日志切面” |
| Join Point(连接点) | 程序执行过程中可以插入切面的点(如方法调用) |
| Advice(通知/增强) | 切面在连接点执行的具体行为(前置、后置、环绕等) |
| Pointcut(切入点) | 匹配连接点的表达式,定义哪些方法会被增强 |
| Weaving(织入) | 将切面应用到目标对象的过程 |
DispatchProxy 方案
DispatchProxy 是 .NET 内置的动态代理基类,位于 System.Reflection.DispatchProxy,无需任何第三方库。
定义接口:
public interface ICalculator
{
int Add(int a, int b);
int Divide(int a, int b);
}
创建实现类:
public class Calculator : ICalculator
{
public int Add(int a, int b) => a + b;
public int Divide(int a, int b) => a / b;
}
创建接口代理:
public class AopProxy<T> : DispatchProxy where T : class
{
private T _target;
// 工厂方法:创建代理
public static T Create(T target)
{
// Create 返回的是 DispatchProxy 的子类,需要强转
var proxy = Create<T, AopProxy<T>>() as AopProxy<T>;
proxy._target = target;
return proxy as T;
}
// 所有接口方法调用都会路由到这里
protected override object Invoke(MethodInfo targetMethod, object[] args)
{
Console.WriteLine($"[Before] 方法: {targetMethod.Name},参数: [{string.Join(", ", args)}]");
object result = null;
try
{
// 调用真实方法
result = targetMethod.Invoke(_target, args);
Console.WriteLine($"[After] 方法: {targetMethod.Name},返回: {result}");
}
catch (TargetInvocationException ex)
{
Console.WriteLine($"[Error] 方法: {targetMethod.Name},异常: {ex.InnerException?.Message}");
throw ex.InnerException; // 把原始异常抛出去
}
return result;
}
}
使用
var real = new Calculator();
// 通过代理对象调用,所有方法自动被拦截
ICalculator calc = AopProxy<ICalculator>.Create(real);
Console.WriteLine(calc.Add(3, 5));
Console.WriteLine(calc.Divide(10, 2));
// Console.WriteLine(calc.Divide(10, 0)); // 会被拦截到异常处理
Castle.DynamicProxy 方案
Castle.DynamicProxy 是 Castle Project 中的一个核心组件,是一个运行时动态生成代理类(Proxy)的 .NET 库。它可以在不修改原始代码的情况下,拦截方法调用并注入自定义逻辑——这是 AOP(面向切面编程) 在 .NET 生态中最经典的实现方式之一。
安装包:
Install-Package Castle.Core
# 若配合 Autofac 使用
Install-Package Autofac.Extras.DynamicProxy
这里我们使用
定义特性基类:
/// <summary>
/// 拦截器基类特性
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public abstract class InterceptBaseAttribute : Attribute
{
/// <summary>
/// 切面优先级,数值越小优先级越高
/// </summary>
public int Order { get; set; } = 0;
/// <summary>
/// 是否忽略拦截(用于方法级覆盖类级配置)
/// </summary>
public bool Ignore { get; set; } = false;
/// <summary>
/// 执行拦截前逻辑(由子类实现)
/// </summary>
/// <param name="invocation">拦截的调用信息</param>
public abstract void OnBefore(IInvocation invocation);
/// <summary>
/// 执行拦截后逻辑(由子类实现)
/// </summary>
/// <param name="invocation">拦截的调用信息</param>
public abstract void OnAfter(IInvocation invocation);
/// <summary>
/// 拦截器异常处理逻辑(由子类实现)
/// </summary>
/// <param name="invocation">拦截的调用信息</param>
/// <param name="ex">发生的异常</param>
public abstract void OnException(IInvocation invocation, Exception ex);
}
/// <summary>
/// 日志拦截器
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class LogAttribute : InterceptBaseAttribute
{
public override void OnAfter(IInvocation invocation)
{
}
public override void OnBefore(IInvocation invocation)
{
}
public override void OnException(IInvocation invocation, Exception ex)
{
}
}
忽略拦截特性:如果我们在标记了整个类,但是类中的某个方法不想被拦截,就可以使用下面这个特性进行标记。
/// <summary>
/// 免拦截特性
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class NoInterceptAttribute : Attribute
{
}
拦截器基类:(抽象类)
/// <summary>
/// 拦截器基类
/// </summary>
public abstract class BaseInterceptor : IInterceptor
{
/// <summary>
/// 拦截方法执行的核心逻辑
/// </summary>
/// <param name="invocation">拦截的调用信息</param>
public virtual void Intercept(IInvocation invocation)
{
// 检查是否全局忽略
if (HasNoIntercept(invocation))
{
invocation.Proceed();//执行目标方法
return;
}
// 收集并排序所有 InterceptAttribute
var attributes = GetInterceptAttributes(invocation).OrderBy(a => a.Order).ToList();
// 执行 Before
foreach (var attr in attributes)
{
if (!attr.Ignore) attr.OnBefore(invocation);
}
try
{
// 执行目标方法
invocation.Proceed();
// 处理异步方法
if (invocation.ReturnValue is Task task)
{
task.GetAwaiter().GetResult(); // 同步等待,确保 After 在完成后执行
}
}
catch (Exception ex)
{
// 6. 执行异常处理
foreach (var attr in attributes)
{
if (!attr.Ignore) attr.OnException(invocation, ex);
}
throw;
}
// 7. 执行 After
foreach (var attr in attributes.AsEnumerable().Reverse()) // 反向执行,类似栈
{
if (!attr.Ignore) attr.OnAfter(invocation);
}
// 8. 子类扩展点
OnIntercepted(invocation);
}
/// <summary>
/// 子类扩展:拦截完成后的额外操作
/// </summary>
protected virtual void OnIntercepted(IInvocation invocation) { }
/// <summary>
/// 检查方法或类是否有 [NoIntercept]
/// </summary>
protected bool HasNoIntercept(IInvocation invocation)
{
return invocation.Method.GetCustomAttribute<NoInterceptAttribute>() != null || invocation.TargetType!.GetCustomAttribute<NoInterceptAttribute>() != null;
}
/// <summary>
/// 收集类级 + 方法级的 InterceptAttribute
/// </summary>
/// <param name="invocation">调用信息</param>
/// <returns>所有相关的 InterceptBaseAttribute</returns>
protected IEnumerable<InterceptBaseAttribute> GetInterceptAttributes(IInvocation invocation)
{
var methodAttrs = invocation.Method.GetCustomAttributes<InterceptBaseAttribute>();//方法级特性
var classAttrs = invocation.TargetType!.GetCustomAttributes<InterceptBaseAttribute>();//类级特性
return methodAttrs.Concat(classAttrs);
}
}
服务层接口:
public interface IUserService
{
Task<User> GetUserAsync(int id);
void DeleteUser(int id);
void InternalMethod(); // 不想被拦截
}
服务层标记:
// 类级:所有方法默认被 Log 和 Performance 拦截
[Log(Order = 1)]
[Performance(Order = 2)]
public class UserService : IUserService
{
public async Task<User> GetUserAsync(int id)
{
// 自动触发 Log + Performance
return await _db.Users.FindAsync(id);
}
// 方法级:额外添加缓存,或覆盖类级配置
[Log(Ignore = true)] // 此类方法不记录日志
public void DeleteUser(int id)
{
_db.Users.Remove(id);
}
[NoIntercept] // 完全跳过拦截
public void InternalMethod()
{
// 纯内部逻辑,不触发任何切面
}
}
使用
我们最后需要应用到对应的对象方法中,这里我们推荐使用 Autofac 来将其对应的拦截器注入到系统服务中,这样每次就可以直接使用!
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()).ConfigureContainer<ContainerBuilder>(container =>
{
//注册 IHttpContextAccessor(必须在 TokenService 之前)
container.RegisterType<HttpContextAccessor>()
.As<IHttpContextAccessor>()
.SingleInstance();
// 注册拦截器
container.RegisterType<LogInterceptor>();
// 注册服务并启用拦截
//container.RegisterType<UserOperated>().As<IUserOperated>().EnableInterfaceInterceptors().InterceptedBy(typeof(LogInterceptor));
});
异步方法拦截方案
如果我们直接使用上面的方法进行拦截,会出现返回值不匹配的问题,故我们使用下面的方案进行拦截!
安装包:
Install-Package Castle.Core.AsyncInterceptor
定义一个拦截器基类(异步):
/// <summary>
/// 拦截器基类(异步版本)
/// </summary>
public abstract class BaseInterceptorAsync : IAsyncInterceptor
{
/// <summary>
/// 异步方法拦截(有返回值 Task<TResult>)
/// </summary>
/// <typeparam name="TResult">返回值类型</typeparam>
/// <param name="invocation">被调用方法信息</param>
public abstract void InterceptAsynchronous<TResult>(IInvocation invocation);
/// <summary>
/// 异步方法拦截(无返回值 Task)
/// </summary>
/// <param name="invocation">被调用方法信息</param>
public abstract void InterceptAsynchronous(IInvocation invocation);
/// <summary>
/// 同步方法拦截
/// </summary>
/// <param name="invocation">被调用方法信息</param>
public abstract void InterceptSynchronous(IInvocation invocation);
}
/// <summary>
/// 日志拦截器
/// </summary>
public class LogInterceptor : BaseInterceptorAsync
{
public override void InterceptAsynchronous<TResult>(IInvocation invocation)
{
invocation.Proceed();
}
public override void InterceptAsynchronous(IInvocation invocation)
{
invocation.Proceed();
}
public override void InterceptSynchronous(IInvocation invocation)
{
invocation.Proceed();
}
}
这样我们的异步方法拦截器就写法好了!
我们定义一个异步方法拦截器适配器:
/// <summary>
/// 异步方法拦截器适配器
/// </summary>
/// <typeparam name="T"></typeparam>
public class InterceptorAsyncAdapter<T> : IInterceptor where T : IAsyncInterceptor
{
/// <summary>
/// 目标对象拦截适配器
/// </summary>
private readonly T _asyncInterceptor;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="asyncInterceptor">目标对象拦截适配器</param>
public InterceptorAsyncAdapter(T asyncInterceptor)
{
_asyncInterceptor = asyncInterceptor;
}
/// <summary>
/// 拦截器适配器
/// </summary>
/// <param name="invocation">被拦截方法的信息</param>
public void Intercept(IInvocation invocation)
{
var returnType = invocation.Method.ReturnType;
// 异步方法
if (typeof(Task).IsAssignableFrom(returnType))
{
if (returnType.IsGenericType)
{
// Task<TResult>
var resultType = returnType.GetGenericArguments()[0];
// 精确获取泛型方法定义
var method = typeof(IAsyncInterceptor).GetMethods().First(m => m.Name == nameof(IAsyncInterceptor.InterceptAsynchronous) && m.IsGenericMethodDefinition);
// 构造具体泛型方法并调用
var genericMethod = method.MakeGenericMethod(resultType);
genericMethod.Invoke(_asyncInterceptor, new object[] { invocation });
}
else
{
// Task(无返回值)
_asyncInterceptor.InterceptAsynchronous(invocation);
}
}
else
{
// 同步方法
_asyncInterceptor.InterceptSynchronous(invocation);
}
}
}
然后我们在 系统 DI 注入 中写入该代码:
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()).ConfigureContainer<ContainerBuilder>(container =>
{
//注册 IHttpContextAccessor(必须在 TokenService 之前)
container.RegisterType<HttpContextAccessor>()
.As<IHttpContextAccessor>()
.SingleInstance();
container.RegisterType<LogInterceptor>();
// 注册适配器
container.RegisterType<InterceptorAsyncAdapter<LogInterceptor>>();
// 注册服务并启用拦截
container.RegisterType<UserOperated>()
.As<IUserOperated>()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(InterceptorAsyncAdapter<LogInterceptor>));
});
扩展:
这里将会补充一些额外的知识!
IInvocation 参数介绍
我们在拦截器和特性中会发现,方法的参数列表中有一个 IInvocation 类型的参数,下面将会进行解释:
| 属性 | 类型 | 说明 |
| ReturnValue | object | 关键:目标方法执行后的返回值。你可以读取它,也可以替换/包装它。 |
| Method | MethodInfo | 被拦截方法的反射信息(接口上的方法声明)。 |
| MethodInvocationTarget | MethodInfo | 目标实现类上的实际方法(当代理接口时,可能与 Method 不同)。 |
| TargetType | Type | 被代理的实现类类型(如 UserService)。 |
| InvocationTarget | object | 被代理的原始实例(非代理对象)。 |
| Proxy | object | 代理对象本身(即外部调用者持有的那个对象)。 |
| Arguments | object[] | 方法调用时的参数数组。在 OnAfter 中通常只读,用于日志记录。 |
| GenericArguments | Type[] | 泛型方法的类型参数(如 GetUser<T>() 中的 T)。 |
| 方法 | 说明 |
| Proceed() | 调用链中的下一个拦截器或目标方法。在 OnAfter 中不应再调用,否则会导致方法重复执行。 |
| GetArgumentValue(int) | 获取指定索引的参数值。 |
| SetArgumentValue(int, object) | 修改参数值。在 OnAfter 中修改无意义(方法已执行完毕)。 |
拦截器 / 特性中获取某些数据
当我们在 .NET Core Web API 中进行编程时,难免需要获取到 系统注入 DI 的对象或是在项目中该拦截器代码在类库A中,而我们某个必要的类对象在类库B中,但是他们都共同引用了类库C,这时候我们就可以使用类似于
首先我们需要在类库C中定义一个跟类库B中这个类对象的接口文件:
public interface ICalculator
{
int Add(int a, int b);
int Divide(int a, int b);
}
类库B中这个类对象的需要继承该接口:
public class Calculator : ICalculator
{
public int Add(int a, int b) => a + b;
public int Divide(int a, int b) => a / b;
}
定义类库B中这个类对象的相关上下文对象:
public static class CalculatorContext
{
public static readonly AsyncLocal<ICalculator?> Current = new();
}
在程序的主入口设置上下文:
//...
CalculatorContext.Current.Value = calculator;
//...
//或者
var app = builder.Build();
// 从容器中获取并设置上下文
var calculator= app.Services.GetRequiredService<Calculator>();//...
CalculatorContext.Current.Value = calculator;
然后,我们可以定义一个扩展方法来获取他的对象数据:
/// <summary>
/// 获取系统注入服务对象
/// </summary>
/// <param name="invocation">调用函数的信息</param>
/// <param name="propertyName">对象属性</param>
/// <param name="_propertyName">对象属性(备用)</param>
/// <returns>目标对象</returns>
public static T? GetSystemDIServicesContext<T>(this IInvocation invocation, string propertyName, string _propertyName = "")
{
// 优先从目标对象属性获取
var engineProp = invocation.TargetType!.GetProperty(propertyName) ?? invocation.TargetType.GetProperty(_propertyName);
if (engineProp != null)
{
var val = engineProp.GetValue(invocation.InvocationTarget);
if (val is T obj) return obj;
}
return default(T);
}
使用:
var calculator = invocation.GetSystemDIServicesContext<IConfiguration>("CalculatorContext", "Calculator");
if (calculator == null) _engine = CalculatorContext.Current.Value;
![[.NET Core + AOP + Attribute]实现横切面对象方法的额外操作,简化开发,提高效率!-资源刺客](https://images.kodo.cdn.itdka.cn/wp-contents/uploads/2026/05/20260510223906175-科技感ENVI安装教程封面-3.png)

![[学习笔记 Day01]C++基础:简单的程序设计,始于梦想的开始!-资源刺客](http://images.kodo.cdn.itdka.cn/wp-content/uploads/2025/09/20250922171813209.webp)
![[学习笔记 Day08]数据分析与应用:数据聚合与分组运算-资源刺客](https://images.kodo.cdn.itdka.cn/wp-content/uploads/2025/09/20250923154612655.png)
![[学习笔记 Day06]Python 数据分析与应用:数据分析库 Pandas 的使用-资源刺客](http://images.kodo.cdn.itdka.cn/wp-content/uploads/2025/09/20250923154612655.png)
![[学习笔记 Day02]Vue基础:前端造梦,继续干!-资源刺客](http://images.kodo.cdn.itdka.cn/wp-content/uploads/2025/09/20250919193418264.jpeg)

![[学习笔记 Day03]Redis 基础:Redis 设计的优化建议与最佳实践-资源刺客](https://images.kodo.cdn.itdka.cn/wp-contents/uploads/2026/05/20260521215425110-科技感ENVI安装教程封面-8.png)
![[学习笔记 Day02]Redis + OpenResty + Lua 实现多级缓存-资源刺客](https://images.kodo.cdn.itdka.cn/wp-contents/uploads/2026/05/20260521202850344-科技感ENVI安装教程封面-7.png)
![[学习笔记 Day 01] Redis 基础:从入门到缓存、主从、分片集群的深入-资源刺客](https://images.kodo.cdn.itdka.cn/wp-contents/uploads/2026/05/20260520211903179-科技感ENVI安装教程封面-6.png)
![[Windows + Redis]Windows 部署安装 Redis 软件附注册 Windows 服务!-资源刺客](https://images.kodo.cdn.itdka.cn/wp-contents/uploads/2026/05/20260518221407144-科技感ENVI安装教程封面-5.png)

![[自动化 + 手残党专属]宝塔安装AllinSSL证书管理教程-资源刺客](http://images.kodo.cdn.itdka.cn/wp-content/uploads/2025/11/20251112122722716.png)




暂无评论内容