AOP面向切面编程
大约 9 分钟约 2835 字
AOP面向切面编程
简介
Aspect Oriented Programming
AOP面向切面编程:解决面向对象语言的静态问题,能突破类的限制,去动态扩展类的功能。
OOP 擅长处理核心业务逻辑,但有些功能(如日志、权限、缓存、事务)横跨多个模块,这些被称为横切关注点(Cross-Cutting Concerns)。AOP 就是用来优雅地处理这些横切关注点的。
特点
核心概念
| 概念 | 说明 |
|---|---|
| 切面(Aspect) | 横切关注点的模块化,如日志切面、权限切面 |
| 连接点(JoinPoint) | 程序执行的某个点,如方法调用前/后 |
| 切入点(PointCut) | 定义在哪些连接点应用切面 |
| 通知(Advice) | 切面在切入点执行的具体操作 |
| 织入(Weave) | 将切面应用到目标对象的过程 |
通知类型
AOP的多种实现方式
方式1:静态代理(装饰器模式)
/// <summary>
/// 静态代理:通过装饰器模式,在不修改原类的情况下扩展功能
/// 缺点:每个类/方法都需要写一个代理类
/// </summary>
// 业务接口
public interface IUserService
{
string GetUser(int id);
void CreateUser(string name);
}
// 真实业务类
public class UserService : IUserService
{
public string GetUser(int id)
{
Console.WriteLine($"查询用户:{id}");
return $"用户{id}";
}
public void CreateUser(string name)
{
Console.WriteLine($"创建用户:{name}");
}
}
// 装饰器 — 增加日志功能(静态代理)
public class UserServiceLogDecorator : IUserService
{
private readonly IUserService _inner;
public UserServiceLogDecorator(IUserService inner)
{
_inner = inner;
}
public string GetUser(int id)
{
// Before
Console.WriteLine($"[日志] 调用 GetUser({id}) - {DateTime.Now}");
try
{
var result = _inner.GetUser(id);
// AfterReturning
Console.WriteLine($"[日志] GetUser 返回:{result}");
return result;
}
catch (Exception ex)
{
// AfterThrowing
Console.WriteLine($"[日志] GetUser 异常:{ex.Message}");
throw;
}
}
public void CreateUser(string name)
{
Console.WriteLine($"[日志] 调用 CreateUser({name}) - {DateTime.Now}");
_inner.CreateUser(name);
Console.WriteLine($"[日志] CreateUser 完成");
}
}
// 使用
IUserService service = new UserServiceLogDecorator(new UserService());
service.GetUser(1);
service.CreateUser("张三");方式2:动态代理(DispatchProxy)
/// <summary>
/// .NET 内置的 DispatchProxy 实现动态代理
/// 无需为每个类手动编写代理类
/// </summary>
public class LoggingInterceptor<TInterface> : DispatchProxy where TInterface : class
{
private TInterface _target;
private Action<string> _logAction;
public static TInterface Create(TInterface target, Action<string> logAction = null)
{
var proxy = Create<TInterface, LoggingInterceptor<TInterface>>();
((LoggingInterceptor<TInterface>)proxy)._target = target;
((LoggingInterceptor<TInterface>)proxy)._logAction = logAction
?? (msg => Console.WriteLine(msg));
return (TInterface)proxy;
}
protected override object Invoke(MethodInfo targetMethod, object[] args)
{
string methodName = $"{targetMethod.DeclaringType.Name}.{targetMethod.Name}";
string argsStr = string.Join(", ", args);
// Before
_logAction($"[调用] {methodName}({argsStr})");
var stopwatch = Stopwatch.StartNew();
try
{
var result = targetMethod.Invoke(_target, args);
stopwatch.Stop();
// AfterReturning
_logAction($"[返回] {methodName} => {result} ({stopwatch.ElapsedMilliseconds}ms)");
return result;
}
catch (Exception ex)
{
stopwatch.Stop();
// AfterThrowing
_logAction($"[异常] {methodName} => {ex.InnerException?.Message} ({stopwatch.ElapsedMilliseconds}ms)");
throw;
}
}
}
// 使用 — 一个代理类适用所有接口
IUserService userService = LoggingInterceptor<IUserService>.Create(
new UserService(),
msg => Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {msg}")
);
userService.GetUser(42);
userService.CreateUser("李四");方式3:委托嵌套(ASP.NET Core 中间件管道)
/// <summary>
/// ASP.NET Core 管道模型 — 最经典的AOP实现
/// 多个中间件层层嵌套,形成洋葱模型
/// </summary>
public class PipelineBuilder
{
private readonly List<Func<RequestDelegate, RequestDelegate>> _middlewares
= new List<Func<RequestDelegate, RequestDelegate>>();
public PipelineBuilder Use(Func<HttpContext, RequestDelegate, Task> middleware)
{
_middlewares.Add(next => context => middleware(context, next));
return this;
}
public RequestDelegate Build()
{
RequestDelegate app = context =>
{
Console.WriteLine("管道末端:处理请求");
return Task.CompletedTask;
};
// 反向遍历,形成嵌套
for (int i = _middlewares.Count - 1; i >= 0; i--)
{
app = _middlewares[i](app);
}
return app;
}
}
public class HttpContext { }
public delegate Task RequestDelegate(HttpContext context);
// 构建管道
var builder = new PipelineBuilder();
builder.Use(async (context, next) =>
{
Console.WriteLine("认证中间件 — Before");
await next(context);
Console.WriteLine("认证中间件 — After");
});
builder.Use(async (context, next) =>
{
Console.WriteLine("日志中间件 — Before");
await next(context);
Console.WriteLine("日志中间件 — After");
});
builder.Use(async (context, next) =>
{
Console.WriteLine("异常处理中间件 — Before");
await next(context);
Console.WriteLine("异常处理中间件 — After");
});
var app = builder.Build();
app(new HttpContext());
// 输出(洋葱模型):
// 认证中间件 — Before
// 日志中间件 — Before
// 异常处理中间件 — Before
// 管道末端:处理请求
// 异常处理中间件 — After
// 日志中间件 — After
// 认证中间件 — After方式4:特性+反射(ASP.NET Core Filter风格)
/// <summary>
/// 自定义特性 + 反射实现AOP
/// 类似 ASP.NET Core 的 Filter 机制
/// </summary>
// 定义AOP特性
[AttributeUsage(AttributeTargets.Method)]
public class LoggingAttribute : Attribute
{
public string Description { get; }
public LoggingAttribute(string description = "")
{
Description = description;
}
}
[AttributeUsage(AttributeTargets.Method)]
public class TimingAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Method)]
public class CacheAttribute : Attribute
{
public int DurationSeconds { get; }
public CacheAttribute(int durationSeconds = 60)
{
DurationSeconds = durationSeconds;
}
}
// AOP 执行引擎
public static class AopExecutor
{
private static readonly Dictionary<string, (object Result, DateTime ExpireTime)> _cache
= new Dictionary<string, (object, DateTime)>();
public static T Execute<T>(Func<T> method, MethodInfo methodInfo, object[] args = null)
{
string methodKey = $"{methodInfo.DeclaringType.Name}.{methodInfo.Name}";
// 检查缓存特性
var cacheAttr = methodInfo.GetCustomAttribute<CacheAttribute>();
if (cacheAttr != null && _cache.TryGetValue(methodKey, out var cached))
{
if (DateTime.Now < cached.ExpireTime)
{
Console.WriteLine($"[缓存命中] {methodKey}");
return (T)cached.Result;
}
_cache.Remove(methodKey);
}
// 检查日志特性 — Before
var logAttr = methodInfo.GetCustomAttribute<LoggingAttribute>();
if (logAttr != null)
Console.WriteLine($"[日志] 开始执行:{logAttr.Description} ({methodKey})");
// 检查计时特性
var timingAttr = methodInfo.GetCustomAttribute<TimingAttribute>();
Stopwatch sw = null;
if (timingAttr != null)
{
sw = Stopwatch.StartNew();
}
try
{
T result = method();
// 计时 — AfterReturning
sw?.Stop();
if (timingAttr != null)
Console.WriteLine($"[计时] {methodKey} 耗时 {sw.ElapsedMilliseconds}ms");
// 日志 — AfterReturning
if (logAttr != null)
Console.WriteLine($"[日志] 执行完成:{logAttr.Description} => {result}");
// 缓存结果
if (cacheAttr != null)
{
_cache[methodKey] = (result, DateTime.Now.AddSeconds(cacheAttr.DurationSeconds));
Console.WriteLine($"[缓存] 已缓存 {methodKey},{cacheAttr.DurationSeconds}秒");
}
return result;
}
catch (Exception ex)
{
sw?.Stop();
if (logAttr != null)
Console.WriteLine($"[日志] 执行异常:{logAttr.Description} => {ex.Message}");
throw;
}
}
}
// 业务类使用AOP特性
public class ProductController
{
[Logging("获取商品详情")]
[Timing]
[Cache(30)]
public string GetProduct(int id)
{
Thread.Sleep(100); // 模拟数据库查询
return $"商品{id}的详细信息";
}
[Logging("创建订单")]
public string CreateOrder(string productName)
{
return $"订单已创建:{productName}";
}
}
// 使用
var ctrl = new ProductController();
var method1 = typeof(ProductController).GetMethod("GetProduct");
// 第一次调用 — 执行方法并缓存
var result1 = AopExecutor.Execute(() => ctrl.GetProduct(1), method1);
// 第二次调用 — 命中缓存
var result2 = AopExecutor.Execute(() => ctrl.GetProduct(1), method1);方式5:Castle DynamicProxy(第三方库)
# 安装 NuGet 包
dotnet add package Castle.Core/// <summary>
/// Castle DynamicProxy — .NET 最流行的动态代理库
/// Autofac、NHibernate 等都基于它实现AOP
/// </summary>
using Castle.DynamicProxy;
public class LoggingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
string methodName = $"{invocation.TargetType.Name}.{invocation.Method.Name}";
string args = string.Join(", ", invocation.Arguments);
Console.WriteLine($"[Before] {methodName}({args})");
var sw = Stopwatch.StartNew();
try
{
// 调用原方法
invocation.Proceed();
sw.Stop();
Console.WriteLine($"[After] {methodName} => {invocation.ReturnValue} ({sw.ElapsedMilliseconds}ms)");
}
catch (Exception ex)
{
sw.Stop();
Console.WriteLine($"[Exception] {methodName} => {ex.Message} ({sw.ElapsedMilliseconds}ms)");
throw;
}
}
}
// 使用
var proxyGenerator = new ProxyGenerator();
var userService = proxyGenerator.CreateInterfaceProxyWithTarget<IUserService>(
new UserService(),
new LoggingInterceptor()
);
userService.GetUser(1);
userService.CreateUser("王五");ASP.NET Core 中的 AOP 实战
全局异常处理中间件
/// <summary>
/// 全局异常处理 — AOP 横切关注点的典型应用
/// </summary>
public class ExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context); // 执行后续管道
}
catch (Exception ex)
{
_logger.LogError(ex, "全局异常捕获");
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = exception switch
{
ArgumentException => 400,
UnauthorizedAccessException => 401,
KeyNotFoundException => 404,
_ => 500
};
var response = new
{
code = context.Response.StatusCode,
message = exception.Message
};
return context.Response.WriteAsJsonAsync(response);
}
}
// 注册中间件
app.UseMiddleware<ExceptionHandlingMiddleware>();自定义 ActionFilter — 方法级AOP
/// <summary>
/// ASP.NET Core ActionFilter — 最常用的方法级AOP
/// </summary>
public class AuditLogFilter : IActionFilter
{
private readonly ILogger<AuditLogFilter> _logger;
private Stopwatch _stopwatch;
public AuditLogFilter(ILogger<AuditLogFilter> logger)
{
_logger = logger;
}
// Before
public void OnActionExecuting(ActionExecutingContext context)
{
_stopwatch = Stopwatch.StartNew();
var action = $"{context.RouteData.Values["controller"]}.{context.RouteData.Values["action"]}";
var args = System.Text.Json.JsonSerializer.Serialize(context.ActionArguments);
_logger.LogInformation("请求开始:{Action},参数:{Args}", action, args);
}
// After
public void OnActionExecuted(ActionExecutedContext context)
{
_stopwatch.Stop();
var action = $"{context.RouteData.Values["controller"]}.{context.RouteData.Values["action"]}";
_logger.LogInformation("请求完成:{Action},耗时:{Elapsed}ms",
action, _stopwatch.ElapsedMilliseconds);
}
}
// 注册为全局Filter
builder.Services.AddControllers(options =>
{
options.Filters.Add<AuditLogFilter>();
});AOP 的选择指南
| 实现方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 装饰器模式 | 简单场景,少量类 | 无第三方依赖,直观 | 每个类都要写代理 |
| DispatchProxy | 需要通用代理 | .NET 内置,无需依赖 | 只能代理接口 |
| 委托嵌套 | 管道式处理 | 灵活,ASP.NET Core 风格 | 仅适合管道场景 |
| 特性+反射 | 方法级控制 | 声明式,直观 | 性能有反射开销 |
| Castle DynamicProxy | 企业级项目 | 功能强大,社区成熟 | 需要第三方包 |
| ASP.NET Core Filter | Web API 项目 | 框架集成,开箱即用 | 仅限 Controller |
优点
缺点
总结
AOP 是 OOP 的强力补充,核心价值在于将横切关注点(日志、权限、缓存、事务、异常处理)从业务代码中剥离出来。在 .NET 生态中,ASP.NET Core 的 Filter 和中间件已经内置了 AOP 能力,日常开发中优先使用框架提供的方式。
关键知识点
- 模式不是目标,降低耦合和控制变化才是目标。
- 先找变化点、稳定点和协作边界,再决定是否引入模式。
- 同一个模式在不同规模下的收益和代价差异很大。
项目落地视角
- 优先画出参与对象、依赖方向和调用链,再落到代码。
- 把模式放到一个真实场景里,比如支付、规则引擎、工作流或插件扩展。
- 配合单元测试或契约测试,保证重构后的行为没有漂移。
常见误区
- 为了看起来“高级”而套模式。
- 把简单问题拆成过多抽象层,导致阅读和排障都变难。
- 只会背 UML,不会解释为什么这里需要这个模式。
进阶路线
- 继续关注模式之间的组合用法,而不是孤立记忆。
- 从业务建模、演进策略和团队协作角度看模式的适用性。
- 把模式结论沉淀为项目模板、基类或约束文档。
适用场景
- 当你准备把《AOP面向切面编程》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在业务规则频繁变化、分支增多或对象协作复杂时引入。
- 当你希望提高扩展性,但又不想把系统拆得过度抽象时,这类主题很有参考价值。
落地建议
- 先识别变化点,再决定是否引入模式,而不是反过来套模板。
- 优先为模式的边界、依赖和调用路径画出简单结构图。
- 把模式落到一个明确场景,例如支付、规则计算、插件扩展或工作流。
排错清单
- 检查抽象层是否过多,导致调用路径和责任不清晰。
- 确认引入模式后是否真的减少了条件分支和重复代码。
- 警惕“为了模式而模式”,尤其是在简单业务里。
复盘问题
- 如果把《AOP面向切面编程》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《AOP面向切面编程》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《AOP面向切面编程》最大的收益和代价分别是什么?
