使用装饰器(注解)编写express实现nestjs控制器原理


效果图

  1. 动图在这里插入图片描述2. 静态图在这里插入图片描述

准备环境

  1. nodejs
  2. express
  3. typescript
  4. vscode

前言

 本文章所涉及的编程语言全程使用typescript,若不了解typescript的同学最好先补充一下前置知识才比较好吸收消化,本文章不会涉及一些前置知识的回顾,如有疑问请于下面评论区留言或者发私信。
 本文章涉及到的核心原理是使用到了typescript所拥有的装饰器写法,必须要打开tsconfig.json里面的experimentalDecorators选项才可以进行装饰器的编写。附上本文章所使用的tsconfig文件配置

{
  "compilerOptions": {
    "outDir": "./dist",
    "target": "ESNext",
    "experimentalDecorators": true, // 核心
    "emitDecoratorMetadata": true,
    "module": "commonjs",
    "baseUrl": "./",
    "typeRoots": ["./src/**/*"],
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

 好了那我们废话少说了,直接开始我们的代码环节吧。


控制器的实现

 总所周知,在设计模式中有一种设计模式用起来很舒服很方便,并且使用频率在后端开发之中是比较大量的,这种设计模式就是装饰器模式。不过本文不过多的解释该设计模式的原理。
 在编写express控制器的时候,我们平时可能是这样子写的

import express from 'express'
const app = express()
app.listen(8080)
app.get('method/get', (req, res) => res.send('get'))

这样子写起来,很方便,但是问题来了,如果后面接口多起来怎么办?我们就不能写的和springboot那样子,丢一个requestMapping吗?多方便呀。

// spring boot里的控制器
@RestController
@RequestMappin("/method")
public class SecondController {
 
    @RequestMapping("/get")
    public String get(){
        return "get";
    }
}

 那我们就要搞清楚springboot他是怎么写的,springboot最基本的控制器是由一个类装饰器 + 方法装饰器进行编写的,而类装饰器是使用了装饰器工厂进行编写的。
 所以我们先赖实现一个类的装饰器。我将这个类装饰器命名为:RequestMapping,支持传入的参数为path,也就是控制器的主路径的意思。
如果只是探究原理的话,在本例中类装饰器工厂的代码量极其之少,只需要3行即可编写完成。

// 控制器所使用的类装饰器工厂
export const RequestMapping = (path: string): ClassDecorator => {
  return function(target) {
    target.prototype.url = path
  }
}

 原理就是基于原型链往目标对象里面增加一个叫url的属性,使得每一个对象里面的成员都可以访问到这个url。
 完成了控制器的装饰,我们开始来写子装饰器,也就是方法装饰器,在nestjs中,我们的子装饰器大多是以GetPostPutDelete进行的命名,并且这也是属于restful的定义规范。
 也就是说我们简单的实现这四个装饰器并且配合类装饰器就能实现基本的原理了。所以我们就拿一个Get来实现一个方法装饰器就好,剩下的原理也就大差不差了。

/**
 * 控制器路由的方法装饰器
 * @param path 
 * @returns 
 */
export const Get = (path: string): MethodDecorator => {
  return (target, key, { value }) => {
    /**
     * 目标方法,只需要拿到返回内容即可,可以直接运行这个方法
     * 而这个类型断言为了类型规范,其实可直接定义any
     */
    const mFun = value as Methods.Get
    // 拿到装饰器工厂的原型链
    const mProto = target.constructor.prototype
    /**
     * 这里的定时器由于装饰器的加载顺序是不一样的
     * 类装饰器工厂是最后一个加载的
     * 所以在我们加载这个方法装饰器的时候
     * 类装饰器还没有写入原型链中的url
     * 所以我们需要写一个东西来等待获取到这个url
     * 应该还有更好的写法
     */
    const mTime = setInterval(() => {
      if (mProto.url) {
        // 拿到url,拼接上之后直接放到express之中即可
        const mUrl = mProto.url + path
        mApp.get(mUrl, async(req, res) => {
          const mData = await mFun(req, res)
          if (typeof mData === 'string') {
            res.send(mData) 
          } else {
            res.send(JSON.stringify(mData))
          }
        })
        clearInterval(mTime)
      }
    }, 5)
  }
}

 上面是我们的控制器的方法装饰器的写法,但是其中是需要有几个注意事项的,因为装饰器的加载顺序是不一样的,方法装饰器的加载顺序先于类装饰器,也就是说我们在加载这个方法装饰器的时候我们的的装饰器工厂还没有加载完毕呢,所以我们一开始是拿不到我们原型里面携带的url的。所以我们需要写一个定时器去等待这个。不过这个写法可以改进,应该有更优的写法。


源码下载

 最终我们就能直接使用这个几个装饰器啦。效果图就放在开头了,然后源码我已经上传至gitee了,需要的就自取啦
下载源码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小蓝阿姨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值