Spring启动时实现初始化的几种方式

本文详细介绍了在Spring中初始化Bean的多种方式,包括构造器注入、@PostConstruct注解、InitializingBean接口、initMethod、ApplicationListener和ApplicationRunner/CommandLineRunner接口的使用。通过示例代码展示了它们的执行顺序和应用场景。

准确的说是spring容器实例化完成后,几种初始化的方式。为什么这么说呢?下看面示例:

@Slf4j
@Component
public class InitBeanDemo {
    @Autowired
    private Environment env;

    public InitBeanDemo() {
        log.info("DefaultProfiles: {}", Arrays.asList(env.getDefaultProfiles()));
        log.info("ActiveProfiles: {}", Arrays.asList(env.getActiveProfiles()));
    }

示例是在bean的构造方法里做一些初始化的工作,示例比较简单只做了日志打印。是理想很丰满,现实很骨感,报错了:Constructor threw exception; nested exception is java.lang.NullPointerException。
原因是,Environment尚未初始化完成。


接下来我们来探索一下 有哪些初始化方式能满足上面示例的需求。


构造方法里初始化

可以正常运行,在所有初始化方式里执行时机最早。原理是在InitBeanDemo实例化前就实例化了Environment。

@Component
public class InitBeanDemo {

    private final Environment env;

    @Autowired
    public InitBeanDemo (Environment environment) {
        this.env = environment;
        log.info("Constructor DefaultProfiles: {}", Arrays.asList(env.getDefaultProfiles()));
        log.info("Constructor ActiveProfiles: {}", Arrays.asList(env.getActiveProfiles()));
    }
}

常规三件套

常规三件套:@PostConstruct、InitializingBean、initMethod。 如果你愿意的话,三种方式可以在同一个Bean下同时使用,执行的优先级@PostConstruct > InitializingBean > initMethod。

@PostConstruct注解

在一个可以被扫描到Bean里,添加一个public void xxx()方法并加上@PostConstruct注解,方法里编写需要初始化的逻辑。
同一个应用程序里可以有多个@PostConstruct注解,同一个Bean里也可以有多个@PostConstruct注解。

@Slf4j
@Component
public class InitBeanDemo {
    @Autowired
    private Environment env;

    @PostConstruct
    public void init() {
        log.info("@PostConstruct DefaultProfiles: {}", Arrays.asList(env.getDefaultProfiles()));
        log.info("@PostConstruct ActiveProfiles: {}", Arrays.asList(env.getActiveProfiles()));
    }

实现InitializingBean接口

实现InitializingBean接口,在afterPropertiesSet() 方法里编写需要初始化的逻辑。
同一个应用程序里可以有多个实现InitializingBean接口的类,执行时机会按类名的自然顺序排序。

@Slf4j
@Component
public class InitBeanDemo implements InitializingBean {
    @Autowired
    private Environment env;

    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("InitializingBean DefaultProfiles: {}", Arrays.asList(env.getDefaultProfiles()));
        log.info("InitializingBean ActiveProfiles: {}", Arrays.asList(env.getActiveProfiles()));
    }
}

指定Bean的initMethod方法

使用@Bean注解的initMethod属性可用于Bean的初始化后执行的方法。initMethod必须是public void 的无参构造方法。

@Slf4j
public class InitBeanDemo implements InitializingBean {
    @Autowired
    private Environment env;
    
	public void initMethod() {
        log.info("initMethod DefaultProfiles: {}", Arrays.asList(env.getDefaultProfiles()));
        log.info("initMethod ActiveProfiles: {}", Arrays.asList(env.getActiveProfiles()));
    }
@Configuration
public class InitBeanConfig {

    @Bean(initMethod="initMethod")
    public InitBeanDemo initBeanDemo () {
        return new InitBeanDemo();
    }

}

等同于 在XML 配置中的init-method属性:

<bean id="initBeanDemo" class="com.xxx.InitBeanDemo" init-method="initMethod"></bean>

自定义ApplicationListener监听

两种方式,一种实现接口,另一种使用注解。

实现ApplicationListener接口

监听ContextRefreshedEvent事件。

@Slf4j
@Component
public class InitBeanDemo implements ApplicationListener<ContextRefreshedEvent>{
    @Autowired
    private Environment env;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        log.info("ApplicationListener DefaultProfiles: {}", Arrays.asList(env.getDefaultProfiles()));
        log.info("ApplicationListener ActiveProfiles: {}", Arrays.asList(env.getActiveProfiles()));
    }
}

@EventListener注释

方法参数里指定ContextRefreshedEvent事件。

@Slf4j
@Component
public class InitBeanDemo {
    @Autowired
    private Environment env;

    @EventListener
    public void onApplicationEvent2(ContextRefreshedEvent event) {
        log.info("@EventListener DefaultProfiles: {}", Arrays.asList(env.getDefaultProfiles()));
        log.info("@EventListener ActiveProfiles: {}", Arrays.asList(env.getActiveProfiles()));
    }
}

Spring Boot提供的初始化接口

ApplicationRunner接口

@Slf4j
@Component
public class InitBeanDemo implements ApplicationRunner {
    @Autowired
    private Environment env;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("ApplicationRunner: {}", args);
        log.info("ApplicationRunner: {}", args.getOptionNames());
        log.info("ApplicationRunner DefaultProfiles: {}", Arrays.asList(env.getDefaultProfiles()));
        log.info("ApplicationRunner ActiveProfiles: {}", Arrays.asList(env.getActiveProfiles()));
    }
}

CommandLineRunner接口

可以在同一个应用程序上下文中定义多个CommandLineRunner bean,并且可以使用@Ordered接口或@Order注释进行排序。

@Slf4j
@Component
public class InitBeanDemo implements CommandLineRunner {
    @Autowired
    private Environment env;

    @Override
    public void run(String... args) throws Exception {
        log.info("CommandLineRunner: {}", args);
        log.info("CommandLineRunner DefaultProfiles: {}", Arrays.asList(env.getDefaultProfiles()));
        log.info("CommandLineRunner ActiveProfiles: {}", Arrays.asList(env.getActiveProfiles()));
    }
}

在同一个Bean里使用以上初始化方式的执行先后顺序

在同一个Bean里使用以上初始化方式,运行的日志片段:

2021-06-07 11:24:41|INFO |main|c.c.s.s.t.ConstructorInitDemo|Constructor DefaultProfiles: [default]
2021-06-07 11:24:41|INFO |main|c.c.s.s.t.ConstructorInitDemo|Constructor ActiveProfiles: [sit]

2021-06-07 11:24:42|INFO |main|c.c.s.s.test.InitBeanDemo|@PostConstruct DefaultProfiles: [default]
2021-06-07 11:24:42|INFO |main|c.c.s.s.test.InitBeanDemo|@PostConstruct ActiveProfiles: [sit]
2021-06-07 11:24:42|INFO |main|c.c.s.s.test.InitBeanDemo|InitializingBean DefaultProfiles: [default]
2021-06-07 11:24:42|INFO |main|c.c.s.s.test.InitBeanDemo|InitializingBean ActiveProfiles: [sit]
2021-06-07 11:24:42|INFO |main|c.c.s.s.test.InitBeanDemo|initMethod DefaultProfiles: [default]
2021-06-07 11:24:42|INFO |main|c.c.s.s.test.InitBeanDemo|initMethod ActiveProfiles: [sit]

2021-06-07 11:24:44|INFO |main|c.c.s.s.test.InitBeanDemo|@EventListener DefaultProfiles: [default]
2021-06-07 11:24:44|INFO |main|c.c.s.s.test.InitBeanDemo|@EventListener ActiveProfiles: [sit]
2021-06-07 11:24:44|INFO |main|c.c.s.s.test.InitBeanDemo|ApplicationListener DefaultProfiles: [default]
2021-06-07 11:24:44|INFO |main|c.c.s.s.test.InitBeanDemo|ApplicationListener ActiveProfiles: [sit]


2021-06-07 11:24:44|INFO |main|c.c.s.s.test.InitBeanDemo|ApplicationRunner: org.springframework.boot.DefaultApplicationArguments@68bef3df
2021-06-07 11:24:44|INFO |main|c.c.s.s.test.InitBeanDemo|ApplicationRunner: []
2021-06-07 11:24:44|INFO |main|c.c.s.s.test.InitBeanDemo|ApplicationRunner DefaultProfiles: [default]
2021-06-07 11:24:44|INFO |main|c.c.s.s.test.InitBeanDemo|ApplicationRunner ActiveProfiles: [sit]

2021-06-07 11:24:44|INFO |main|c.c.s.s.test.InitBeanDemo|CommandLineRunner: {}
2021-06-07 11:24:44|INFO |main|c.c.s.s.test.InitBeanDemo|CommandLineRunner DefaultProfiles: [default]
2021-06-07 11:24:44|INFO |main|c.c.s.s.test.InitBeanDemo|CommandLineRunner ActiveProfiles: [sit]

也即 整篇文章整理的先后顺序。



<think>好的,我现在需要处理用户关于Spring启动时初始化加载注解的问题。首先,用户之前问了关于Map.get如何避免空指针的问题,现在转向了Spring的启动初始化机制,这可能意味着他们在开发中遇到了相关需求,或者想优化应用的启动流程。 我需要先回想Spring中常见的初始化注解,比如@PostConstruct、@Bean的initMethod、InitializingBean接口,还有@EventListener和ApplicationRunner/CommandLineRunner。这些都是常用的方法,应该覆盖到。 用户可能想知道这些注解的区别和使用场景,所以需要分点说明,每个注解或方法的用法、执行顺序以及适用场景。同时,要注意结构清晰,可能用户需要代码示例来更好地理解,所以每个部分都应该有代码示例,并且用代码块展示。 另外,用户可能对执行顺序比较关心,比如哪些注解会在Spring生命周期的哪个阶段执行。比如@PostConstruct是在Bean初始化后执行,而ApplicationRunner是在上下文准备完成后执行。需要明确这些顺序,帮助用户合理安排初始化逻辑。 还需要提到一些高级用法,比如结合@Profile或@Conditional进行条件化初始化,以及如何避免阻塞启动,比如使用@Async异步执行。这些内容能帮助用户处理更复杂的场景。 最后,总结对比不同方法的优缺点,让用户能根据具体需求选择合适的方式。比如@PostConstruct简单但无法处理依赖其他Bean的情况,ApplicationRunner适合访问外部资源等。 需要确保语言简洁明了,避免使用过多专业术语,但又要准确。检查是否有遗漏的重要注解或方法,比如@DependsOn是否相关?不过可能主要关注初始化方法,而@DependsOn是控制Bean的创建顺序,可能不在本次讨论范围内。 另外,用户可能关心如何选择这些方法,所以总结部分需要明确各自的适用场景,帮助他们做决定。同时,提到的第三方库如Guava可能在之前的回答中出现过,但这里主要聚焦Spring自带的方式,不需要涉及第三方库。 最后,确保代码示例正确,没有语法错误,并符合Java和Spring的惯例。比如使用@Component注解,正确的方法命名等。检查是否有拼写错误,比如Bean的生命周期阶段是否正确,比如BeanPostProcessor的处理阶段是否准确。 现在整理这些内容,分点列出,确保每个部分都有说明、示例和注意事项,结构清晰,用户能够快速找到适合自己的方法。</think>在Spring框架中,实现启动时初始化加载的常见方式有以下几种,通过注解或接口控制初始化逻辑的执行时机: --- ### **1. `@PostConstruct` 注解** - **作用**:标记在Bean的初始化方法上,**在依赖注入完成后**执行。 - **执行顺序**:`构造方法` → `依赖注入` → `@PostConstruct` - **示例**: ```java @Component public class MyService { @PostConstruct public void init() { System.out.println("Bean初始化完成,执行资源预加载"); } } ``` - **特点**: - 属于JSR-250规范,需确保项目中包含`javax.annotation-api`依赖。 - **无法处理依赖其他Bean的初始化顺序**(需结合`@DependsOn`)。 --- ### **2. `InitializingBean` 接口** - **作用**:实现`afterPropertiesSet()`方法,在Bean属性设置完成后执行。 - **示例**: ```java @Component public class MyService implements InitializingBean { @Override public void afterPropertiesSet() { System.out.println("属性注入完成,执行初始化"); } } ``` - **特点**: - 与`@PostConstruct`执行时机相同,但属于Spring原生接口。 - **耦合性较高**(需实现接口),推荐优先使用`@PostConstruct`。 --- ### **3. `@Bean` 的 `initMethod`** - **作用**:在配置类中定义Bean时,指定初始化方法。 - **示例**: ```java @Configuration public class AppConfig { @Bean(initMethod = "init") public DataSource dataSource() { return new HikariDataSource(); } } public class HikariDataSource { public void init() { System.out.println("数据库连接池初始化"); } } ``` - **适用场景**:适用于第三方库的Bean初始化控制。 --- ### **4. `ApplicationRunner` 或 `CommandLineRunner`** - **作用**:在**Spring上下文完全就绪后**执行(所有Bean初始化完成)。 - **区别**: - `ApplicationRunner`:参数封装为`ApplicationArguments`,支持键值对解析。 - `CommandLineRunner`:直接接收命令行参数(原始字符串数组)。 - **示例**: ```java @Component public class AppStartupRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) { System.out.println("应用启动完成,执行预加载任务"); } } ``` - **特点**: - 适合执行**全局性初始化任务**(如缓存预热、配置文件检查)。 - 可通过`@Order`注解控制多个Runner的执行顺序。 --- ### **5. `@EventListener` 监听事件** - **作用**:监听Spring的生命周期事件(如`ContextRefreshedEvent`)。 - **示例**: ```java @Component public class MyEventListener { @EventListener(ContextRefreshedEvent.class) public void onContextRefreshed() { System.out.println("上下文刷新完成,执行初始化"); } } ``` - **注意**: - 可能被多次触发(例如热部署时),需添加逻辑判断是否重复执行。 - 与`ApplicationRunner`执行时机相近,但更底层。 --- ### **6. 自定义 `BeanPostProcessor`** - **作用**:通过Bean后置处理器拦截初始化过程(高级用法)。 - **示例**: ```java @Component public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean instanceof MyService) { System.out.println("MyService初始化完成后执行自定义逻辑"); } return bean; } } ``` - **适用场景**:需要精细化控制特定Bean的初始化逻辑。 --- ### **执行顺序总结** ```text 构造方法 → 依赖注入 → @PostConstruct/InitializingBean → BeanPostProcessor → ContextRefreshedEvent → ApplicationRunner/CommandLineRunner ``` --- ### **选择建议** | 方式 | 适用场景 | 优点 | 缺点 | |----------------------|---------------------------------|--------------------------|----------------------| | `@PostConstruct` | 单Bean的简单初始化 | 注解轻量,代码简洁 | 无法控制全局顺序 | | `ApplicationRunner` | 全局启动任务(如缓存预热) | 上下文完全就绪后执行 | 仅执行一次 | | `@EventListener` | 响应上下文刷新事件 | 灵活监听多种事件 | 需处理重复触发问题 | | `BeanPostProcessor` | 对特定Bean进行增强 | 高度定制化 | 实现复杂度高 | --- ### **高级技巧** 1. **条件化初始化** 结合`@Profile`或`@Conditional`实现环境差异化初始化: ```java @Profile("prod") @Component public class ProdInitializer { @PostConstruct public void init() { /* 生产环境专用初始化 */ } } ``` 2. **异步初始化** 使用`@Async`避免阻塞启动流程: ```java @Async @Component public class AsyncInitializer { @PostConstruct public void asyncInit() { /* 后台线程执行 */ } } ``` 3. **依赖顺序控制** 通过`@DependsOn`强制初始化顺序: ```java @DependsOn("databaseConfig") @Component public class CacheLoader { /* 确保databaseConfig先初始化 */ } ```
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值