J-SPRING
Spring for TS
运行步骤:
-
一.注解解析
-
1.1 主力主要查看 SpringAnnotation.ts 里面 注解的实现 以及 annotation.test.ts 里面对注解的扩展。
-
1.2 这里有一个难点,就是理解 SpringContext 中的 SwapBeanDefine 类,这个类是用来描述辅助描述 bean 的元(类,方法,参数,字段)信息的。
-
1.3 还要系统的复习一遍 TS 的注解实现以及反射。
-
1.4 注解的运行在容器启动之前,所以在检索注解的时候会搜集指定的 bean(后置处理器),供后面容器的运行。
-
二.日志
-
1.1 默认启动内置日志,使用 console 打印。配置第三方后,委托给第三方运行.
-
1.2 调用 spring.loadLogger(op: (r: ResourceOperate) => Logger) 使用第三方日志功能。第三方日志无法使用容器功能,只能通过 ResourceOperate 读取 spring 的配置。
-
三.spring 工厂
-
3.1 首先打印所有检索到的配置项(通过@Value 注解触发搜集到的配置信息)
-
3.2 实例化后置处理器(通过@Component 扫描的实现 BeanPostProcessor 接口的 bean),根据 sort 属性决定运行顺序。
-
3.3 随后装配手动注入的类(通过 spring.bind|bindList|bindMoudle)
-
四.容器操作
-
4.1 通过 launch 运行容器中启动类(该类必须包含 main 方法),找到后直接调用 main 方法。
-
4.2 通过 getBean 获取容器中的指定类。
-
4.3 通过 replaceClass 调换容器中的指定类,需要在工厂实例化之前操作
-
五.模块启动器 starter
-
5.1 设计原因,解决模块间的启动顺序和依赖关系。 例如模块(web,mysql)需要先启动 mysql 然后启动 web。
-
5.2 这里存在一个延迟装配的概念,例如 web 模块中可能导入了 mysql 的模块,但是工厂启动时无法像 web 中装配 mysql 的模块,因为 Mysql 的模块需要在 stater 执行后注入到 spring 容器中。所以工厂将这些装配信息(例如 web 缺少 mysql.db 模块)记录到 lazyAutowiredList 中。每当一个启动器执行时,就将未装配的 lazyAutowiredList 信息尝试装配一次!当所有 starter 启动后,仍然存在未装配的记录,则抛出异常‘依赖不满足’。
-
5.3 spring 调用 invokeStarter 执行异步启动器,启动顺序由手动注入顺序决定。
spring
.loadConfig(config) //加载配置
.loadLogger() //设置日志
.bindModule([springWebModule, springWebConfig, controllerClassList]) //绑定模块
.invokeStarter();
install
npm install j-spring
Usage
example
import { spring,Component,Autowired } from "j-spring";
it('autowired by special interface',()=>{
interface A {
v():number;
}
@Component()
class A1 implements A{
v(): number {
return 1;
}
}
@Component()
class A2 implements A{
v(): number {
return 2;
}
}
@Component()
class Application {
@Autowired<A>({clazz:A1})
a1:A;
@Autowired<A>({clazz:A2})
a2:A;
main(c:number){
return this.a1.v()+this.a2.v()+c;
}
}
expect(spring.getBean(Application).main(3)).toBe(6);
})
inject profile infomation
import { spring,Component,Autowired } from "j-spring";
describe('resource config load test',()=>{
@Component()
class Student {
@Value({path:'student.name',type:String})
name:String;
@Value({path:'student.age',type:Number})
age:20;
@Value({path:'student.city',type:String})
city:String
getMsg(){
return ''+this.name+this.age+this.city;
}
}
@Component()
class Application {
@Value({path:'app.msg',type:String})
appMsg:string;
@Autowired({clazz:Student})
student:Student;
public main(){
return this.appMsg+this.student.getMsg();
}
}
it('A simple set value',()=>{
spring.loadConfig({
'app.msg':'hello',
student:{
name:'lina',
age:20,
city:'youda'
}
})
expect(spring.launch(Application)).toEqual(`hello! my name is lina and 20 years old!`)
})
})
aop
it('test sample Aop',()=>{
import { spring,BeanPostProcessor,Component } from "j-spring";
//create Annotation
const SupperCaseParamter = spring.methodAnnotationGenerator('SupperCaseParamter',{})
@Component()
class SupperCaseParamterBeanProcessor implements BeanPostProcessor {
getSort(): number {
return 100;
}
postProcessBeforeInitialization(bean: any, _beanDefine: BeanDefine): Object {
return bean;
}
postProcessAfterInitialization(bean: any, beanDefine: BeanDefine): Object {
beanDefine.methodList.filter(m => m.hasAnnotation(SupperCaseParamter)).forEach(m => {
const method = bean[m.name];
bean[m.name] = function(...args:any[]){
return method.apply(bean,args.map(a => {
return typeof a === 'string' ? (a as string).toUpperCase() : a;
}));
}
})
return bean;
}
}
@Component()
class Application {
@SupperCaseParamter
main(name:string){
return name;
}
}
expect(spring.bind(SupperCaseParamterBeanProcessor).getBean(Application).main('hello')).toEqual('HELLO');
})
costom annotation
import { spring, SpringContainer } from '../src';
//diy annotation
const Controller = (path: string) =>
spring.classAnnotationGenerator('Controller', { path }, Controller);
const ResfulApi = spring.classAnnotationGenerator('ResfulApi', {});
const Inject = (path: string) =>
spring.fieldAnnotationGenerator('Inject', { path }, Inject);
const Get = (path: string) =>
spring.methodAnnotationGenerator('Get', { path }, Get);
const Query = (fieldName: string) =>
spring.paramterAnnotationGenerator('Query', fieldName, {}, Query);
describe('test custom annotation', () => {
it('it should be work', () => {
@Controller('/apiController')
@ResfulApi
class ApiController extends SpringContainer {
@Inject('small pigBank')
pigBank: String;
@Get('/say')
async say(@Query('user') user: string) {
return user;
}
main() {
let result: any[] = [];
this.getBeanDefineMap().forEach((_v, k) => {
const data = {
class: k.clazz,
'anno-length': k.annotationList.length,
'anno-class': k.annotationList.map(a => a.clazz),
'anno-param-list': k.annotationList.map(a => a.params),
'field-list': k.fieldList.map(f => {
return {
name: f.name,
'anno-list': f.annotationList.map(a => a.clazz),
'anno-param-list': f.annotationList.map(a => a.params),
};
}),
'method-list': k.methodList.map(m => {
return {
name: m.name,
'anno-list': m.annotationList.map(m => m.clazz),
'anno-params': m.annotationList.map(m => m.params),
'field-list': m.paramterDefineList.map(pb => {
return {
name: pb.name,
index: pb.index,
'anno-list': pb.annotationList.map(a => a.clazz),
};
}),
};
}),
};
result.push(data);
});
return result;
}
}
expect(spring.launch(ApiController)).toEqual([
{
class: ApiController,
'anno-length': 2,
'anno-class': [ResfulApi, Controller],
'anno-param-list': [{}, { path: '/apiController' }],
'field-list': [
{
name: 'pigBank',
'anno-list': [Inject],
'anno-param-list': [{ path: 'small pigBank' }],
},
],
'method-list': [
{
name: 'say',
'anno-list': [Get],
'anno-params': [{ path: '/say' }],
'field-list': [
{
name: 'user',
index: 0,
'anno-list': [Query],
},
],
},
],
},
]);
});
});
replace dependence
it('replace autowired class', () => {
@Component()
class A {
value() {
return 1;
}
}
@Component()
class A100 extends A {
value(): number {
return 100;
}
}
@Component()
class Application {
@Autowired({ clazz: A })
a: A;
main() {
return this.a.value();
}
}
expect(spring.replaceClass(A, A100).launch(Application)).toBe(100);
});