Spring启动初始化策略对比(相关生命周期)
很多时候我们需要在启动时就加载些资源,初始化一些内容。比如一些大数据的常用内容,或许要提前就加载到redis里,或者本地caffeine缓存里等各类实际场景的需求和做法,此时我们就需要考虑下什么时候该准备好
一:先说几个简单常见常用的
@PostConstruct 注解方式;实现 CommandLineRunner 接口方式
他们都是在 Spring Boot 应用程序启动时用于执行初始化逻辑的两种方法,但它们的用法和生命周期稍有不同
-
@PostConstruct:
@PostConstruct是 Java 的标准注解,由 JSR-250 定义,用于在依赖注入完成后立即执行初始化方法。- 当 Spring 完成Bean的实例化、配置和注入以后,就会调用
@PostConstruct标注的方法。 - 用于需要在该 Bean 初始化完成(即依赖注入完成)后立即执行的逻辑。
- 注解在类的一个方法上,该方法无参数且返回值为
void。
@Component public class SomeComponent { @PostConstruct public void init() { // 初始化逻辑 } } -
CommandLineRunner:
CommandLineRunner是 Spring Boot 提供的一个接口,用于在 Spring Boot 应用程序启动后执行一些代码。- 应用程序启动并且 Spring 容器创建和刷新完成后,会执行实现
CommandLineRunner接口的run方法。 - 主要用于在应用程序启动之后,但在 Spring Boot 应用完全就绪之前(例如从命令行读取参数、执行启动时任务)。
@Component public class MyCommandLineRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { // 初始化逻辑 } } -
生命周期区别
@PostConstruct在 Spring 容器创建和配置所有 Bean 的过程中发生的。此时Spring bean容器的所有bean还没有完全初始化并就绪CommandLineRunner在所有 Spring Beans 被完全初始化并就绪后执行,但在 Spring Boot 应用完全就绪之前。这意味着它会在应用启动的最后阶段运行- 因此,@PostConstruct 方法会比 CommandLineRunner 更早执行
二:一些其他方式
-
ApplicationRunner:
- 用途: 与
CommandLineRunner类似,但提供了更丰富的应用参数处理能力。 - 实现方式: 实现
ApplicationRunner接口,并重写run(ApplicationArguments args)方法。 - 代码示例:
import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; @Component public class MyApplicationRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) { // 获取应用程序参数 System.out.println("ApplicationRunner executed!"); } }
- 用途: 与
-
InitializingBean:
- 用途: 提供了一种基于接口的方式,在 Spring 完成 Bean 属性设置后执行自定义初始化逻辑,也就是依赖注入完成之后被调用
- 实现方式: 实现
InitializingBean接口,并重写afterPropertiesSet()方法。 - 代码示例:
import org.springframework.beans.factory.InitializingBean; import org.springframework.stereotype.Component; @Component public class MyInitializingBean implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { System.out.println("InitializingBean executed after properties set!"); } }
-
@EventListener注解 和ApplicationListener:-
用途: 监听 Spring 应用上下文事件,如
ApplicationReadyEvent,并在事件触发后执行逻辑。 -
@EventListener实现方法:import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import org.springframework.boot.context.event.ApplicationReadyEvent; @Component public class MyEventListener { @EventListener public void onApplicationReady(ApplicationReadyEvent event) {//当然你也可以自定义一个方法名,参数类,比如继承ApplicationEvent的子类来实现业务关联的字段内容等 System.out.println("Application is ready!"); } } -
ApplicationListener 实现方法:
import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; @Component public class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { System.out.println("Context refreshed!"); } }
-
-
@Scheduled:- 用途: 用于定时任务,但可以配置为在启动时执行。
- 实现方式: 配置一个只执行一次的定时任务。
- 代码示例:
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component public class MyScheduledTask { @Scheduled(initialDelay = 0, fixedDelay = Long.MAX_VALUE) public void onStartup() { System.out.println("Scheduled task executed on startup!"); } }
-
Bean Initialization Methods:
- 用途: 使用配置文件或注解指定 Bean 的初始化方法。
- 实现方式:
- XML 配置:
<bean id="myBean" class="com.example.MyBean" init-method="init"/> - Java 配置:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AppConfig { @Bean(initMethod = "init") public MyBean myBean() { return new MyBean(); } }
- XML 配置:
-
ApplicationContextInitializer:- 用途: 在 Spring 应用上下文刷新之前进行初始化。
- 实现方式: 实现
ApplicationContextInitializer接口,并在应用启动配置中注册。 - 代码示例:
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext context) { System.out.println("ApplicationContextInitializer executed!"); } } - 注册方法(通常在
SpringApplication中):import org.springframework.boot.SpringApplication; public class MySpringApplication { public static void main(String[] args) { SpringApplication app = new SpringApplication(MySpringApplication.class); app.addInitializers(new MyApplicationContextInitializer()); app.run(args); } }
三:简述下顺序
鉴于上面的内容,简单概述下部分顺序
-
Spring Boot 启动:
- Spring Boot 应用开始启动,启动类一般是一个带有
@SpringBootApplication注解的类。
- Spring Boot 应用开始启动,启动类一般是一个带有
-
Spring 容器初始化:
- Spring 应用上下文(
ApplicationContext)被创建并初始化。 - 所有 Spring Beans 被实例化、依赖注入、初始化的过程。
- 如果 某些 Bean 实现了
InitializingBean接口或使用了@PostConstruct注解,则执行相关初始化逻辑。
- Spring 应用上下文(
-
Commands 执行 (CommandLineRunner and ApplicationRunner):
CommandLineRunner和ApplicationRunner实例的run()方法被调用。- 这是你可以在应用启动时执行定制逻辑的地方,比如清理或加载初始数据、启动后台任务等。
CommandLineRunner接收从命令行传递到应用的参数,这些参数可以用于配置启动行为。
-
Spring Boot 应用准备就绪:
- 事件如
ApplicationReadyEvent被发布,表示应用程序完全准备好接受请求或执行其他业务操作。
- 事件如
因此,CommandLineRunner 提供了一个合适的钩子,在你想在应用程序完全启动并准备好对外提供服务之前,执行自定义代码或准备工作时使用。
简单常用的基本是@PostConstruct,CommandLineRunner或ApplicationRunner
四:稍微扩展
前面说到
InitializingBean接口,他定义了一个单一的方法 afterPropertiesSet(),实现该接口的Bean会在 实例化 -> 初始化(依赖注入配置等)后立即执行。
但是如果我们难道要每次每个bean需要特殊处理时,都要单独实现该接口么? 显然不是
BeanPostProcessor也是 Spring 框架中用于管理和控制 bean 生命周期的一个接口,但是他可以集中管理。
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.stereotype.Component;
/**
* 我举个例子:某些Bean在初始化后,我需要把他们交给代理,从而触发我的拦截(实现Spring AOP中的org.aopalliance.intercept.MethodInterceptor接口的拦截类)
* 可以发现BeanPostProcessor接口定义的postProcessAfterInitialization方法参数是Object bean, String beanName
* 所以 BeanPostProcessor 接口被实现后,其他所有的被交给Spring Bean容器管理的bean,在实例化初始化的阶段都会去调用BeanPostProcessor实现类的重写方法
*/
//该类本身也要交给Spring管理
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (shouldIntercept(beanName)) { // 判断是否需要拦截
ProxyFactory proxyFactory = new ProxyFactory(bean);
proxyFactory.addAdvice(new MyMethodInterceptor());
return proxyFactory.getProxy();
}
return bean;
}
private boolean shouldIntercept(String beanName) {
// 在这里决定哪些 bean 需要被拦截
return "myTargetBeanName".equals(beanName);
}
}
由此可见,BeanPostProcessor接口并不像InitializingBean一般只关注自身实现该接口的bean生命周期。而是所有Bean的生命周期。
他有两个主要方法:
postProcessBeforeInitialization(Object bean, String beanName): 在 bean 初始化之前调用。可以在这个方法中对 bean 进行一些自定义的处理。postProcessAfterInitialization(Object bean, String beanName): 在 bean 初始化之后调用。同样地,这个方法可以用来对 bean 进行一些调整或包装操作。
/**
* 当然了,也可以使用CGLIB 的 org.springframework.cglib.proxy.MethodInterceptor 接口
*/
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 检查是否是目标类,TargetClass换成目标类,CGLIB代理是针对类的
if (bean instanceof TargetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(bean.getClass());
enhancer.setCallback(new MyMethodInterceptor(bean));
return enhancer.create();
}
return bean;
}
private static class MyMethodInterceptor implements MethodInterceptor {
private final Object target;
public MyMethodInterceptor(Object target) {
this.target = target;
}
//Spring AOP 的MethodInterceptor是重写 invoke
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 在方法调用之前执行的逻辑
System.out.println("Before method: " + method.getName());
// 调用原始方法
Object result = proxy.invoke(target, args);
// 在方法调用之后执行的逻辑
System.out.println("After method: " + method.getName());
return result;
}
}
}
相关拦截和切面可以点击跳转简单讲一点切面

浙公网安备 33010602011771号