当前位置: 首页 > news >正文

SpringBean模块(二)bean初始化(2)和容器初始化顺序的比较--引入ApplicationContextInitializer

前面介绍了获取容器可以让spring bean实现ApplicationContextAware,实际也是初始化执行了setApplicationContext接口,

初始化接口还可以借助一些注解或者spring bean的初始化方法,那么他们的执行顺序是什么样的呢?

一、验证(没有依赖关系时)是无序的

1、demo
下面新建三个class文件

分别使用ApplicationContextAware、@PostConstruct和InitializingBean

package com.bit.demo.test.bean;

import com.bit.demo.dto.UserDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class InitUserInitUtil implements ApplicationContextAware {

    private static UserDTO userDTO;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.info("初始化方法,ApplicationContextInitializer");
        UserDTO user = new UserDTO();
        user.setUserName("zs");
        user.setPassword("123456");
        this.userDTO = user;
    }

    public static UserDTO getUserDTO() {
        return userDTO;
    }
}
package com.bit.demo.test.bean;

import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class TestClass1 {

    @PostConstruct
    public void init(){
        //项目启动执行方法
        log.info("初始化方法, 使用PostConstruct");
    }
}
package com.bit.demo.test.bean;

import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class TestClass2 implements InitializingBean {

    @PostConstruct
    public void initAgain() {
        log.info("初始化方法, 同时使用PostConstruct和InitializingBean");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("初始化方法, 使用InitializingBean");
    }
}
  启动项目输出
 .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.4)

2025-03-28T10:56:01.574+08:00  INFO 13716 --- [           main] com.bit.demo.BitApplication              : Starting BitApplication using Java 17.0.11 with PID 13716 (C:\mydemo\bit-demo\target\classes started by Tina.Zhang in C:\code\cci-voice)
2025-03-28T10:56:01.576+08:00  INFO 13716 --- [           main] com.bit.demo.BitApplication              : No active profile set, falling back to 1 default profile: "default"
2025-03-28T10:56:02.209+08:00  INFO 13716 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 7777 (http)
2025-03-28T10:56:02.216+08:00  INFO 13716 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-03-28T10:56:02.217+08:00  INFO 13716 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.19]
2025-03-28T10:56:02.258+08:00  INFO 13716 --- [           main] o.a.c.c.C.[.[localhost].[/bitDemo]       : Initializing Spring embedded WebApplicationContext
2025-03-28T10:56:02.258+08:00  INFO 13716 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 648 ms
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
Initialization Sequence datacenterId:6 workerId:4
 _ _   |_  _ _|_. ___ _ |    _ 
| | |\/|_)(_| | |_\  |_)||_|_\ 
     /               |         
                        3.5.5 
2025-03-28T10:56:02.495+08:00  INFO 13716 --- [           main] c.bit.demo.test.bean.InitUserInitUtil    : 初始化方法,ApplicationContextInitializer
2025-03-28T10:56:02.496+08:00  INFO 13716 --- [           main] com.bit.demo.test.bean.TestClass1        : 初始化方法, 使用PostConstruct
2025-03-28T10:56:02.497+08:00  INFO 13716 --- [           main] com.bit.demo.test.bean.TestClass2        : 初始化方法, 同时使用PostConstruct和InitializingBean
2025-03-28T10:56:02.497+08:00  INFO 13716 --- [           main] com.bit.demo.test.bean.TestClass2        : 初始化方法, 使用InitializingBean
2025-03-28T10:56:02.718+08:00  INFO 13716 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 7777 (http) with context path '/bitDemo'
2025-03-28T10:56:02.723+08:00  INFO 13716 --- [           main] com.bit.demo.BitApplication              : Started BitApplication in 1.385 seconds (process running for 1.765)
2、考虑到路径影响类的加载

前面的输出顺序是InitUserInitUtil > TestClass1 > TestClass2,正好和类路径顺序一致,考虑到类的路径影响类的加载,现在TestClass1、TestClass2各copy出来一个,加个前缀Aa,排在InitUserInitUtil 的前面。

新增文件

再次启动

可以看到Aa开头的类日志先输出了。

3、结论

ApplicationContextAware的setApplicationContext、@PostConstruct、

InitializingBean的afterPropertiesSet 他们的执行顺序是随机的。

4、错误引用

基于上面,AaTestClass1 > AaTestClass2 > InitUserInitUtil > TestClass1 > TestClass2

现在如果在AaTestClass2中利用InitUserInitUtil 获取UserDTO:

package com.bit.demo.test.bean;

import com.bit.demo.dto.UserDTO;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class AaTestClass2 implements InitializingBean {

    @PostConstruct
    public void initAgain() {
        log.info("初始化方法, 同时使用PostConstruct和InitializingBean");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("初始化方法, 使用InitializingBean");
        UserDTO innitUser = InitUserInitUtil.getUserDTO();
        if(innitUser != null) {
            log.info("获取user成功",innitUser);
        }else{
            log.info("获取user失败",innitUser);
        }
        
    }
}

因为此时InitUserInitUtil 还没有初始化,获取到的是null:

注意:

这也间接说明了,不要滥用ApplicationContextAware来获取Bean,能自动获取Bean的都通过Autowired等注解获取,因为使用了注解spring自身会优化加载顺序,让被依赖的Bean先执行。在必须手动获取如非spring bean中使用则不用考虑加载问题(非spring bean根本不会自动加载)。

5、使用自动装配引入依赖关系来解决引用问题 

我们知道,使用@Autowired等自动装配,可以让被依赖的bean先执行。针对上面的问题,改写下:

package com.bit.demo.test.bean;

import com.bit.demo.dto.UserDTO;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class AaTestClass2 implements InitializingBean {

    @Autowired
    private InitUserInitUtil initUserInitUtil;

    @PostConstruct
    public void initAgain() {
        log.info("初始化方法, 同时使用PostConstruct和InitializingBean");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("初始化方法, 使用InitializingBean");
        UserDTO innitUser = InitUserInitUtil.getUserDTO();
        if(innitUser != null) {
            log.info("获取user成功",innitUser.toString());
        }else{
            log.info("获取user失败",innitUser);
        }

    }
}

这时启动打印:AaTestClass1  > InitUserInitUtil > AaTestClass2  > TestClass1 > TestClass2,

对比上面的AaTestClass1 > AaTestClass2 > InitUserInitUtil > TestClass1 > TestClass2,可见InitUserInitUtil 提前加载了 

6、引入ApplicationContextInitializer

上面增加依赖关系可以解决bean的引用问题。

那如果就希望InitUserInitUtil做为一个最底层的bean,能够在其他业务bean之前加载,还可以使用

ApplicationContextInitializer。
package com.bit.demo.test.bean;

import com.bit.demo.dto.UserDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class InitUserInitUtil implements ApplicationContextInitializer {

    private static UserDTO userDTO;

    public static UserDTO getUserDTO() {
        return userDTO;
    }

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        log.info("初始化方法,ApplicationContextInitializer");
        UserDTO user = new UserDTO();
        user.setUserName("zs");
        user.setPassword("123456");
        this.userDTO = user;
    }
}

注册:

@SpringBootApplication
public class BitApplication {

    public static void main(String[] args) {
        //SpringApplication.run(BitApplication.class, args);
        SpringApplication application = new SpringApplication(BitApplication.class);
        application.addInitializers(new InitUserInitUtil()); // 注册自定义 ApplicationContextInitializer
        application.run(args);
    }
}
package com.bit.demo.test.bean;

import com.bit.demo.dto.UserDTO;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class AaTestClass2 implements InitializingBean {

    @PostConstruct
    public void initAgain() {
        log.info("初始化方法, 同时使用PostConstruct和InitializingBean");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("初始化方法, 使用InitializingBean");
        UserDTO innitUser = InitUserInitUtil.getUserDTO();
        if(innitUser != null) {
            log.info("获取user成功",innitUser);
        }else{
            log.info("获取user失败",innitUser);
        }
        
    }
}
   
再次启动输出
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.4)

2025-03-28T11:18:53.520+08:00  INFO 15408 --- [           main] c.bit.demo.test.bean.InitUserInitUtil    : 初始化方法,ApplicationContextInitializer
2025-03-28T11:18:53.525+08:00  INFO 15408 --- [           main] com.bit.demo.BitApplication              : Starting BitApplication using Java 17.0.11 with PID 15408 (C:\mydemo\bit-demo\target\classes started by Tina.Zhang in C:\code\cci-voice)
2025-03-28T11:18:53.526+08:00  INFO 15408 --- [           main] com.bit.demo.BitApplication              : No active profile set, falling back to 1 default profile: "default"
2025-03-28T11:18:54.182+08:00  INFO 15408 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 7777 (http)
2025-03-28T11:18:54.190+08:00  INFO 15408 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-03-28T11:18:54.190+08:00  INFO 15408 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.19]
2025-03-28T11:18:54.233+08:00  INFO 15408 --- [           main] o.a.c.c.C.[.[localhost].[/bitDemo]       : Initializing Spring embedded WebApplicationContext
2025-03-28T11:18:54.233+08:00  INFO 15408 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 672 ms
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
Initialization Sequence datacenterId:6 workerId:2
 _ _   |_  _ _|_. ___ _ |    _ 
| | |\/|_)(_| | |_\  |_)||_|_\ 
     /               |         
                        3.5.5 
2025-03-28T11:18:54.467+08:00  INFO 15408 --- [           main] com.bit.demo.test.bean.AaTestClass1      : 初始化方法, 使用PostConstruct
2025-03-28T11:18:54.467+08:00  INFO 15408 --- [           main] com.bit.demo.test.bean.AaTestClass2      : 初始化方法, 同时使用PostConstruct和InitializingBean
2025-03-28T11:18:54.468+08:00  INFO 15408 --- [           main] com.bit.demo.test.bean.AaTestClass2      : 初始化方法, 使用InitializingBean
2025-03-28T11:18:54.468+08:00  INFO 15408 --- [           main] com.bit.demo.test.bean.AaTestClass2      : 获取user成功
2025-03-28T11:18:54.469+08:00  INFO 15408 --- [           main] com.bit.demo.test.bean.TestClass1        : 初始化方法, 使用PostConstruct
2025-03-28T11:18:54.469+08:00  INFO 15408 --- [           main] com.bit.demo.test.bean.TestClass2        : 初始化方法, 同时使用PostConstruct和InitializingBean
2025-03-28T11:18:54.469+08:00  INFO 15408 --- [           main] com.bit.demo.test.bean.TestClass2        : 初始化方法, 使用InitializingBean
2025-03-28T11:18:54.688+08:00  INFO 15408 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 7777 (http) with context path '/bitDemo'
2025-03-28T11:18:54.694+08:00  INFO 15408 --- [           main] com.bit.demo.BitApplication              : Started BitApplication in 1.376 seconds (process running for 1.72)

二、ApplicationContextInitializer

1、介绍

ApplicationContextInitializer 是 Spring 框架中的一个接口,主要用于在 Spring 容器刷新(refresh())之前ApplicationContext 进行自定义的初始化操作。它允许在 ApplicationContext 完全初始化之前进行配置,例如:添加属性、激活配置文件(Profiles)等。

这种机制让开发者能够在 Spring 启动过程中更早地进行干预,适用于有高级配置需求的应用程序。

该接口位于 org.springframework.context 包下:

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
    void initialize(C applicationContext);
}
2、方法说明

initialize(C applicationContext):

  • 该方法在 ApplicationContext 刷新(refresh())之前调用,可以对 applicationContext 进行初始化。

  • CConfigurableApplicationContext 的子类,如 AnnotationConfigApplicationContextGenericApplicationContext

3、典型使用场景
(1)动态修改 Environment 变量

initialize() 方法中修改 Environment,实现动态配置:

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    ConfigurableEnvironment environment = applicationContext.getEnvironment();
    environment.getSystemProperties().put("my.custom.property", "CustomValue");
    System.out.println("✅ 设置环境变量 my.custom.property = " + environment.getProperty("my.custom.property"));
}
(2)激活特定的 Spring Profile
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    ConfigurableEnvironment environment = applicationContext.getEnvironment();
    environment.setActiveProfiles("dev"); // 激活 dev Profile
    System.out.println("✅ 激活 Profile: " + String.join(", ", environment.getActiveProfiles()));
}
(3)在 Spring 容器启动前添加 PropertySource

如果需要在 Spring 启动前添加额外的配置源(如数据库、远程配置中心等),可以这样做:

import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import java.util.Properties;

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
    Properties properties = new Properties();
    properties.put("extra.config", "Loaded from ApplicationContextInitializer");
    propertySources.addFirst(new PropertiesPropertySource("extraProperties", properties));

    System.out.println("✅ 额外配置: " + applicationContext.getEnvironment().getProperty("extra.config"));
}

4、与其他 Spring 组件的对比
组件作用范围作用时间点主要用途
ApplicationContextInitializerSpring 容器ApplicationContext.refresh() 之前初始化 ApplicationContext,添加 PropertySource、修改配置等
BeanFactoryPostProcessorBeanFactoryApplicationContext.refresh() 期间修改 Bean 定义(如动态修改 @Bean 配置)
BeanPostProcessor单个 BeanBean 初始化前后处理 Bean 实例,如 AOP、代理等
ApplicationRunner / CommandLineRunner应用启动完成后ApplicationContext 初始化后运行启动任务(如初始化数据、执行业务逻辑)
5、执行时期和注意事项

ApplicationContextInitializer 的执行时机 早于 Spring 容器刷新(refresh()),如下所示:

(1)创建 SpringApplication

(2)调用 ApplicationContextInitializer.initialize()

(3)加载 Environment

(4)创建 ApplicationContext 并调用 refresh()

(5)扫描 BeanFactory 并注册 Bean

(5)调用 BeanFactoryPostProcessor

(6)调用 InitializingBean@PostConstruct

(7)启动 Spring 容器

注意:由于 ApplicationContextInitializerrefresh() 之前执行,此时 Bean 还未初始化,不能调用 applicationContext.getBean()

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;

public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("✅ 获取 ApplicationContext:" + applicationContext);
        System.out.println("✅ 获取 Environment:" + applicationContext.getEnvironment());

        // ❌ 不能使用 getBean(),因为此时 Bean 还未初始化
        try {
            Object myBean = applicationContext.getBean("myBean");
            System.out.println("获取 Bean: " + myBean);
        } catch (Exception e) {
            System.out.println("⚠️ 此时无法获取 Bean,因为 ApplicationContext 还未刷新!");
        }
    }
}
6、使用方法
(1)实现ApplicationContextInitializer
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("🚀 ApplicationContextInitializer 执行...");

        // 获取 Environment
        ConfigurableEnvironment environment = applicationContext.getEnvironment();

        // 设置自定义属性
        environment.getSystemProperties().put("custom.property", "Hello Spring!");

        // 打印 Environment 变量
        System.out.println("✅ Environment 自定义属性:" + environment.getProperty("custom.property"));
    }
}
(2)注册ApplicationContextInitializer

Spring 提供了 3 种方式 来注册 ApplicationContextInitializer

①  在 SpringApplication 中添加 addInitializers

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(MyApplication.class);
        application.addInitializers(new MyApplicationContextInitializer()); // 注册自定义 ApplicationContextInitializer
        application.run(args);
    }
}

② 在 spring.factories 中自动加载

创建 META-INF/spring.factories 文件:

org.springframework.context.ApplicationContextInitializer=com.example.MyApplicationContextInitializer

③ 在测试中使用 @ContextConfiguration

如果只想在测试环境中使用 ApplicationContextInitializer,可以使用 @ContextConfiguration

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;

@SpringBootTest
@ContextConfiguration(initializers = MyApplicationContextInitializer.class)
public class MyApplicationTests {

    @Test
    void contextLoads() {
        System.out.println("🔍 运行测试...");
    }
}

相关文章:

  • SQLark SQL编辑器秘籍,编写高效SQL查询
  • SpringBoot3解决跨域请求问题(同源策略、JSONP、CORS策略)(Access-Control-Allow-Origin)(2025详细教程)
  • 电销行业机器人外呼话术设计:关键注意事项与实践指南
  • C语言之数据结构:双向链表
  • 整理一些php7 新特性
  • Node.js 模块加载机制--详解
  • 【设计模式】策略模式+门面模式设计对接银行接口的API
  • # 线性代数:660习题总结660# 宋浩讲解视频
  • [Lc18_拓扑排序] string+queue+map | 火星字典
  • Stable Diffusion vue本地api接口对接,模型切换, ai功能集成开源项目 ollama-chat-ui-vue
  • 银行的压力测试如何进行?
  • GitHub绑定本地计算机以及仓库创建跟推送指南
  • 深入解析VLAN接口类型与数据处理机制
  • es6的100个问题
  • 无人机,雷达定点飞行时,位置发散,位置很飘,原因分析
  • 合规+增效 正也科技携智能营销产品出席中睿论坛
  • 材质及制作笔记
  • 如何在根据名称或id找到json里的节点以及对应的所有的父节点?
  • 【JavaScript】八、对象
  • mybatis笔记(下)
  • 125%→10%、24%税率暂停90天,对美关税开始调整
  • 《克莱默夫妇》导演罗伯特·本顿去世,终年92岁
  • 国务院关税税则委员会公布公告调整对原产于美国的进口商品加征关税措施
  • 山东枣庄同一站点两名饿了么骑手先后猝死,当地热线:职能部门正调查
  • 第1现场 | 印巴停火次日:当地民众逐渐恢复正常生活
  • 人民空军:网上出现的“运-20向外方运送物资”为不实消息