j-spring 将收集的信息用于装配(3)

65 阅读4分钟

j-spring源码

前言

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>([]);
    1. 首先通过上一章讲的注解功能,我们可以知道在运行的spring.getBean方法之前,注解已经提前运行,并且把类的元数据信息添加到beanDefineList中了。
    1. 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();

}
    1. 进入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的简简简化版,就是一辆人民的小五菱。