-
Notifications
You must be signed in to change notification settings - Fork 50
异步切面
所谓的异步切面,就是为OnEntry/OnSuccess/OnException/OnExit
提供了对应的异步方法OnEntryAsync/OnSuccessAsync/OnExceptionAsync/OnExitAsyc
。
通过继承AsyncMoAttribute
,AsyncMo
,然后就可以重写对应的异步方法了,这两个类型的区别是AsyncMoAttribute
继承了Attribute
,而AsyncMo
没有。或实现IAsyncMo
接口(netstandard 2.1 兼容版本支持)。
public class TestAttribute : AsyncMoAttribute
{
public override async ValueTask OnEntryAsync(MethodContext) { }
public override async ValueTask OnSuccessAsync(MethodContext) { }
public override async ValueTask OnExceptionAsync(MethodContext) { }
public override async ValueTask OnExitAsync(MethodContext) { }
}
同步切面类型和异步切面类型均实现IMo
接口,IMo
接口中包含了所有同步切面方法和异步切面方法的定义,同步切面类型实现IMo
接口后会为异步切面方法增加默认实现,同理,异步切面类型实现IMo
接口后会为同步切面方法增加默认实现。下面以MoAttribute
和AsyncMoAttribute
为例:
public abstract class MoAttribute : RawMoAttribute
{
public override void OnEntry(MethodContext context) { }
// 异步切面方法默认实现,内部直接调用同步切面方法
public sealed override ValueTask OnEntryAsync(MethodContext context)
{
OnEntry(context);
return default;
}
}
public abstract class AsyncMoAttribute : RawMoAttribute
{
public override ValueTask OnEntryAsync(MethodContext context) => default;
// 同步切面方法默认实现,内部直接调用异步切面方法并阻塞获取结果
public sealed override void OnEntry(MethodContext context)
{
OnEntryAsync(context).ConfigureAwait(false).GetAwaiter().GetResult();
}
}
当切面类型应用到方法上后,如果方法是同步方法,那么 Rougamo 将织入同步切面方法的调用,如果方法时异步方法,那么 Rougamo 将织入异步切面方法的调用。
// 切面类型应用到同步方法上
[Test]
public void M() { }
// Rougamo 织入后的代码
[Test]
public void M()
{
var test = new TestAttribute();
var context = new MethodContext();
// 调用同步切面方法
test.OnEntry(context);
$Rougamo_M();
}
// 将切面类型应用到异步方法上
[Test]
public async Task MAsync() { }
// Rougamo 织入后的代码
[Test]
public async Task MAsync()
{
var test = new TestAttribute();
var context = new MethodContext();
// 调用异步切面方法
await test.OnEntryAsync(context);
await $Rougamo_MAsync();
}
通过上面的例子,你应该已经了解同步切面与异步切面之间的关系。同时,你可能也发现了,如果将异步切面类型应用到同步方法上,那么其实无法达到真正的异步效果,内部还是同步阻塞等待异步返回。对于这一情况 Rougamo 也是无可奈何的,目标方法时同步方法,在同步方法中执行异步操作,如果需要异步方法的执行结果,只能同步等待了。但如果你的 AOP 操作可以同时支持同步和异步操作,那么你还可以完全实现IMo
的所有同步异步方法。
假设有以下场景,我想要控制某个方法的分布式并发,要在执行方法前获取分布式锁。这里选择使用 Redis 做分布式锁,SDK 选择 StackExchange.Redis
,此时你会选择创建一个同步切面类型,还是一个异步切面类型?我的建议是,创建一个通用切面类型,因为StackExchange.Redis
对于常用操作分别提供了同步方法和异步方法。
// 以下代码在文本编辑器中编辑,未经验证,经供参考
public class LockAttribute : IMo
{
private const string KEY = "rougamo";
private readonly string _token = Guid.NewGuid().ToString();
private IDatabase _db;
private bool _took;
public void OnEntry(MethodContext context)
{
// 通过 Rougamo.DI 实现依赖注入,详见 https://github.com/inversionhourglass/Rougamo.DI
_db = context.GetRequiredService<IDatabase>();
// 同步切面方法中调用同步方法获取锁
_took = _db.LockTake(KEY, _token, TimeSpan.FromSeconds(3));
// 锁获取失败,拦截方法执行,返回默认值
if (!_took) context.ReplaceReturnValue(this, null);
}
public async ValueTask OnEntryAsync(MethodContext context)
{
_db = context.GetRequiredService<IDatabase>();
// 异步切面方法中调用异步方法获取锁
_took = await _db.LockTakeAsync(KEY, _token, TimeSpan.FromSeconds(3));
// 锁获取失败,拦截方法执行,返回默认值
if (!_took) context.ReplaceReturnValue(this, null);
}
public void OnExit(MethodContext context)
{
if (!_took) return;
_db.LockRelease(KEY, _token);
}
public async ValueTask OnExitAsync(MethodContext context)
{
if (!_took) return;
await _db.LockReleaseAsync(KEY, _token);
}
public void OnSuccess(MethodContext context) { }
public void OnException(MethodContext context) { }
public ValueTask OnSuccessAsync(MethodContext context) => default;
public ValueTask OnExceptionAsync(MethodContext context) => default;
}
如果你的 AOP 操作有同步实现和异步实现,推荐像上面这样针对同步切面方法和异步切面方法分别实现,这样在实际运行时可以达到最佳效果。另外,像上面的例子中,实际只需要实现OnEntry
和OnExit
,直接实现IMo
接口还需要针对OnSuccess
和OnException
实现四个空方法,比较繁琐。此时可以直接继承RawMoAttribute
和RawMo
,然后重写用到的方法即可。
Rougamo 内部定义了多个切面类型方便使用,可以根据自己的需求选择父类/接口,而不必每次都实现最基础的IMo
接口。
IMo # 切面类型基础接口,所有切面类型都需要实现该接口
├── ISyncMo # 同步切面接口,通过默认接口方法对异步切面方法添加了默认实现(仅 netstandard2.1 以上版本可用)
├── IAsyncMo # 异步切面接口,通过默认接口方法对同步切面方法添加了默认实现(仅 netstandard2.1 以上版本可用)
├── RawMoAttribute # 继承 Attribute,可自定义同步和异步切面方法
│ ├── MoAttribute # 仅可自定义同步切面方法,异步切面方法使用调用同步切面方法的默认实现
│ └── AsyncMoAttribute # 仅可自定义异步切面方法,同步切面方法使用调用异步切面方法的默认实现
└── RawMo # 与 RawMoAttribute 功能相同,唯一差别是 RawMo 未继承 Attribute
├── Mo # 与 MoAttribute 功能相同,唯一差别是 Mo 未继承 Attribute
└── AsyncMo # 与 AsyncMoAttribute 功能相同,唯一差别是 AsyncMo 未继承 Attribute