Spring系列2-BeanFactory

98 阅读15分钟

Spring生命周期

上面分析了如何创建一个ApplicationContext,让我们可以在各种特殊环境下构建一个Spring应用,但是日常工作中大多数还是直接使用的Spring提供的创建方式,如xml、注解、Groovy脚本等。

工作中更多的时候我们对Spring的定制化改造是在其Spring应用生命周期中,如统一的拦截,特定的加密,对bena的特殊的初始化等等操作。

上面我们分析了,一个Spring应用就是一个ApplicationContext的具体实现,这个就比较宏观了,我们知道怎么创建启动一个Spring应用了没用呀,毕竟工作中是在Spring框架范围内,进行研发,因此要知道细节。

一个Bean粗略一生

按照我们自己的理解来想一下,如果要自己实现一个IOC容器功能,需要有哪几步?

  1. 加载资源(配置),告诉IOC要创建哪些Bean
  2. 创建完成后为bean设置互相依赖关联,实现依赖注入
  3. 执行完2后,应用基本上就创建好了,最后完成一些创建完成后初始化值操作
  4. 等着销毁,执行一些扫尾操作

抛开Spring的那些强大功能和多元化的bean操控方式,大致也就上面这几个步骤。接下来仔细看下Spring是如何实现,上面几步,并且是怎样提供扩展接口的。

Spring周期总览

上面介绍的ApplicationContext你会发现现有Spring的无论哪个ApplicationContext最后都会执行一个方法就是org.springframework.context.support.AbstractApplicationContext#refresh该方法基本上可以说就是Spring生命周期体现,源码如下:

public void refresh() throws BeansException, IllegalStateException {
    // 保证同一时刻,只能由一个线程初始化ApplicationContext
    synchronized(this.startupShutdownMonitor) {
        // 创建前准备,加载资源***************************************************************************************
        // 创建bean前做准备,如记录启动时间,是否启动标识,初始化环境等操作;
        // 这一步我们也可以重写,可以在这里加点自己公司的logo啥的,提前判断环境是否具备应用启动条件等。
        // 如我们就在自己的ApplicationContext里面检测环境下是否配备了某些环境变量,某些工具脚手架,安全环境等,如果不具备提示或者抛出异常
        // 做这一步的目的是为了杜绝在应用运行过程中发现环境不满足导致程序出错,比如我们在平常有个功能不会使用,但是每个季度会有一次数据收集,需要借助一个第三方工具,需要在机器上安装,因此可以在应用启动的时候就去判断,当然不止这一个,如果就为了这一个功能还可以做到应用里面更加方便,简单来说这个方法可以直接用来做应用前启动的状态检查
        this.prepareRefresh();

        // *********************************** 准备BeanFactory
        // 这一步非常关键,它决定了如何创建bean,通过名字可以判断这一步用于刷新BeanFacotry
        // 在AbstractRefreshableApplicationContext中,这一步会创建beanFactory并且同时加载bean
        // 在GenericApplicationContext中,这一步没有过多的操作,只会保证线程安全的刷新即可
        // 这两个ApplicationContext后面会详说
        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();

        // prepareBeanFactory为了beanfactory做准备工作,如设置classloader、添加ApplicationContextAwareProcessor等
        this.prepareBeanFactory(beanFactory);

        try {
            // postProcessBeanFactory是一个纯粹提供给子类实现的一个空白方法,如我们常用到到的ServletContextAware实现接口处理器就是ServletWebServerApplicationContext通过重写该方法在这里注册的
            this.postProcessBeanFactory(beanFactory);

            // 上面的prepareBeanFactory和postProcessBeanFactory算是ApplicationContext层面定制化接口,甚至我很少看到有定制prepareBeanFactory方法的,重写postProcessBeanFactory方法的倒还见过几次
            // invokeBeanFactoryPostProcessors 调用BeanFactoryPostProcessor接口的具体实现,这个方法的具体调用就看过很多了
            // 该方法一般用于处理beanFactory开始工作前最后的一些回调,如动态添加一个BeanDefinition,总的来说,这里可以完全接管到准备好的beanFactory,可以让你随便操控
            // 这个地方用得挺多的
            this.invokeBeanFactoryPostProcessors(beanFactory);

            // 向BeanFactory中注册BeanPostProcessor实现类
            this.registerBeanPostProcessors(beanFactory);
            // 设置 context 消息的国际化实现类。如果没有配置,则使用 DelegatingMessageSource 实现。
            this.initMessageSource();

            // 监听器******************************** ↓↓ 这一块我们自己定义的用得挺少的,一般都是直接用回调就能满足需求,很少自定义监听器
            // 初始化事件监听处理器,主要是用来处理发生了相应事件进行通知,如常用的ApplicationListener:ContextRefreshedEvent,ContextStartedEvent事件等
            this.initApplicationEventMulticaster();
            // 该方法类似于benfactory的prepareBeanFactory方法,主要是在bean初始化前进行一些自定义回调,在AbstracApplicationContext中是个空方法
            this.onRefresh();
            // 这个见名知其意,就是注册监听器的到ApplicationEventMulticaster里面便于调用
            this.registerListeners();
              // BeanFactory最终的初始化操作,用来对上面的beanFactory扫尾操作,也是初始化bean,属性注入实例的入口
            this.finishBeanFactoryInitialization(beanFactory);
            // 完成上下文创建,如发送 ContextRefreshedEvent 事件广播就是在这一步完成的
            this.finishRefresh();
        } catch (BeansException var9) {
            if (this.logger.isWarnEnabled()) {
                this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
            }

            this.destroyBeans();
            this.cancelRefresh(var9);
            throw var9;
        } finally {
            this.resetCommonCaches();
        }

    }
}

Spring的生命周期就是AbstractApplicationContext#refresh 作为入口贯彻整个应用的,大致如下图(图是网上找的)。

2022-08-24-21-01-11-image.png

在日常工作中,基于Spring不修改源码情况下开发主要就是基于这些过程定制。

BeanFactory生命周期

实例化BeanFactory

自定义BeanFactory

ApplicationContext与BeanFactory之间的关系

通过分析AbstractApplicationContext#refresh方法我们发现它大多数的方法都要传递beanFactory这个参数,然后调用beanFactory的具体方法,并且在实现BeanFactory接口方法的时候采用的是一种代理静态代理设计模式的设计,将具体的实现调用创建的BeanFactory的方法。

因此Applicationcontext与BeanFactory采用的是一种基于组合的设计,但是我们查看ApplicationContext的类体系发现它是实现了BeanFactory的接口的,那么为什么ApplicationContext呢?

主要原因是灵活扩展,采用组合的方式能够让ApplicationContext责任更轻,将创建bean的责任更加专注的交给BeanFactory,并且更加方便支持ApplicationContext切换BeanFactory。这样能够让代码更加内聚,符合“多用组合,少用设计”的思想。

ps:简单介绍一下内聚在哪儿:比如现在我们有另外一个地方需要BeanFactory的功能来创建bean,但是它又没有ApplicationContext这么复杂,这么多生命周期回调,如果我们将BeanFactory具体实现放到ApplicationContext中,这时候为了实现一个简单的bean创建,我们就得重写很多ApplicationContext方法,就是为了不让他们干扰我们作为简单的bean创建,使得ApplicationContext很笨重。同理如果我们要单独使用ApplicationContext的某些功能,我们并不需要Beanfactory,但是为了满足运行还是要将BeanFactory逻辑放在里面,不符合单一原则。

因此ApplicationContext和BeanFactory采用组合设计模式更加合理,比如我们现在要为ApplicationContext使用一个我们自定义的BeanFactory,只需要两步:

  1. 创建类继承AbstractApplicationContext,重写obtainFreshBeanFactory方法
  2. 创建我们自己的BeanFactory类实现ConfigurableListableBeanFactory接口,通过obtainFreshBeanFactory方法我们自定义的BeanFactory

通过以上两步我们可以复用原有的ApplicationContext逻辑用自己的BeanFactory来管理Spring的bean。但是说实话,没点勇气别去自定义BeanFactory,看下Spring默认的DefaultListableBeanFactory上千行的代码就知道不是一件容易的事情,但是DefaultListableBeanFactory的很多关键方法都是被重写的,可以看出来开发这个类的人,在开发的时候很好的考虑了基于这个类扩展。

2022-08-25-20-23-21-image.png

BeanFactory实例化时机

上面说到了没勇气还是别去推翻,自定义BeanFactory,一般都是直接使用DefaultListableBeanFactory最多也就是基于这个类做点扩展,并且上面也说了获取beanFactory是通过AbstractApplicationContext#obtainFreshBeanFactory方法来获取的。那么到底是什么时候创建DefaultListableBeanFactory的呢?

前面介绍ApplicationContext的体系结构的时候介绍了两个重要的实现类,分别是GenericApplicationContextAbstractRefreshableApplicationContext它们两个创建BeanFactory的时机不同,并且扩展性也不同。

GenericApplicationContext创建BeanFactory

GenericApplicationContext在其创建的时候就已经创建的BeanFactory:

2022-08-25-20-55-05-image.png

并且还提供了带DefaultListableBeanFactory参数的构造函数方便扩展。

2022-08-25-20-55-28-image.png

AbstractRefreshableApplicationContext创建BeanFactory

由于AbstractRefreshableApplicationContext是一个抽象类,因此决定创建BeanFactory可以由子类实现创建具体的BeanFactory主要体现在AbstractApplicationContext的生命周期方法refreshBeanFactory中,下图是AbstractRefreshableApplicationContext#refreshBeanFactory的实现,可以看到也是创建的DefaultListableBeanFactory。

2022-08-25-21-07-21-image.png

小总结

总结一下,GenericApplicationContext实现的ApplicationContext在构造函数中创建了BeanFactory;AbstractRefreshableApplicationContext实现的ApplicationContext在refreshBeanFactory方法中创建BeanFactory;一个是用组合的方式创建的BeanFactory一个是用继承的方式创建BeanFactory。

再来说下:知道BeanFactory创建时机有什么用,首先在介绍ApplicationContext体系的时候,我们知道现目前的所有ApplicationContext都是基于这两个类GenericApplicationContextAbstractRefreshableApplicationContext实现的,因此知道它们创建BeanFactory的时机能够更好的定制化DefaultListableBeanFactory,提前对BeanFactory参数设置等。

接管BeanFactory

垂直接管BeanFactory

上面介绍了BeanFactory是如何实例化的,如何被创建的,如果我们要定制化开发自己的BeanFactory需要怎样实现,基本上我还没遇到过要构建自己BeanFactory的场景,但是对已经实例化好的BeanFactory进行参数设置,干预后续BeanFactory对bean的创建等场景还是挺多。

如果不是要定制化自己的BeanFactory,就用不着干预BeanFactory的创建,只是对现有的DefaultListableBeanFactory进行参数设置,属性修改,如增删改BeanDefinition等,Spring专门提供了生命周期方法来处理。

分别是:AbstractApplicationContext#prepareBeanFactoryAbstractApplicationContext#postProcessBeanFactory这两个方法,专门用来做BeanFactory的回调,但是这两个方法都是基于ApplicationContext的,因此要通过这两个方法接管BeanFactory需要自己继承AbstractApplicationContext才可以,在日常开发中也用得比较少,具体使用可以参考AbstractApplicationContext的实现类。

无侵入接管BeanFactory(BeanFactoryPostProcessors)

上面的两种方法都是对Spring应用的一种侵入式扩展,有一定风险,并且直接关系到了Spring应用的创建,因此不是对Spring改造一般不会采用这两种方式来接管BeanFactory。

工作中更多的是采用实现BeanFactoryPostProcessorsBeanDefinitionRegistryPostProcessor接口的方式。

BeanFactoryPostProcessor

public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
}

当beanFactory初始化完毕后,在AbstractApplicationContext#invokeBeanFactoryPostProcessors调用实现了BeanFactoryPostProcessor接口的实现类回调方法postProcessBeanFactory,同时会将beanFactory的实例传入,可以让使用者,直接编写类接管到beanFactory实例对其进行相应的管理。

BeanDefinitionRegistryPostProcessor

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry var1) throws BeansException;
}

可以看到BeanDefinitionRegistryPostProcessor接口继承了BeanFactoryPostProcessor,该类与BeanFactoryPostProcessor主要的区别在于,这里可以接管到BeanDefinitionRegistry,在上面分析ApplicationContext的体系结构的时候,说到AbstractRefreshableApplicationContextGenericApplicationContext一个很大的区别在于GenericApplicationContext实现了BeanDefinitionRegistry接口实现了动态注册BeanDefinition,但是实现注册BeanDefinition是Spring体系的类。因此这里的回调方法postProcessBeanDefinitionRegistry着重于在无侵入的接管BeanDefinitionRegistry,可以在bean实例化前,让第三方应用自己注册相应的BeanDefinition。

ps:小扩展

可以猜一下,这个回调方法BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistryBeanFactoryPostProcessor#postProcessBeanFactory谁先调用?

答案是BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry的调用顺序优先于BeanFactoryPostProcessor#postProcessBeanFactory。这个我认为是跟BeanFactoryProcessor的语义有关系,BeanFactory的回调方法postProcessBeanFactory的目的是为了让使用者能够接管到一个完整的BeanFactory实例,因此如果它的顺序后于BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry方法,则丢失完整的BeanFactory这个特性。

使用示例

接下来我们看它们的实际运用。

启动前注入自定义的bean

我工作中遇到过最多的在BeanFactory这个生命周期的扩展场景就是,在应用启动前注入自定义的bean。

ps:可能很多人没有遇到过这类似的场景,一般都是在spring应用启动前我都已经把类给创建好了,只需要加配置把这个类注入到spring容器中就直接使用了,即时我不是用,注入进去应该也不影响吧。这是一般正常使用spring的场景,也没有问题。

我简单说下我们的场景:我们都知道大多数时候再MVC设计下,面向的是接口编程,如service依赖dao,一般依赖dao的接口而不是具体实现,但是spring实例化的时候肯定是要实例具体的实现的,在这个前提下,我们系统也是传统的MVC设计,service依赖于dao接口,但是我们的dao接口有两套实现,一套是基于数据库实现的数据交互,一套是基于本地缓存实现的数据交互。为什么要两套,这是因为我们环境的特殊性,有的地方部署的时候不允许安装数据库,或者说某些场景下根本装不了数据库。这时候我们在部署的时候就要指定应用是“纯单机”模式还是“依赖数据库”模式,如果是“纯单机”模式则需要注入本地缓存的dao,否则正常使用数据库的dao。

在不影响service的情况下我们的做法就是重写了BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry,通过在启动的时候,读取配置文件是否是“纯单机”模式,如果是则将所有的本地缓存dao注入,而不注入数据库的dao,并且在BeanFactoryPostProcessor#postProcessBeanFactory中禁用数据库连接创建、连接池等有关的操作,让其启动完全脱离数据库。

在有上面的业务场景情况下,接下来看实现,为什么我们要在postProcessBeanDefinitionRegistry中注入,在postProcessBeanFactory来排除数据库。

如果我们单纯的要在bean创建前注入自己的bean,可以有两种方法:

BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        String beanName = "localDao";
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(LocalDao.class);
        beanDefinitionRegistry.registerBeanDefinition(beanName,beanDefinition);
}

BeanFactoryPostProcessor#postProcessBeanFactory

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
     String beanName = "localDao";
     beanFactory.registerSingleton(beanName,new LocalDao());
}

以上两种方法都可以在Spring的bean实例化前注入一个bean实例,区别在于postProcessBeanDefinitionRegistry注入的是一个BeanDefinition,而postProcessBeanFactory直接注入的一个bean实例对象,这意味着beanFactory.registerSingleton(beanName,new LocalDao());在spring实例化bean之前就已经创建好了对象,使得注入的bean实例不会走spring生命周期方法,因为spring的bean生命周期方法都是从AbstractAutowireCapableBeanFactory#createBean进入的,因为在beanFactory阶段就已经实例化了好了bean,也就不会再创建了因此不会走生命周期方法,这个在后面bean创建的时候回详细说。

因此我们通常都是使用postProcessBeanDefinitionRegistry注入bean,这个得注意其区别,要不然在不熟悉其spring生命周期的,查bug很难查,我一个同事当初就找了半天问题,为啥不执行生命周期方法。

言归正传,我们在这特定的场景下,已经知道了如果手动注入BeanDefinition了,那么我们只需要写出如下代码即可完成上诉的“变态”需求。

ps:以下是伪代码,真实代码因为在内网拷不出来

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        boolean isStandAlone = isStandAloneModel();
        Map<String, BeanDefinition> daoBeanDefinitions;
        if(isStandAlone){
            daoBeanDefinitions = scanLocalDaoBean();
        }else{
            daoBeanDefinitions = scanDBDaoBean();
        }
        daoBeanDefinitions.forEach((beanName,beanDefinition)->{
            beanDefinitionRegistry.registerBeanDefinition(beanName,beanDefinition);
        });
    }

这样就初步完成了,在启动的时候按照特定场景下注入指定的bean的功能,并且完全不干扰service的逻辑,这就是面向接口编程的好处。

接下来只需要继续在BeanFactoryPostProcessor#postProcessBeanFactory中排除掉数据库环境配置即可。

ps:其实上诉需求如果是SpringBoot也可以用@ComponentScan的过滤器或者xml的<context:component-scan >来做,都是比较好的方案。但是这种方式呢,它功能比较单一,只适用于类扫描,而使用动态注入BeanDefinition的方式就很灵活,但是要自己实现很多逻辑,如不改变包扫描的前提先,动态注入,并且声明其作用域,例如jar包里面的纯java类,要批量注入到容器中等。

最终在这种场景下还是推荐使用component-scan,以上例子可以作为学习使用。但是如果真有特定bean要用代码注入的时候,而且不太适合component-scan还是可以在生命周期这儿做文章的。

MyBatis的对postProcessBeanDefinitionRegistry的运用

MyBatis也是实现了postProcessBeanDefinitionRegistry方法

    public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        if (this.processPropertyPlaceHolders) {
            this.processPropertyPlaceHolders();
        }

        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        scanner.setAddToConfig(this.addToConfig);
        scanner.setAnnotationClass(this.annotationClass);
        scanner.setMarkerInterface(this.markerInterface);
        scanner.setSqlSessionFactory(this.sqlSessionFactory);
        scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
        scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
        scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
        scanner.setResourceLoader(this.applicationContext);
        scanner.setBeanNameGenerator(this.nameGenerator);
        scanner.registerFilters();
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
    }

我们都知道MyBatis是没有具体实现类的,是通过动态代理生成的,因此要像容器中注入MyBatis的mapper实例,就得在这里做文章。

1、通过扫描将所有mapper接口注入BeanDefinition,class设置为mapperFactoryBean

2、将beanDefinition的autowireMode设置为byType

代码为:

MapperScannerConfigurer#postProcessBeanDefinitionRegistry

    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));

ClassPathBeanDefinitionScanner#scan

    this.doScan(basePackages);

ClassPathMapperScanner#doScan

    this.processBeanDefinitions(beanDefinitions);

        definition.setBeanClass(this.mapperFactoryBean.getClass());

        definition.setAutowireMode(2);

这里最关键点就在于将注入的BeanDefinition的class类型设置为了 MapperFactoryBean;MapperFactoryBean是FactoryBean的实现,也就是通过getObject返回了具体的实例:

    @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
小总结

在spring对BeanFactory的生命周期开发的里面主要有两块,一个是接管BeanDefinition注册的BeanDefinitionRegistryPostProcessor,一个是完全接管BeanFactory的BeanFactoryPostProcessor,其中BeanDefinitionRegistryPostProcessor优先级高于BeanFactoryPostProcessor。

我们可以在BeanDefinitionRegistryPostProcessor回调中,动态设置BeanDefinition,进行增删改查等操作,如批量设置某一类型的bean的作用域等属性,由单例改为原型这种操作;批量添加,删除bean等操作;又比如MyBatis中结合FactoryBean实现动态代理实现类注入,将bean的定义和具体实现分离,这是一个很典型的案例。

我们可以在BeanFactoryPostProcessor进行完全接管BeanFactory,可以预先装载实例对象等操作。

ps:在BeanFactory的回调中,一定要谨慎配置和修改BeanDefinition,因为对于不熟悉的人来说,只看类在xml或者注解的定义BeanDefinition,而在BeanFactory中修改了这些BeanDefinition很容易导致出现了bug不好排查。