j-spring
v2.0.42
Published
> Spring for TS
Downloads
6
Readme
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);
});