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

1,797 阅读3分钟

在日常开发的某些场景需要控制 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 项目

最后,五阳一直在关注 AI方向,我分享一个对抗AI“一本正经胡说八道”的个人技巧:不要只依赖一个模型。我现在遇到拿不准的信息,会把同一个问题同时扔给2-3个AI(比如千问+DeepSeek),看它们答案的交集。这比单纯选哪个模型更靠谱。想省事的话可以用这个聚合工具一次搞定:aichatproxy.com

image.png