spring(三)-----------什么是beanDefinition?

上篇我们以mybatis如何注入mapper对象为引,发现mybatis使用了FactoryBean(动态代理)+动态注册beanDefinition 的方式实现了对多个bean进行注入。

这篇我们延续上篇的问题,什么是beanDefinition?动态注入beanDefinition是个什么样的过程?

1、beanDefinition

beanDefinition可以称之为spring bean的建模对象,

        那么什么是spring bean 的建模对象呢?还是一言概之就是把一个 bean 实例化出来的模型对象?有人会 问把一个bean 实例化出来有 Class 就行了啊, Class 也就是我们通常说的类对象,就是一个普通对象的建 模对象,那么为什么spring 不能用 Class 来建立 bean ? 很简单,因为 Class 无法完成 bean 的抽象,比如 bean的作用域, bean 的注入模型, bean 是否是懒加载等等信息, Class 是无法抽象出来的,故而需要 一个BeanDefifinition 类来抽象这些信息,以便于 spring 可以根据这些信息去完美的实例化一个 bean
        上述文字可以简单理解spring 当中的 BeanDefifinition 就是 java 当中的 Class Class 可以用来描述一个 类的属性和方法等等其他信息 BeanDefifintion 可以描述 springbean 当中的 scope lazy ,以及属性和 方法等等其他信息。
spring(三)-----------什么是beanDefinition?_第1张图片

(其中PropertyValues中在spring注入中会使用到,constructorArgumentVaalues在推断构造方式时使用)

BeanDefifintion 是一个接口,但是 spring 框架当中有很多类实现了 BeanDefifintion ,各个不同的
BeanDefifintion 的作用有一点点区别 —— 大同小异。关于 BeanDefifintion 的详细内容后面我们再来分 析;这里先列举几个和本节课有关的公共属性;上图如果用一个类代码来表示:
BeanDefintion{

    //如果这个对象当中有值,spring会把他作为构造方法的参数填充
    private ConstructorArgumentValues constructorArgumentValues;

    //默认单例
    private String scope = SCOPE_DEFAULT;

    //是否需要依赖别的bean 默认没有
    private String[] dependsOn;

    //这个beanDefinition对象所描述的类
    private volatile Object beanClass;

    //如果这个对象当中有值,spring会把他作为setter方法的参数填充
    private MutablePropertyValues propertyValues;
}
好了如果你对 spring BeanDefifinition 有了基本理解;那么下面在通过图来继续说明 beanDefifinition 是怎 么关联spring bean 的实例化的。先用一副图来说明 java 实例化一个对象的基本流程:
spring(三)-----------什么是beanDefinition?_第2张图片
上图说的是假设磁盘上有 N .java 文件,首先我们把这些 java 文件编译成 class 文件,继而 java 虚拟机启 动会把这些class 文件 load 到内存,当遇到 new 关键字的时候会根据类的模板信息实例化这个对象;
但是 spring bean 实例化过程和一个普通 java 对象的实例化过程还是有区别的,同样用一幅图来说明一 下:
spring(三)-----------什么是beanDefinition?_第3张图片

特别说明:上图扫描之后识别 X Y 后存的不是类,是 ASM 直接读取的,但是为了容易理解姑且认为就是类吧
前提:假设磁盘上有 A C X Y Z D E 这些类;其中只有 X Y 是被加了注解(能够被 spring 扫描 到);
:启动 spring 容器,开始扫描,识别 X Y 后存入集合
:遍历集合实例化一个 BeanDefifinition 对象,解析 X Y ;填充 BeanDefifinition
:把初始化好、填充好的 beandefifinition 对象存到一个 beandefifinitionMap 当中
:调用所有 BeanFactoryPostProcessor (假设你没有提供、没有扩展,只会执行 spring 内部的,不 走红色)
spring 在实例化 bean 之前会对 beandefifinition 进行 validate (验证是否单例、是否 lazy 等等)
:如果一切正常(先不考虑不正常的),则直接实例化对象,完成自动注入等等后放到单例池
这是正常流程,一个简化版的 bean 实例化过程(实际远比这个复杂,后面再来分析复杂版的)
当然如果你对 spring 做了扩展,比如你提供了一个类实现了 BeanFactoryPostProcessor ,并且被 spring 扫描到了 且你提供的这个BeanFactoryPostProcessor 修改了 beandefifinitionMap 当中的信息则第 步之后后先走
:回调你提供的 BeanFactoryPostProcessor ,根据 key="x" 获取 beandefifinitionMap 当中的
beandefifinition 对象,然后修改这个 beandefifinition 对象的 beanclass D.class 那么最后实例化出来的bean 就是 D 而不是 X。
总结:又是一言概之 —— 单例池当中的 bean 可能与你提供的类无关;和 beandefifinitionMap 是有直接关 系的,可以被偷梁换柱;
言归正传,我们现在知道 BeanDefifinition在spring bean形成的大概作用和流程,下面我们还需要回到我们文章的主题,如何动态注册BeanDefifinition?
2、如何动态注册BeanDefifinition?
1)、 BeanFactoryPostProcessor接口(只能修改BeanDefinition,不能注册)
从上面的bean实例化过程可以知道,在没实例化成bean之前,spring提供了一个 BeanFactoryPostProcessor扩展接口(bean工厂的后置处理器),我们去实现该接口,就可以获取到BeanFactory对象,此时就能拿到BeanDefifinition对象,然后进行设置了,大概代码如下:
public class testAA implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

        BeanDefinition aa = beanFactory.getBeanDefinition("aa");
        aa.setLazyInit(true);
        aa.setFactoryBeanName("aaaa");
    }
}

但是我们一般不使用这种方式。

spring中还提供了两种方式去注入BeanDefifinition。

2)、BeanDefifinitionRegistryPostProcessor接口

public class testAA implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        BeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinitionRegistry.registerBeanDefinition("aa", beanDefinition);

    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }
}

可以实现该接口实现postProcessBeanDefinitionRegistry方法进行BeanDefition对象的注册

或者实现postProcessBeanFactory方法,获取到beanFactory对象编写类似1)进行修改BeanDefition。

我们还是不推荐第二种方式,比起这个还有很好的第三种方式

3)、ImportBeanDefifinitionRegistrar接口

(该接口在spring、springboot大量的使用,例如@Service、@Compnent等都是用它进行动态注册bean,很多三方框架集成Spring的时候,都会通过该接口,实现扫描指定的类,然后注册到spring容器中。 比如Mybatis中的Mapper接口,springCloud中的FeignClient接口,都是通过该接口实现的自定义注册逻)

作用:可以动态注册Beandefition、bean对象等。

spring(三)-----------什么是beanDefinition?_第4张图片

spring(三)-----------什么是beanDefinition?_第5张图片

实现其registerBeanDefinitions方法,我们可以看看如何模拟第三方框架集成到spring中:

@Slf4j
public class testAA implements ImportBeanDefinitionRegistrar {

    Map map= new HashMap<>();


    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //模拟第三方集成spring时,扫描需要注入的对象,并将对象转化为BeanDefinition类型存进map中
        scan();
        
        //遍历扫描到的BeanDefinition对象进行属性修改操作(修改非必须),然后进行动态注册
        for(String key : map.keySet()){
            AbstractBeanDefinition mapperBd = (AbstractBeanDefinition)
                    map.get(key);
            Class mapperClz =mapperBd.getBeanClass();
            System.out.println();
            log.debug("before:{}",mapperBd.getBeanClass().getName());
            mapperBd.setBeanClass(MyFactoryBean.class);
            log.debug("after:{}",mapperBd.getBeanClass().getName());
            mapperBd.getPropertyValues().add("mapperInterface",mapperClz.getName());
            
            //动态注册BeanDefition
            registry.registerBeanDefinition(key,mapperBd);

    }

}

    private void scan() {
        //假装模拟了扫描过程,把扫描结果放到了map当中
    }
    }

然后编写配置类,将这个testAA配置类注入spring中(使用@Import注解)

@Configuration
@Import(testAA.class)
public class aaConfig{
}

(注意:上面的这几种方式都是动态注册Beanfinition,但是仍然需要将配置类注入到spring中,spring才能知道你注册了这个beandefinition)ImportBeanDefifinitionRegistrar一般搭配@Import一起使用Spring @Import导入Bean的三种类型:Component、ImportSelector、ImportBeanDefinitionRegistrar_每天都要加油呀!的博客-CSDN博客

一般第三方集成到spring时,第三方的框架是不能使用spring的注入注解的,都是在开发xxx-spring的集成插件的时候利用配置类使用@Import的方式去批量导入多个bean到spring中。

但是此时如果你用@Component注解将testAA注入spring中,此时则发现无法注入testAA中动态注册的Beandefinition,这是为什么???

registerBeanDefinitions方法的修改Beandefinition逻辑:(这里的MyFeactoryBean和上篇文章的MapperFactoryBean一样的写法)

遍历 map ,把 map当中的 beandefifinition 对应的 beanclass 替换成为 MyFactoryBean (因为我们的目的就是为了让 MyFactoryBean生效,使用了FactoryBean);然后又给 beandefifinition 填充了 getPropertyValues() 的值为当前类 (例如某个mapper , 故而后面 spring 源码在实例化这些 MyFactoryBean 的时候会去 setter 当前类 ;从而完成代理;
这里用了编码的方式写了一个for循环完成了多个mapper的注入
(使用ImportBeanDefifinitionRegistrar完成动态注册BeanDefinition,从而解决了上一篇文章提及的如何解决注入多个mapper的简易性问题)

 这也是@MapperScan将扫描的到Mapper动态注册为BeanDefinition的伪代码。(老版本的mybatis-spring和新版本的代码实现有点差别,但差别不大)

可以看看一些模拟@Mapper、模拟@Compnent、模拟feign集成spring等例子加深对FactoryBean+ ImportBeanDefifinitionRegistrar来实现第三方集成spring进行bean注入的理解
动态注册bean(ImportBeanDefinitionRegistrar, FactoryBean) | wangqi的blog

借助ImportBeanDefinitionRegistrar接口实现bean的动态注入 - 简书

java - 基于ImportBeanDefinitionRegistrar和FactoryBean动态注入Bean到Spring容器中 - 个人文章 - SegmentFault 思否

至此,我们知道了mybatis使用FactoryBean+ImportBeanDefifinitionRegistrar集成到spring中,将mapper对象注入到spring容器中。但这两篇文章的目的是了解对象如何注入spring的,但可以看到,我们对于扫描的部分并没有分析,下面我们开始了解spring是怎么扫描到那些标注了@Compnent注解的类,然后将他们注入到spring中的。(可以先做下上面几个链接的练习,熟悉下怎么模拟达到@Compnent的效果)

除了知道如何进行动态注入BeanDefinition外,我们还可以知道任何实现了spring扩展接口(例如BeanFactory、BeanFactoryPostProcess、BeanPostProcess、ImportBeanDefifinitionRegistrar等)的配置类都需要注入到spring中让spring感知到有这个实现,有些接口的实现类需要搭配某个注解才能使得实现方法生效,例如ImportBeanDefinitionPostProcess就需要使用@Import导入到spring容器中(如果搭配@Compnent虽然实现类能被注入但实现类的实现方法不会被调用到),而BeanFactory、BeanFactoryPostProcess等则是两个注解都能使用。

你可能感兴趣的:(spring,mybatis,java)