[Typescript]用装饰器封装Express服务(4)-AOP实现

196 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情

设计

Aop,既是面向切面编程。目前整个AOP的设计如下:

1.分别实现前置处理(@before)和后置处理(@after)

2.匹配规则为方法名称匹配,待完善

3.可实现多个Aop处理,添加index排序参数,如若不传index参数,则使用默认参数进行排序

4.仅对扫描到的component的方法,进行aop匹配

实现

首先,定义类装饰器EnableAspect,标识项目开启Aop功能

@EnableAspect
class App(){

}

而此装饰器的作用,是在application上下文中,将是否启用aop切面参数标记为true

export const EnableAspect = (): ClassDecorator => (targetClass: any) => {
    console.log('========================= Enable Aspect============================')
    application.isEnableAspect = true
}

以便在后续的初始化工作中,加载代理aop方法到目标函数中 其次,是根据@before 和 @after 装饰器收集各个切面方法

export const Aspect = (): ClassDecorator => {
    return (TargetClass: any) => {
        application.aspectManager.aspectClassMap.set(TargetClass.name, proxify(new TargetClass()))
    }
}

export const AspectMethodKey = 'AspectMethod'

function createAspect(type: string) {
    /**
     * @param exp:切点表达式<pr>
     * @param index:顺序
     */
    return (param: { exp: string, index?: number }): MethodDecorator => {
        // 所属类,被注解的方法,方法描述符
        return (target: any, methodName: string, methodDecorator: PropertyDescriptor) => {
            
            let fn = target[methodName]
            const className = target.constructor.name
            const aspectInfo: AspectInfo = {
                aspectExp: param.exp,
                aspectFn: fn,
                type,
                className,
                target,
                index: param.index || 100
            }
            let olds = Reflect.getMetadata(AspectMethodKey, methodDecorator.value) || []
            // console.log(olds)
            let news = [...olds, aspectInfo]
            // console.log(news)
            // 在此方法上添加aop信息
            Reflect.defineMetadata(AspectMethodKey, news, methodDecorator.value);

        }
    }
}

export const Before = createAspect('before')
export const After = createAspect('after')

然后,是创建一个AspectManager类,在该类中,定义实现切面应做的各个工作,如注册切面以及,实现代理切面方法等

export interface AspectInfo {
    aspectExp: string
    aspectFn: any
    type: string
    className: string
    target: any,
    index: number
}

export interface MethodAspectsInfo {
    before?: AspectInfo[]
    after?: AspectInfo[]
}

export class AspectManager {
    aspectClassMap: Map<string, any> = new Map()
    aspectMethodMap: Map<string, AspectInfo[]> = new Map()

    public registerAspect() {
        let _this = this
        this.aspectClassMap.forEach((instance: any, key: string, map: Map<string, any>) => {
            // 实例属性
            const proto = Object.getPrototypeOf(instance);
            // 方法数组
            const functionNameArr = Object.getOwnPropertyNames(proto).filter(
                n => n !== 'constructor' && typeof proto[n] === 'function',
            );
            functionNameArr.forEach(functionName => {
                let aspectInfos: AspectInfo[] = Reflect.getMetadata(AspectMethodKey, proto[functionName]);
                if (!aspectInfos) return;
                aspectInfos.forEach(aspectInfo => {
                    let as = _this.aspectMethodMap.get(aspectInfo.aspectExp)
                    if (!as) {
                        as = []
                    }
                    as.push(aspectInfo)
                    _this.aspectMethodMap.set(aspectInfo.aspectExp, as)
                })
            })
        })
    }

    public invoke(instance: any, methodName: string): (...args: any[]) => Promise<any> {
        // 1.根据方法名获取切面执行数组 befores afters
        let isAsyncFunction = util.types.isAsyncFunction(instance[methodName])
        let original = instance[methodName]
        let as = this.getMethodAspectsInfo(instance, methodName)
        if (as.before.length <= 0 && as.after.length <= 0) {
            return null
        }
        let newFn = async function (...args: any[]) {
            // console.log('arguments:' + arguments)
            // before
            for (let b in as.before) {
                await as.before[b].aspectFn(...args)
            }
            // 原方法
            let result = await original.apply(this, arguments);
            // after
            for (let b in as.after) {
                await as.after[b].aspectFn({ originalArgs: args, result })
            }
            return result
        }
        return newFn;
    }

    public getMethodAspectsInfo(instance: any, methodName: string) {
        let as: MethodAspectsInfo = {
            before: new Array(),
            after: new Array()
        }
        application.aspectManager.aspectMethodMap.forEach((value, key, map) => {
            // todo:方法匹配优化
            //console.log(`=========================methodName:${methodName} --- key:${key} =========================`)
            if (methodName == key) {
                // console.log('========================= 符合条件 =========================')
                for (let i in value) {
                    let ai = value[i]
                    if (ai.type == 'before') {
                        as.before.push(ai)
                    }
                    if (ai.type == 'after') {
                        as.after.push(ai)
                    }
                }
            }
        })
        // 根据index排序
        // 从小到大的排序 
        as.after.sort((Acomponent, Bcomponent) => {
            return Acomponent.index - Bcomponent.index;
        });
        as.before.sort((Acomponent, Bcomponent) => {
            return Acomponent.index - Bcomponent.index;
        });
        return as;
    }
}

在application对象实例化时,创建AspectManager实例,并添加至application对象实例上。在组件扫描完成后,便调用AspectManager的registerAspect()方法