Spring启动初始化策略对比(相关生命周期)

很多时候我们需要在启动时就加载些资源,初始化一些内容。比如一些大数据的常用内容,或许要提前就加载到redis里,或者本地caffeine缓存里等各类实际场景的需求和做法,此时我们就需要考虑下什么时候该准备好

一:先说几个简单常见常用的

@PostConstruct 注解方式;实现 CommandLineRunner 接口方式

他们都是在 Spring Boot 应用程序启动时用于执行初始化逻辑的两种方法,但它们的用法和生命周期稍有不同

  1. @PostConstruct:

    • @PostConstruct 是 Java 的标准注解,由 JSR-250 定义,用于在依赖注入完成后立即执行初始化方法。
    • 当 Spring 完成Bean的实例化、配置和注入以后,就会调用@PostConstruct标注的方法。
    • 用于需要在该 Bean 初始化完成(即依赖注入完成)后立即执行的逻辑。
    • 注解在类的一个方法上,该方法无参数且返回值为 void
    @Component
    public class SomeComponent {
    
        @PostConstruct
        public void init() {
            // 初始化逻辑
        }
    }
    
  2. 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 {
            // 初始化逻辑
        }
    }
    
  3. 生命周期区别

    • @PostConstruct 在 Spring 容器创建和配置所有 Bean 的过程中发生的。此时Spring bean容器的所有bean还没有完全初始化并就绪
    • CommandLineRunner 在所有 Spring Beans 被完全初始化并就绪后执行,但在 Spring Boot 应用完全就绪之前。这意味着它会在应用启动的最后阶段运行
    • 因此,@PostConstruct 方法会比 CommandLineRunner 更早执行

二:一些其他方式

  1. 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!");
          }
      }
      
  2. 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!");
          }
      }
      
  3. @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!");
          }
      }
      
  4. @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!");
          }
      }
      
  5. 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();
            }
        }
        
  6. 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);
          }
      }
      

三:简述下顺序

鉴于上面的内容,简单概述下部分顺序

  1. Spring Boot 启动

    • Spring Boot 应用开始启动,启动类一般是一个带有 @SpringBootApplication 注解的类。
  2. Spring 容器初始化

    • Spring 应用上下文(ApplicationContext)被创建并初始化。
    • 所有 Spring Beans 被实例化、依赖注入、初始化的过程。
    • 如果 某些 Bean 实现了 InitializingBean 接口或使用了 @PostConstruct 注解,则执行相关初始化逻辑。
  3. Commands 执行 (CommandLineRunner and ApplicationRunner)

    • CommandLineRunnerApplicationRunner 实例的 run() 方法被调用。
    • 这是你可以在应用启动时执行定制逻辑的地方,比如清理或加载初始数据、启动后台任务等。
    • CommandLineRunner 接收从命令行传递到应用的参数,这些参数可以用于配置启动行为。
  4. 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;
        }
    }
}

相关拦截和切面可以点击跳转简单讲一点切面

posted @ 2024-11-29 11:17  J九木  阅读(59)  评论(0)    收藏  举报