目录
MinimalisticWPF ( .NET6.0 / .NET8.0 )
Ⅰ 为什么需要AOP
AOP可视作对OOP(面向对象编程)的补充,OOP将事物抽象为class,并赋予对象交互行为,这么做的确便于处理复杂业务逻辑,但当业务需求更改,就需要修改class,然而问题正出现在这里,一旦class涉及到继承与多态,修改行为可能会破坏一整条逻辑链……于是,AOP诞生了,它做的事情正如同它的名字那样,在每个功能触发的那个时刻打开一道缺口,也就是“切面”,接着,你可在这个“切面”的前后添加自定义的逻辑,当然,你还可以直接覆盖默认的功能实现,此时,我们扩展/修改了功能,但我们并不需要修改class源码。
Ⅱ AOP常见应用场景
1.安全/日志
例如你是一个小白,但你的业务逻辑中涉及私密数据的读写,那么老师傅会拦截你的数据处理逻辑,在前后添加安全验证、日志记录等逻辑,这个过程不用修改class,,这种场景C#是提供了原生实现的,也是本文着重介绍的。
2.游戏外挂
例如游戏里有个伤害方法Hit,你同样可以做到拦截Hit调用,并覆写为新的逻辑,虽然这需要一些额外的手段,比如你需要反编译以理解原始逻辑,比如你需要一些IL注入修改工具……
Ⅲ C#实现
这里使用WPF(.NET6.0)作为演示
1.实现一个代理中间件
【Invoke】拦截了方法调用,在这个函数中可以转发方法调用给到原始对象并扩展/覆写方法调用
public class ProxyInstance : DispatchProxy
{
//公开无参构造函数是必需的
public ProxyInstance() { }
//被代理的对象实例
internal object? _target;
//此处会拦截方法调用,参数一是方法信息,参数二是本次传给方法的实参
protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
{
//先获取被调用的方法名
var Name = targetMethod?.Name ?? string.Empty;
if (Name == string.Empty) return null;
//以get_开头的方法名是C#属性getter器
//以set_开头的方法名是C#属性setter器
//其它方法名是定义的函数的名字
if (Name.StartsWith("get_"))
{
//…………此处可在get前执行自定义逻辑
//默认行为,可覆写为自定义逻辑
var result = _targetType?.GetMethod(Name)?.Invoke(_target, args);
//…………此处可在get后执行自定义逻辑
return result;
}
else if (Name.StartsWith("set_"))
{
var result = _targetType?.GetMethod(Name)?.Invoke(_target, args);
return result;
}
else
{
var result = _targetType?.GetMethod(Name)?.Invoke(_target, args);
return result;
}
}
2.实现一个扩展方法,用于为对象生成代理中间件
【IProxy】是一个自定义的基础接口,因为C#原生实现只能对接口创建代理,所以这里我自己给了一个接口限制,防止传入奇怪的对象实例
public static T CreateProxy<T>(this T target) where T : IProxy
{
dynamic proxy = DispatchProxy.Create<T, ProxyInstance>() ?? throw new InvalidOperationException();
proxy._target = target;
return proxy;
}
3.将class中需要拦截的属性和方法抽象为接口
(1).假定已有如下class
我们想拦截Name的get和set行为,就需要将这个属性抽象到接口中,方法亦是如此
public class TObj : IPropertyProxy
{
public TObj() { }
public string Name { get; set; } = "defaultValue";
public string GetName()
{
return "defaultResult";
}
}
(2).IPropertyProxy接口实现
这里相当于声明了我们想要拦截Name属性和GetName方法
public interface IPropertyProxy : IProxy
{
string Name { get; set; }
string GetName();
}
4.为对象实例创建代理,实现AOP
TObj obj = new TObj();
IPropertyProxy pro = obj.CreateProxy<IPropertyProxy>();
此时,我们调取 [ pro.Name ] 时,会在先前的 [ Invoke ] 处被拦截 , 而 [ obj.Name ] 就不会被拦截 , 并且如果我们并没有覆写原始逻辑 , 那么 [ pro.Name = "newValue" ] 执行后,[ obj.Name ] 的值也会变成新的值,这就是 [ 代理 ] 的效果 。
Ⅳ 作者在一个WPF类库项目中实现的AOP
MinimalisticWPF ( .NET6.0 / .NET8.0 )
(1).拦截示例
这里依旧是上面定义的TObj类,类库提供了简单的拦截构建语法,于是代理对象pro可以拦截GetName方法调取并转用自定义的逻辑
TObj obj = new TObj();
IPropertyProxy pro = obj.CreateProxy<IPropertyProxy>();
pro.SetMethod(nameof(pro.GetName),
object? (args) => { MessageBox.Show($"before method call"); return null; },
object? (args) => { return "coverage result"; },
object? (args) => { MessageBox.Show($"after method call"); return null; });
(2).获取库
Ⅴ 总结
(1) C#实现AOP的三个重要环节:
1.对需要代理的属性/方法做接口抽象
2.对 Invoke 做细节处理
3.创建代理对象
(2) 注意点:
1.警惕stackoverflow隐患:
例如你覆写的getter逻辑中存在对代理对象的getter调用,那么当你通过代理对象触发getter时会直接stackoverflow
2.性能:
基于方法调用都会经过代理转发给原始对象,因此它通常不是高性能的,对于高频次的调取,需要在原始对象和代理对象间权衡