依赖注入(IOC)

IOC(Inversion of Control)控制反转,是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。

什么是 IOC

控制反转(Inversion of Control)是一种借助于第三方容器,实现对具有依赖关系的对象之间解耦的一种代码设计思路

例如我们的程序中有对象 A、B、C、D 像齿轮一样相互依赖的运作着,如下图:

images

我们可以看到,在这样的齿轮组中,如果有一个齿轮出了问题,就可能会影响到整个齿轮组的正常运转。

为了解决对象之间的耦合度过高的问题,软件专家 Michael Mattson 提出了 IOC 理论,借助于“第三方容器”实现对象之间的解耦,如下图:

images

由于引进了中间位置的“第三方容器”,也就是IOC容器,使得 A、B、C、D 这 4 个对象没有了耦合关系,齿轮之间的传动全部依靠“第三方容器”了,全部对象的控制权全部上缴给“第三方”IOC 容器。

当对象 A 需要用到对象 B 的时候,只需要通过 IOC 容器来获取对象B的实例,而不需要自己创建一个对象 B 实例。而 IOC 容器又通过依赖注入的方式将对象 A 需要的对象 B 实例注入到对象 A 中。

images

@Resource、@Inject 修饰器

Uma 提供了@Resource修饰器@Inject修饰器来实现IOC容器依赖注入

@Resource 修饰器可以修饰${URSA_ROOT}中的任意class,被@Resource 修饰的 class,将会在 IOC 容器中加入一个该class的实例

Resource 装饰器还可以传入参数作为 class 的实例化参数。 export declare function Resource(...props: any[]): Function;

@Resource('user')
class Test {
    constructor(readonly tablename) {
    }
}
1
2
3
4
5

@Inject 修饰器可以将被@Resource修饰过的 class 的实例注入到指定变量中

例如,我们在${URSA_ROOT}/model中创建一个user.model.ts,并使用@Resource 将该类实例化后加入到资源容器中.

注意inject传入容器名称为被修饰class的文件名,而非class类名。比如Resouce修饰了service.user.ts,使用@Inject('user')注入。

import { Resource } from '@umajs/core'

// ===> @Resource修饰器
@Resource()
export default class UserModel {
  findAll() {
    // ...
    return []
  }
}
1
2
3
4
5
6
7
8
9
10

然后我们可以在 controller 中获取该 model 实例

import {
  BaseController,
  Path,
  Private,
  Param,
  Query,
  RequestMethod,
  Inject,
  Aspect,
} from '@umajs/core'
import UserModel from '../model/user.model'

export default class Index extends BaseController {
  // ===> 获取实例,实例的名称为@Resource修饰的class所在的文件名
  @Inject('user')
  user: UserModel

  async testModel() {
    // ===> 这里不需要创建user实例,@Resource已经将userModel实例化保存在容器中
    const userList = await this.user.findAll()
    return this.json(userList)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

@Resource 可以对任何class进行修饰,即任何资源都可以加入到容器中,然后通过依赖注入的方式使用。

@Resource修饰器支持传入参数,传入的参数将会被作为修饰类实例化时的构造参数使用。

@Service 修饰器

除了提供的 ResourceInject 装饰器,还有一个特殊的依赖注入装饰器 Service@Service 装饰器仅提供 Controller 使用,为了方便使用,Service 注入中内置了 ctx,框架默认将${URSA_ROOT}/service下的文件实例化加入到service的容器中,当我们使用的时候,通过@Service修饰器去获取该实例。

import DemoService from '../service/demo.service'

export default class Index extends BaseController {
  @Service(DemoService)
  demoService: DemoService

  @Path('/demo')
  demoService() {
    // return this.demoService.loadAll();
  }
}
1
2
3
4
5
6
7
8
9
10
11
import { BaseService } from '@umajs/core'

export default class Demp extends BaseService {
  loadAll() {
    // return
  }
}
1
2
3
4
5
6
7

@Service 和@Resource 最大的不同是,在@Service 修饰的方法中可以访问到ctx上下文对象,而@Resource 没有

非 Controller 中使用 @Service 时,必须传入 ctx 进行实例化才能使用或者 service 类不继承 BaseService,使用@Resource 容器修饰此 class。

Service 参考文档

service文件夹目录下使用@Resource需要启用非严格目录,初始化 Uma 实例时设置strictDir:true

const options: TUmaOption = {
  Router,
  bodyParser: { multipart: true },
  strictDir: true, // 启用非严格模式
  ROOT: __dirname,
  env: process.argv.indexOf('production') > -1 ? 'production' : 'development',
}
const uma = Uma.instance(options)
uma.start(8058)
1
2
3
4
5
6
7
8
9
// service
import { Inject, Resource } from '@umajs/core'
import User from '../model/User'
@Resource()
export default class {
  @Inject(User) // or @Inject('User')
  user: User

  getDefaultUserAge() {
    return this.user.getAge()
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
// controller
import { BaseController, Path, Result, Inject } from '@umajs/core'
import UserService from '../service/user.service'

export default class Index extends BaseController {
  @Path('/user')
  test() {
    console.log(this.userService.getDefaultUserAge())
    return Result.send('get defaultUserAge')
  }
}
1
2
3
4
5
6
7
8
9
10
11