面试官:如何确保 Bean 第一个被加载进Spring 上下文?详解 Spring 初始化 Bean 顺序

918 阅读5分钟

在日常开发的某些场景需要控制 Bean 的加载顺序。例如我希望 实现一个功能,通过静态方法获取到 Spring 上下文。于是我通过如下方式 定义

@Service
public class ApplicationContextUtils implements ApplicationContextAware {

    private static ApplicationContext context;

    public static ApplicationContext getContext() {
        return context;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        CommonLog.warn("ApplicationContextUtils 可正常获取 applcationContext");
        context = applicationContext;
    }
}

定义 ApplicationContextUtils 实现 ApplicationContextAware 接口,可实现 Bean 初始化阶段被注入 Spring 上下文。 上下文被存放到 静态属性中,就可以通过静态方法获取上下文。

然而我在实际使用时,遇到了问题。 如果 Bean 的初始化顺序早于 ApplicationContextUtils 那么无法使用此工具类!

解决 Spring Bean 的加载顺序有如下几种方式,我推荐最后一种!

1. Autowire 显式依赖

Spring 在初始化 Bean 时,依次执行 PostContruct afterPropertiesSet init-method方法。在执行 Bean 的初始化方法之前,Spring 会初始化 Bean 的 Autowired 依赖。

如下Spring 源码,populateBean 中会注入 Spring 依赖的 Bean(如果还未初始化,则初始化),然后才初始化 Bean 本身。 image.png

源码位置: AbstractAutowireCapableBeanFactory

2. DependOn注解

@DependsOn 注解中可以声明 依赖的 BeanName,那么 Spring 会保证先初始化这些 Bean。以下方式可以保证 先初始化applicationContextUtils,然后再初始化 DemoMemberPurchaseExtension

@DependsOn({"applicationContextUtils"})
public class DemoMemberPurchaseExtension implements PurchaseExtension {

…………
}

3. Order注解

@Order 注解用于指定带有特定功能的组件(如拦截器、切面等)的执行顺序,但是不能解决 Bean 的初始化顺序。首先是 Spring 初始化 Bean 的时候并没有这段代码,实际测试也不生效。

因此不推荐使用 Order 注解,解决 bean 的初始化顺序问题。

ApplicationContextInitializer

声明 ApplicationContextInitializer 实现类

首先声明实现类,实现两个接口。ApplicationContextInitializer 和 BeanDefinitionRegistryPostProcessor ,在实现时,当 Spring 启动之初,还未加载bean定义时,执行initialize方法,在该方法中想 Spring 上下文注入 当前类作为 BeanFactoryPostProcessor,然后 Spring 稍后执行所有的 BeanFactoryPostProcessor,在该接口方法 postProcessBeanDefinitionRegistry中 项 Spring 注入普通的一个bean 即 applicationContextUtils,因此该 Bean 将作为第一个Bean (业务自定义)被加载进 Spring 中。

public class MemberClubApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>, BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        beanDefinitionRegistry.registerBeanDefinition("applicationContextUtils", new RootBeanDefinition(ApplicationContextUtils.class));
    }

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

    }

    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        configurableApplicationContext.addBeanFactoryPostProcessor(this);
    }
}

4. 声明 Spring 加载 ApplicationContextInitializer

可以通过API 方式,也可以通过配置方式注册 Initializer

API 方式

在初始化 SpringApplication 对象后,向其注册 MemberClubApplicationContextInitializer

SpringApplication application = new SpringApplication(AppStarter.class);

application.addInitializers(new MemberClubApplicationContextInitializer());

application.run(args);

配置方式

可以在 application.yml 配置文件注册 Initializer

context:
  initializer:
    classes: com.memberclub.common.util.MemberClubApplicationContextInitializer

第四种方式是最优方案,可以一劳永逸的解决 Bean 加载顺序,而使用 Autowired 和 DependOn 方式则需要不断关注 bean 的依赖和加载顺序,难以调试和维护。

因此推荐使用方案 4,即ApplicationContextInitializer 方式!

以上完整代码可以参考 Memberclub 项目

我的开源项目

最后夹带一点私货,五阳最近花了3个月的时间完成一个开源项目。

开源3周以来,已有近 230 多个关注和Fork

Gitee:gitee.com/juejinwuyan…

GitHub github.com/juejin-wuya…

开源平台上有很多在线商城系统,功能很全,很完善,关注者众多,然而实际业务场景非常复杂和多样化,开源的在线商城系统很难完全匹配实际业务,广泛的痛点是

  • 功能堆砌,大部分功能用不上,需要大量裁剪;
  • 逻辑差异点较多,需要大量修改;
  • 功能之间耦合,难以独立替换某个功能。

由于技术中间件功能诉求较为一致,使用者无需过多定制化,技术中间件开源项目以上的痛点不明显,然而电商交易等业务系统虽然通用性较多,但各行业各产品的业务差异化极大,所以导致以上痛点比较明显

所以我在思考,有没有一个开源系统,能提供电商交易的基础能力,能让开发者搭积木的方式,快速搭建一个完全契合自己业务的新系统呢?

  • 他们可以通过编排和配置选择自己需要的功能,而无需在一个现成的开源系统上进行裁剪
  • 他们可以轻松的新增扩展业务的差异化逻辑,不需要阅读然后修改原有的系统代码!
  • 他们可以轻松的替换掉他们认为垃圾的、多余的系统组件,而不需要考虑其他功能是否会收到影响

开发者们,可以择需选择需要的能力组件,组件中差异化的部分有插件扩展点能轻松扩展。或者能支持开发者快速的重新写一个完全适合自己的新组件然后编排注册到系统中?

memberclub 就是基于这样的想法而设计的。 它的定位是电商类交易系统工具箱, 以SDK方式对外提供通用的交易能力,能让开发者像搭积木方式,从0到1,快速构建一个新的电商交易系统!

image.png

具体介绍可参见

Gitee开源地址gitee.com/juejinwuyan…

GitHub开源地址 : github.com/juejin-wuya…

在这个项目中你可以学习到 SpringBoot 集成 以下框架或组件。

  1. Mybatis、Mybatis-plus 集成多数据源
  2. Sharding-jdbc 多数据源分库分表
  3. redis/redisson 缓存
  4. Apollo 分布式配置中心
  5. Spring Cloud 微服务全家桶
  6. RabbitMq 消息队列
  7. H2 内存数据库
  8. Swagger + Lombok + MapStruct

同时你也可以学习到以下组件的实现原理

  1. 流程引擎的实现原理
  2. 扩展点引擎实现原理
  3. 分布式重试组件实现原理
  4. 通用日志组件实现原理 参考:juejin.cn/post/740727…
  5. 商品库存实现原理: 参考:juejin.cn/post/731377…
  6. 分布式锁组件: 参考:
  7. Redis Lua的使用
  8. Spring 上下文工具类 参考: juejin.cn/post/746927…