前言 - 什么是AOP
AOP又叫面向切面编程,是一种非侵入式扩充对象、方法和函数行为的技术。什么是非侵入式呢?侵入式是需要知道框架中的代码,与框架代码紧密结合在一起。非侵入式是可以自由选择和组装各个功能模块,没有过多的依赖。有很多方式可以增加和合并方法,例如继承、组合、委托。通过AOP可以从“外部”去增加一些行为,在很多情况下,AOP比其他方式更灵活、更少侵入。简而言之,AOP就是在现有代码程序中,在程序生命周期或者横向流程中 加入/减去 一个或多个功能,不影响原有功能。
面向切面编程(AOP)
假设在代码中有一个“简单类”,在程序中许多地方都使用了这个类的实例,我们怀疑这个类中的一个方法是导致性能问题的根源,想要跟踪记录该方法的输入参数、计算所花费的时间,以及返回的result的值。 有几种方式:
- 修改所有调用点
在被调用的每个位置都进行日志记录,但方法在很多位置被调用,这意味着将进行很多次的复制、粘贴。可能会遗漏某些位置,更糟糕的是,在收集到数据后,可能忘记将某些位置的代码移除。 - 修改源代码
尽管这样做只需要修改一个位置,但具有相当的入侵性:需要修改类的源代码。想象一下,如果类中方法的源码比较复杂,包含多处 return 和一些 try/catch/finally 块。在不改变方法的行为的同时,收集到所需要的数据,代码修改起来并不是那么容易。 - 使用继承
可以避免修改类的源代码,但是需要将使用类的每一个位置都修改为继承类。
关注分离
“跟踪记录”这个分析功能,它与“简单类”原本的目的无关,它是一个额外的功能。“简单类”很可能是为了解决特定领域的特定问题而创建,上面那些解决方案都将一些无关行为引入到“简单类”中。“简单类”只需要完成自身的工作而不需要关心任何与分析相关的工作,但以上的解决方案都迫使分析工作与 “简单类” 的本质工作直接关联在一起。 我们需要一种技术,以一种可控的、非侵入式的方式来引入这类行为。也就是说,这种方式能够有力保障 “简单类” 的行为,并且不需要我们修改 “简单类” 的源代码,或者修改使用 “简单类” 的位置的源代码。
AOP的应用
在 JavaScript,这非常简单,甚至不需要使用任何工具或库就能实现 AOP,但它非常实用,就像其他工具或库帮助你构建可复用的模式一样。详细了解AOP可以看下这两篇文章面向切面编程(AOP)简介、用AOP改善javascript代码
koa 源码
1
2
3
4
5
6
├── lib
│ ├── application.js
│ ├── context.js
│ ├── request.js
│ └── response.js
└── package.json
这个就是 GitHub
https://github.com/koajs/koa上开源的koa2源码的源文件结构,核心代码就是lib目录下的四个文件
- application.js 是整个koa2 的入口文件,封装了context,request,response,以及最核心的中间件处理流程。
- context.js 处理应用上下文,里面直接封装部分request.js和response.js的方法
- request.js 处理http请求
- response.js 处理http响应
Koa.js 作为一个web框架,总结出来提供了两种能力
- 中间件机制(AOP切面)
- HTTP服务
Koa.js 的AOP设计
Koa洋葱模型本质是AOP面向切面编程,切面由中间件机制实现,中间件单一职责,遵循先进后出的切面执行顺序,类似入栈出栈的顺序。
HTTP 切面流程(请求=>中间件=>响应) 从HTTP请求拿到想要的数据,然后处理想要处理的事情,最后返回处理后的结果
Koa.js 中间件的分类
狭义中间件
- 中间件内操作请求 request
- 请求拦截
- 中间件内操作响应 response
- 响应拦截
- 中间件内操作上下文 context
- 直接上下文代理,初始化实例时候挂载代理在
app.context
上,初始化应用的时候才注入,只注入一次,每次请求都可以使用 - 请求过程上下文代理,请求时候挂载代理在
ctx
上,直接被app.use,请求时候才有注入,每次请求的注入都不同
- 直接上下文代理,初始化实例时候挂载代理在
- 大多数直接被 app.use() 加载
- 注意: 初始化实例挂载代理
context
不被app.use()
- 注意: 初始化实例挂载代理
举个栗子 例如 中间件koa-static主要是靠拦截请求和响应,加载静态资源,中间件koa-bodyparser主要是拦截请求后解析出HTTP请求体重的POST数据,再挂载到ctx上。
广义中间件
- 不直接提供中间件
- 通过间接方式提供了中间件或者子中间件
- 间接被 app.use() 加载
- 其他方式接入Koa切面
举个例子 例如中间koa-router 是先注册路由后形成多个子中间件,后面再封装成一个父中间件提供给app.use()加载,让所有子中间件加载到Koa.js的请求洋葱模型中。