前面介绍了获取容器可以让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;@Overridepublic 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 {@PostConstructpublic 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 {@PostConstructpublic void initAgain() {log.info("初始化方法, 同时使用PostConstruct和InitializingBean");}@Overridepublic 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 {@PostConstructpublic void initAgain() {log.info("初始化方法, 同时使用PostConstruct和InitializingBean");}@Overridepublic 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 {@Autowiredprivate InitUserInitUtil initUserInitUtil;@PostConstructpublic void initAgain() {log.info("初始化方法, 同时使用PostConstruct和InitializingBean");}@Overridepublic 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;}@Overridepublic 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()); // 注册自定义 ApplicationContextInitializerapplication.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 {@PostConstructpublic void initAgain() {log.info("初始化方法, 同时使用PostConstruct和InitializingBean");}@Overridepublic 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
进行初始化。 -
C
是ConfigurableApplicationContext
的子类,如AnnotationConfigApplicationContext
或GenericApplicationContext
。
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 ProfileSystem.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 组件的对比
组件 | 作用范围 | 作用时间点 | 主要用途 |
---|
ApplicationContextInitializer | Spring 容器 | ApplicationContext.refresh() 之前 | 初始化 ApplicationContext ,添加 PropertySource 、修改配置等 |
BeanFactoryPostProcessor | BeanFactory | ApplicationContext.refresh() 期间 | 修改 Bean 定义(如动态修改 @Bean 配置) |
BeanPostProcessor | 单个 Bean | Bean 初始化前后 | 处理 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 容器
注意:由于 ApplicationContextInitializer
在 refresh()
之前执行,此时 Bean 还未初始化,不能调用 applicationContext.getBean()
。
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {@Overridepublic 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> {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {System.out.println("🚀 ApplicationContextInitializer 执行...");// 获取 EnvironmentConfigurableEnvironment 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()); // 注册自定义 ApplicationContextInitializerapplication.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 {@Testvoid contextLoads() {System.out.println("🔍 运行测试...");}
}