AOP --- 面向切面编程

引言

AOP

AOP(Aspect-Oriented Programming) 编程思想是一种面向切面编程的编程范式。在日常的软件开发中,我们经常会遇到一些横切关注点(cross-cutting concerns),如日志记录事务处理权限控制异常处理等。这些横切关注点可能会存在于程序的多个模块中,使得程序的不同模块之间存在较强的耦合性,从而影响了程序的可维护性和可扩展性。AOP编程思想的目的就是将这些横切关注点从程序的业务逻辑中剥离出来,并将其模块化处理,从而提高程序的可维护性和可扩展性。

切面

在AOP编程思想中,切面(aspect)是指横切关注点的抽象概念,它通常用一个类或一个模块来表示。切面通过将横切关注点的代码封装到独立的模块中,使得这些代码可以在程序的不同模块之间共享和复用。切面通过定义切点(pointcut)和通知(advice)来实现对横切关注点的处理。

切点

切点定义了哪些代码片段需要被处理,通知则定义了在切点处执行的处理逻辑。在AOP编程中,通知可以分为前置通知(before advice)、后置通知(after advice)、环绕通知(around advice)等不同类型,具体使用哪种类型的通知取决于需要实现的功能。

AOP 的优缺点

优点

  1. 解耦:AOP编程将横切关注点从业务逻辑中分离出来,使得程序各个模块之间的依赖关系降低,从而实现了解耦。
  2. 复用:AOP编程将横切关注点封装到独立的模块中,使得这些代码可以在程序的不同模块之间共享和复用。
  3. 可维护性:AOP编程将横切关注点的处理逻辑从业务逻辑中分离出来,使得程序的各个模块更加简单,易于维护。
  4. 可扩展性:AOP编程通过切面的定义和配置,可以很容易地扩展系统的功能,而不需要对原有的业务逻辑进行修改。

缺点

  1. 增加复杂性:AOP编程将程序的逻辑分散到多个模块中,增加了程序的复杂性,对于一些小规模的项目可能会显得过于复杂。
  2. 难以调试:由于横切关注点的代码可能被多个模块共享,所以在调试时可能会比较困难。

AOP编程思想增加了程序的复杂性,难以调试等。但总体来说,AOP编程思想对于大型软件系统的开发和维护是非常有用的。它可以使得程序的结构更加清晰、易于维护和扩展,同时也可以提高程序的重用性和可测试性。使用AOP编程思想可以让开发人员更加专注于业务逻辑的实现,而将横切关注点的处理交给AOP框架去处理,从而提高开发效率和代码质量。

C# 利用AutoFac实现简单AOP

接下来我们借助反射Attribute,三方容器AutoFac,实现一个简单AOP:

1. 创建动态代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
internal class DynamicProxy<T> : DispatchProxy
{
public T? decorated { get; set; }//目标类

public Action<object?[]?>? _BeforeAction { get; set; } // 动作之前执行

public Action<object?[]?, object>? _AfterAction { get; set; } // 动作之后执行

public Action<Exception>? _CatchExceptionAction { get; set; } // 捕获异常之后执行

protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
{
Exception exception = null;

Before(args);

object result = null;
try
{
//调用实际目标对象的方法
result = targetMethod?.Invoke(decorated, args);
}
catch (Exception ex)
{
exception = ex;
}

After(args, result);

//调用完执行方法后的委托,如果有异常,抛出异常
if (exception != null)
{
CatchException(exception);
}
return result;
}

/// <summary>
/// 创建代理实例
/// </summary>
/// <param name="decorated">代理的接口类型</param>
/// <param name="beforeAction">方法执行前执行的事件</param>
/// <param name="afterAction">方法执行后执行的事件</param>
/// <param name="catchException">异常捕获后执行的事件</param>
/// <returns></returns>
public T Create(T decorated, Action<object?[]?> beforeAction, Action<object?[]?, object> afterAction, Action<Exception> catchException)
{
// 调用DispatchProxy 的Create 创建一个新的T
object proxy = Create<T, DynamicProxy<T>>();

DynamicProxy<T> proxyDecorator = (DynamicProxy<T>)proxy;

proxyDecorator.decorated = decorated;

//把自定义的方法委托给代理类
proxyDecorator._AfterAction = afterAction;

proxyDecorator._BeforeAction = beforeAction;

proxyDecorator._CatchExceptionAction = catchException;

return (T)proxy;
}

private void Before(object?[]? args)
{
try
{
_BeforeAction.Invoke(args);
}
catch (Exception ex)
{
Console.WriteLine($"执行之前异常:{ex.Message}");
}
}

private void After(object?[]? args, object? result)
{
try
{
_AfterAction.Invoke(args, result);
}
catch (Exception ex)
{
Console.WriteLine($"执行之后异常:{ex.Message}");
}
}

private void CatchException(Exception ex)
{
_CatchExceptionAction(ex);
}
}

2. 创建拦截器属性

标记AOP切点的Attribute–InterceptAttribut(拦截器属性)

1
2
3
4
5
6
7
8
9
10
11
12
/// <summary>
/// 自定义拦截器特性
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
internal class InterceptAttribut : Attribute
{
public Type Type { get; set; }
public InterceptAttribut(Type type)
{
this.Type = type;
}
}

3. 创建动态代理工厂类

它是泛型工厂,用于创建不同类型的代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class DynamicProxyFactory
{
/// <summary>
/// 创建代理实例
/// </summary>
/// <param name="decorated">代理的接口类型</param>
/// <returns></returns>
public static T Create<T>()
{
var decorated = ServiceHelp.GetService<T>(typeof(T));

var type = decorated.GetType();

var interceptAttribut = type.GetCustomAttribute<InterceptAttribut>();

var interceptor = ServiceHelp.GetService<IInterceptor>(interceptAttribut.Type);
//创建代理类
var proxy = new DynamicProxy<T>().Create(decorated, interceptor.BeforeExecuted, interceptor.AfterExecuted, interceptor.CatchException);

return proxy;
}
}

4. 创建ServiceHelp

ServiceHelp用于获取实例,其核心就是以Autofac这个IOC容器去注册及获取服务.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
internal class ServiceHelp
{
//实例化Autofac容器
private static ContainerBuilder builder = new ContainerBuilder();

public static IContainer? serviceProvider { get; set; }

public static void BuildServiceProvider()
{
//注册InstanceModule组件
builder.RegisterModule<InstanceModule>();
//创建容器
serviceProvider = builder.Build();
}

internal static T GetService<T>(Type serviceType)
{
if (serviceProvider.IsRegistered(serviceType))
{
return (T)serviceProvider.Resolve(serviceType);
}
return default(T);
}
}

5. 创建AOP切面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/// <summary>
/// 自定义拦截器接口
/// </summary>
interface IInterceptor
{
/// <summary>
/// 执行前
/// </summary>
/// <param name="args"></param>
void BeforeExecuted(object?[]? args);
/// <summary>
/// 执行后
/// </summary>
/// <param name="args">参数</param>
/// <param name="result">返回值</param>
void AfterExecuted(object?[]? args, object? result);

void CatchException(Exception ex);

}
/// <summary>
/// 方法执行的切面
/// </summary>
class ExecutAOP : IInterceptor
{
public void AfterExecuted(object?[]? args, object? result)
{
Console.WriteLine($"拦截器中方法后执行~~~~");
}

public void BeforeExecuted(object?[]? args)
{
if (args != null && args.Length > 0 && args[0] == null)
throw new Exception("参数错误");
Console.WriteLine($"拦截器中方法前执行~~~~");

}
public void CatchException(Exception ex)
{
Console.WriteLine($"拦截器中捕获到了异常~~~~\r\n{ex.InnerException.Message}");
}
}

6. 创建测试模型

设定一个业务场景,有一个交通工具ITransportation有两个公共方法 Run()Eat(),以及其实现Hours

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface ITransportation
{
public void Run();

public void Eat(string food);
}

[InterceptAttribut(typeof(ExecutAOP))]
class Horse : ITransportation
{
public void Eat(string food)
{
Console.WriteLine($"小马儿吃了{food}~~~~~~~~~~~~");
}

public void Run()
{
Console.WriteLine("马儿马儿快马加鞭~~~~~~~~~~~~");
throw new Exception("小马儿掉沟里了");
}
}

7.测试场景

  1. 测试切面方法执行前和执行后
1
2
3
4
5
6
7
8
9
10
static void Main(string[] args)
{
ServiceHelp.BuildServiceProvider();

var hours = DynamicProxyFactory.Create<ITransportation>();

hours.Eat("新鲜牧草");

Console.ReadLine();
}

输出:

image.png

从控制台输出可以看到,Horse再执行Eat方法前和方法后都被拦截,并输出了预期结果

  1. 测试切面方法异常处理
1
2
3
4
5
6
7
8
9
10
static void Main(string[] args)
{
ServiceHelp.BuildServiceProvider();

var hours = DynamicProxyFactory.Create<ITransportation>();

hours.Run();

Console.ReadLine();
}

输出:

image.png
HorseRun方法中我们主动抛出了异常,然后从控制台输出可以看到,异常也被拦截器拦截,并做了处理输出拦截器中捕获到了异常及异常信息。

总结

总之,AOP编程思想是一种非常有用的编程范式,它可以使得程序的结构更加清晰、易于维护和扩展,同时也可以提高程序的重用性和可测试性。在日常的软件开发中,我们可以使用AOP框架来实现AOP编程思想,从而提高开发效率和代码质量。