基于 typescript 的模仿 spring 依赖注入和面向切面

依赖注入

支持注解 3 个,自动扫描@Resource 注入构造函数参数@inject 注入对象类型的构造函数参数@injectObj

支持配置文件

支持同时注解和配置文件

循环依赖检测

可扫描文件注解文件和配置文件,支持**和*占位符

支持继承父亲构造函数注入

支持工厂类,继承 factoryBean 类,默认获取 getObject 返回对象;可通过&id 获取工厂对象自身

获取实例时候可动态传参数覆盖注解或配置文件的参数

支持 factoryBean 特性

支持接口类型注入

目前只支持构造函数注入

面向切面

提供注解 Aspect Before After Around AfterReturn AfterThrow PointCut

提供配置文件,使用Checker校验

Aspect 的类必须同时使用Resource标记为 bean

配置文件的 adviceId 如果省略,默认为 aspectId

Before After Around 的对应方法,只有一个 Invoker 类型的参数. AfterThrow 和 AfterReturn 有第二个参数,分别是返回值和抛出的错误。

注解模式的全局切点:如果一个类标记了 PointCut,但是没有标记 Advice,会把这些 PointCut 作为全局切点

Aspect(number)设置优先级,数字>=0,数字越小优先级越高。

默认优先级都是 0,相同优先级的切面按照 Around Before After AfterReturn AfterThrow 的顺序执行,不同切面优先级不同的按照优先级先执行。比如有两个切面 a1 和 a2.

a1: around1 before1 after1 afterReturn1

a2: around2 before2 after2 afterReturn2

如果 a1 和 a2 优先级相同,执行顺序为 around1-start around2-start before1 before2 原生方法 around2-end around1-end after2 after1 afterReturn2 afterReturn1

但是具体是 around1-start 先还是 around2-start 先不一定。如果一个是配置文件,一个是注解的,那么配置文件先,否则不一定

如果 a1 优先级 0 a2 优先级 1,则为 around1 before1 around2 before2 原生方法 around2-end after2 afterReturn2 around1-end after1 afterReturn1

安装

npm install better-inject

使用

有一个service类,依赖dao类,依赖jdbc类。

下面的例子通过注解扫描到service和dao类。

通过配置文件定义jdbc类。

然后通过getBean获取到注入后的service实例。

// 主文件
import Context from 'better-inject'
const root = '绝对路径地址'
const context = new Context({
// 注解目标文件扫描
// 如果是相对路径,相对于root地址解析
// 占位符包含** 和 *
// **在目录中间代表目录下所有文件
// *在文件地址中表示正则.*
scanFiles: [
'service.ts',
'dao.ts',
// 这个含义是查找test目录下,第一层所有目录下的*.ts文件
'test/**/*.ts'
],
// 配置文件扫描
configFiles: 'config.ts',
root,
});
console.log(context.get('service'))
// service.ts
import { Resource } from 'better-inject';
import Dao from './dao';
@Resource(/** 默认 type: prototype id: 类名 parent: 无**/)
// 内部生成类似service: {dao: Dao}的定义数据
class Service {
private dao: Dao;
// 这里如果没有@Inject,会获取Dao类,并默认要注入一个id为dao的对象
// 如果你想注入一些别的东西或者Dao是一个接口,必须使用配置文件或Inject
// 注解主动注入
constructor(@Inject('oracledao')dao: Dao) {
this.dao = dao;
}
getDao() {
return this.dao;
}
}
export default Service;
// Dao.ts
import { Resource } from 'better-inject';
import Jdbc from './jdbc';
@Resource({type: 'single'})
export default class Dao {
private jdbc: Jdbc;
constructor(jdbc: Jdbc) {
this.jdbc = jdbc;
}
getJdbc() {
return this.jdbc;
}
}
// Jdbc.ts
export default class Jdbc {
private name: string;
constructor(name: string) {
this.name = name;
}
getName() {
return this.name;
}
}
// config.ts 可以通过配置文件定义全部数据关系,
import Jdbc from './jdbc';
import {Checker} from '../../context';
// 可以不使用Checker,直接导出,但是这样就没有ts数据格式校验
export default Checker([
{
// id,对应getBean()的参数
id: 'jdbc',
// 别名,对应getBean()的参数
alias: ['Jdbc', 'JDBC'],
// 这个bean对应类
beanClass: Jdbc,
// 构造函数参数直接值
constructParams: {
// 位置
0: {
// 值
value: 'oracle',
// 表示把这个value看做id,用于注入
isBean: true,
}
},
// 构造函数参数使用对象merge
constructParams: {
// 位置 表示注入一个对象,包含jdbc和name
两个属性,会和getBean的参数进行merge,getBean参数优先级高
0: [{
jdbc: {
// 值
value: 'oracle',
// 表示把这个value看做id,用于注入
isBean: true,
},
name: {
value: 'zcs',
},
}]
},
// 实例化类型 single 单例模式 prototype 原型模式 默认是prototype
type: 'single'
},
]);

注意

context 的 root 默认是 process.cwd()

Resouce 内部逻辑

把这个类标记成一个可通过容器获取的目标,把类名小写作为 id。

然后会读取类中的构造函数参数,如果认为某个构造函数是类,就会自动把它认为是一个依赖注入项,把这个类的类名小写作为要注入的 id。或者通过 inject 标签主动注入,inject 的值默认认为是 id。