前言
spring的主要作用就两个,自动装配加AOP,其实在spring3之后AOP这块也是在装配期间完成的。
spring或者springBoot,冗余代码太多了,很多的环境检查,状态检查,导致启动缓慢。(至少我心里是不爽的,总感觉没用的代码都是未来的坑)
这篇文章就讲下j-spring是如何完成装配的,方便自己日后回看,温故知新。
装配过程
1.代码案例
下面代码就是两个类实现了同一接口,然后使用@Autowired注解进行按类注入。
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);
})
2. 装配逻辑
//类信息和元数据
export const beanDefineList = new Set<BeanDefine>([]);
-
- 首先通过上一章讲的注解功能,我们可以知道在运行的spring.getBean方法之前,注解已经提前运行,并且把类的元数据信息添加到beanDefineList中了。
-
- spring.getBean首先会实例化后置处理器,方便对类的功能提升,和AOP操作。随后初始化额外 绑定的类。和java不同TS在编译成JS时,使用了treeshake技术,不引用的代码会被清除。这是在编译器期间发生的,无法通过编程技巧弥补,只能spring.bind(Class)进行手动引用。我认为这是一种很大的进步,让代码更简洁,紧凑,不用担心第三方ES库造成的代码臃肿。
//装配bean
export function getBean<T>(clazz:new()=>T):T{
beanFactoryInit();
return assemble(clazz);
}
export const beanFactoryInit = ()=>{
//1.首先实例化后置处理器
instanceBeanPostProcessor();
//2.实例化额外绑定的类
instanceExtClazz();
}
-
- 进入assemble方法,就是进行装配了。首先通过类获取到BeanDefine,然后进入assembleBeanDefine方法,根据类的描述信息进行装配。过程其实和spring差不多,只是没有那么复杂,我只写我实际需要的部分。
//根据类获取定义
export const getBeanDefineByClass = function(clazz:Clazz):BeanDefine|undefined{
return Array.from(beanDefineList).find(b => b.clazz == clazz);
}
//装配指定class
export const assemble = function (clazz:Clazz){
const bd = getBeanDefineByClass(clazz)
if(!bd){
throw Error(`can not find class ${clazz},maybe you forgot to add @Component!`)
}
const app = assembleBeanDefine(bd);
return app;
}
还是把代码拎出来吧,虽然很简单,但是光靠一张嘴确实费劲。
//装配指定beanDefine
function assembleBeanDefine(bd:BeanDefine):any{
if(beanDefineMap.has(bd))
return beanDefineMap.get(bd);
//防止循环引用 加入装配记录
if(assembleingClass.has(bd.clazz))
throw Error(`clazz ${bd.clazz} already assemble`)
assembleingClass.add(bd.clazz);
//实例化对象 只支持无参构造器
let bean = new bd.clazz();
//获取后置处理器
const beanPostProcessor = getSoredBeanPostProcessorList()
//bean处理器处理装配前操作
beanPostProcessor.forEach(post => bean = post.postProcessBeforeInitialization(bean,bd))
//字段属性装配和注入
bd.fieldList.forEach(fieldBd => {
const fieldName = fieldBd.name;
fieldBd.annotationList.forEach(anno => {
//处理装配 依据class类型
if(anno.clazz === Autowired){
const param = anno.params as AutowiredParam<Clazz>;
const id = param.id;
const clazz = param.clazz;
const force = param.force;
let subApp;
//处理装配 依据id
if(id){
const targetBd = idBeanDefineMap.get(id);
if(!targetBd){
throw Error(`[ID_NOT_FIND_ERROR] @ComponentId('${id}') not find!`)
}
if(clazz !== targetBd.clazz){
throw Error(`[CLAZZ_MATCH_ERROR] id '${id}' class must be ${clazz}` )
}
subApp = assembleBeanDefine(targetBd)
}
//强制装配
if(force){
subApp = assemble(param.clazz);
}else{
const oldValue = (bean as any)[fieldName]
if(!isPlainObject(oldValue))
throw Error(`[NOT_FORCE] class[${bd.clazz}] field[${fieldName}] autowired.force=false must be set object!`)
const fieldClazzBd = getBeanDefineByClass(param.clazz)
if(fieldClazzBd){
subApp = assembleBeanDefine(fieldClazzBd);
}else{
return;
}
}
// const subApp = assemble(param.clazz);
if(!Reflect.set(bean,fieldName,subApp)){
throw Error(`[REFLECT_PROPERTY_SET_ERROR]:class:${clazz.name} field:${fieldName}`)
}
}
//处理自动注入
if(anno.clazz === Value){
const param = anno.params as ValueParam;
//如果不是强制赋值 并且没有配置参数
if(!param.force && !hasConfig(param.path)){
if(bean[fieldName] === void 0){
throw Error(`class:${bd.clazz} field:${fieldName} must be set initial value!`)
}
}else if(!Reflect.set(bean,fieldName,geFormatValue(param.path,param.type))){
throw Error('annotation Value error')
}
}
})
});
//调用生命周期函数
(bean as SpringBean).onAttrMounted?.();
//bean处理器处理装配后操作
beanPostProcessor.forEach(post => bean = post.postProcessAfterInitialization(bean,bd))
//删除引用
assembleingClass.delete(bd.clazz);
beanDefineMap.set(bd,bean);
if(isSpringStarter(bean)){
starterBeanList.add(bean);
}
return bean;
}
- 第一步:检查是否实例化过了,存在返回缓存。j-spring目前只支持单例,后期如果真的有多例这个需求在升级吧。其实很简单,但我就是不想写,不用的代码后面就是坑。
- 第二步:检查当前类是否在装配任务中,如果存在必然发生了循环引用。报错退出。
- 第三步:使用空构造器创建实例,我用spring从不用构造器参数注入。什么不用构造器注入就是破坏了OO,这些都是狗屁的教条主义,一切从实际出来,少就是多。
- 第四步:后置处理器的装配前操作
- 第五步:对属性进行注入和装配 (遇到@Autowired方法,就会进入递归装配重新回到第一步)
- 第六步:调用bean的生命周期函数onAttrMounted方法。
- 第七步:后置处理器的装配后操作。(前后后置处理器都改变了bean的引用,返回的bean可能已经只是代理Bean了)
- 第八步:收尾工作,将当前类剔除装配任务,将bean放入缓存。
- 第九步:如果该类是启动器,放入到启动器Set中
- 第十步:返回最终bean.
总结
j-spring的装配工作就是spring的简简简化版,就是一辆人民的小五菱。