最近在学习NestJs
过程中发现,NestJs
大量借鉴了 Angular
和 spring
的,引入了AOP面向切面编程的思想。什么是面向切面编程呢?一个正常的http请求经过 Controller(控制器)、Service(服务)、Repository(数据库访问)的过程中,如果需要加入日志记录、权限控制、异常处理等链路。我们可以避开直接改造Controller 层会带来的不优雅的后果,而是在调用 Controller 之前和之后加入一个执行通用逻辑,就像切了一刀一样。
这样的横向扩展点就叫做切面,这种透明的加入一些切面逻辑的编程方式就叫做 AOP。
NestJs
实现这种AOP思想的核心大多是通过@Module
、 @Controller
、@Injectable
、@UsePipes
、@SetMetadata
、@UseFilters
、@UseInterceptors
等核心装饰器来实现的。ts的装饰器和java的注解非常相似,都可以通过添加源数据支持,虽然在语法上很相似,但是不同的语言之间使用的方法和概念上有所差异:
- 使用注解(Annotation)的语言:Java、C#(叫 Attribute)。
- 使用装饰器(Decorator)的语言:Python、TypeScript。
现在 JavaScript
中的装饰器提案已经进入到 stage 3
阶段了,相信不久之后不用ts也可以使用。
装饰器(Decorator)
ts的装饰器是一种特殊的声明(只能声明为函数
),可以附加到类
、方法
、属性
、参数
上,装饰者使用 @函数名
形式来修改类的行为。常见的装饰器有:
类装饰器
;属性装饰器
;方法装饰器
;参数装饰器
;
而装饰器的写法又分为两种它们分别是:
普通装饰器(无法传参数)
;装饰器工厂(可以传参数)
;
类装饰器 (普通装饰器)
- 类装饰器在类声明之前声明(紧靠着类声明),用来
监视
、修改
或者替换
类定义。 - 类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数
ts
function ClassDecorator(target: any) {
console.log(target === Services); // true
target.prototype.name = "kobe";
}
@ClassDecorator
class Services {
constructor() {}
}
const s: any = new Services();
console.log(s.name); // 'kobe'
类装饰器(装饰器工厂)
如果想定义一个装饰器工厂也很简单,它也是一个函数。和前面的不同的是,它又返回一个函数,这个被返回的函数接收一个参数,这个参数就是被装饰的类。具体代码如下所示:
js
// 装饰器工厂可以带参数传入
function ClassDecorator(params: string) {
return function (target) {
console.log(target === Services); // true
target.prototype.name = params; // 携带的参数可以直接赋值
};
}
@ClassDecorator("kobe")
class Services {
constructor() {}
}
const s: any = new Services();
console.log(s.name); // kobe
属性装饰器
- 属性装饰器用来装饰属性
- 属性装饰器表达式会在运行时当做函数被调用,传入
两个参数
:- 第一个参数: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 第二个参数: 是属性的名称
js
function PropertyDecorator(params) {
return function (target: any, propertyKey: any) {
console.log(target);
console.log(propertyKey);
console.log(params);
};
}
class Services {
@PropertyDecorator("kobe")
public name: number;
}
const s: any = new Services();
// 输出结果:
'Services: {}'
'name'
'kobe'
方法装饰器
- 方法装饰器用来装饰方法
- 第一个参数: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 第二个参数: 是方法的名称
- 第三个参数: 是方法的属性描述符
js
function MethodDecorator() {
return function (target: any, propertyKey: any, descriptor: any) {
console.log(target);
console.log(propertyKey);
console.log(descriptor);
};
}
class Services {
constructor() {}
@MethodDecorator()
getDate() {}
}
// 输出结果:
'Services: {}'
'getDate'
{
"writable": true,
"enumerable": fasle,
"configurable": true
}
参数装饰器
- 参数装饰器用来装饰参数
- 第一个参数: 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 第二个参数: 成员的名字
- 第三个参数: 参数在函数参数列表中的索引
js
function ParameterDecorator(params: any) {
return function (target: any, propertyKey: any, parameterIndex: number) {
console.log(params);
console.log(target);
console.log(propertyKey);
console.log(parameterIndex);
};
}
class Services {
constructor() {}
getDate(@ParameterDecorator("kobe") value: any) {
console.log(value);
}
}
const s: any = new Services();
s.getDate("james");
// 输出结果:
"kobe"
"Services {}"
"getDate"
0
"james"
装饰器执行顺序
装饰器的执行顺序依赖ts执行的上下文,谁先执行完就先调用谁(方法参数是从后到前,方法也是)。具体代码如下所示:
js
function a(params: any) {
console.log("类装饰器");
}
function b(params: any) {
return function (target: any, propertyKey: any) {
console.log("属性装饰器");
};
}
function c(params: any) {
return function (target: any, propertyKey: any, descriptor: any) {
console.log("方法装饰器");
};
}
function d(params: any) {
return function (target: any, propertyKey: any, parameterIndex: number) {
console.log("参数装饰器");
};
}
function e(params: any) {
return function (target: any, propertyKey: any, parameterIndex: number) {
console.log("参数装饰器1");
};
}
@a
class Services {
constructor() {}
@c("")
getDate(@d("") value, @e("") v: any) {}
@b("")
public name: number | undefined;
}
const s: any = new Services();
s.getDate("nba");
// 输出结果
"参数装饰器1"
"参数装饰器"
"方法装饰器"
"属性装饰器"
"类装饰器"