在业务开发中,经常需要处理一些通用的逻辑,比如说:
在一个方法执行时,打印是否处理成功、耗时的日志 在一个方法执行时,保证其是事物的 在某些类型的方法执行时,增加通用的处理 思路 Advice通过 Advice 注解来申明一个实现,可以在 Pointcut 中使用,或者对其添加 Crosscut 来实现通用的 Advice。
interface AdviceContext<T=object> {
that: T;
method: PropertyKey;
args: ...any[];
}
interface IAdvice<T=object> {
// 在函数调用前执行
beforeCall?(ctx: AdviceContext<T>): Promise<void>;
// 在函数执行成功之后执行
afterReturn?(ctx: AdviceContext<T>, result: any): Promise<void>;
// 在函数抛错之后执行
afterThrow?(ctx: AdviceContext<T>, error: Error): Promise<void>;
// 无论函数成功还是失败都会执行
afterFinally?(ctx: AdviceContext<T>): Promise<void>;
// 拦截函数执行
around?(ctx: AdviceContext<T>, block: () => Promise<any>): Promise<any>;
}
function Advice() {
return function(advice: IAdvice) {
}
}
使用方式
@Advice()
class LogAdvice implement IAdvice {
beforeCall(ctx: AdviceContext) {
console.log('before call');
}
}
执行顺序
beforeCall
around(block start)
function call
around(block end)
afterReturn
afterThrow
afterFinally
Pointcut
在方法上加入 Pointcut 注解来添加 Advice。可以通过添加多个 Pointcut 来实现添加多个 Advice。
interface PointcutOptions { // 默认值 1000 order?: number; }
function Pointcut(advice: IAdvice, options?: PointcutOptions) {
}
使用方式
@ContextProto()
class Foo {
@Prointcut(LogAdvice)
async hello() {
console.log('hello');
}
}
Crosscut
在业务开发中,经常会遇到通用的拦截需求,比如说日志或者异常处理。这种情况下使用 Pointcut 的方式就不够了,手动添加很容易遗漏。
interface CrosscutOptions {
// 默认值 100
order?: number;
}
enum CrosscutType {
// 通过基类来拦截
CLASS = 'CLASS',
// 通过类名 + 方法名正则的方式来拦截
NAME = 'NAME';
// 通过自定义函数的方式来拦截
// 函数必须是静态定义的,动态定义de
CUSTOM = 'CUSTOM';
}
interface CrosscutParam {
type: CrosscutType;
}
interface ClassCrosscutParam extends CrosscutParam {
type: CrosscutType.CLASS;
clazz: new (...args: any[]) => object;
}
interface NameCrosscutParam extends CrosscutParam {
type: CrosscutType.NAME;
className: RegExp;
methodName: RegExp;
}
interface CustomCrossCutParam extends CrosscutParam {
type: CrosscutType.CUSTOM;
callback: (clazz: new (...args: any[]) => object, method: PropertyKey): boolean;
}
function (param: CrosscutParam, options?: CrosscutOptions) {
return function (aspect: IAspect) {}
}
使用方式
@CrossCut({
type: CrosscutType.NAME;
className: /.*Proxy/,
method: /.*/,
})
@Aspect()
class ProxyCallLog {
beforeCall(ctx: AdviceContext) {
console.log('before call');
}
}