springboot项目常用的初始化方式,看看你知道几个?

简介: 平常的项目开发中,经常会遇到数据初始化的需求,比如说在项目启动之后需要读取自定义的配置信息、初始化自定义对象信息等等,那springboot项目中进行初始化方式有哪些,今天就一起来聊一下.为方便小伙伴查阅,第二个章节已经将各种方式进行了实现,需要用到的小伙伴可以直接拿去用。至于为什么能那样做,翻阅了相关官方文档,会做出简要说明,感兴趣的小伙伴可以看下第三个章节。

一、前言


    平常的项目开发中,经常会遇到数据初始化的需求,比如说在项目启动之后需要读取自定义的配置信息、初始化自定义对象信息等等,那springboot项目中进行初始化方式有哪些,今天就一起来聊一下.为方便小伙伴查阅,第二个章节已经将各种方式进行了实现,需要用到的小伙伴可以直接拿去用。至于为什么能那样做,翻阅了相关官方文档,会做出简要说明,感兴趣的小伙伴可以看下第三个章节。


二、springboot项目初始化方式实战篇


1.ApplicationRunner

@Component
public class PersonalApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("spring容器初始化方式:ApplicationRunner");
    }
}

2.CommandLineRunner

@Component
public class PersonalCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("spring容器初始化方式:CommandLineRunner");
    }
}

3.ApplicationListener监听ContextRefreshedEvent事件

@Component
public class PersonalContextRefreshedEvent implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        System.out.println("spring容器初始化方式:ApplicationListener<ContextRefreshedEvent>");
    }
}

4.InitializingBean

@Configuration
public class PersonalInitializingBean implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("spring容器初始化方式:PersonalInitializingBean");
    }
}


5.@PostConstruct

@Configuration
public class PersonalInitializingBean implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("spring容器初始化方式:PersonalInitializingBean");
    }
}


6.静态代码块

@Component
public class PersonalStacticInit {
    static {
        System.out.println("静态代码块执行。。。。。。。。");
    }
}


二、各种初始化操作的执行顺序


项目启动,控制台输出内容如下:

9da306dcf12ba6ded0a7fde8fd8a93f5_52cf432e53dd4da3b5a858333258f1c2.png


三、从官方文档看各种初始化操作


1.ApplicationRunner与CommandLineRunner

    springboot项目启动类中run方法执行逻辑实际上是执行SpringApplication中run方法:


public ConfigurableApplicationContext run(String... args) {
        //计时工具
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        //第一步,获取并启动监听器
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();
        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //第二步,根据SpringApplicationRunListeners以及参数来准备环境
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            //准备Banner打印器‐就是启动Spring Boot的时候在console上的ASCII艺术字体
            Banner printedBanner = this.printBanner(environment);
            //第三步:创建Spring容器
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            //第四步:Spring容器前置处理
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            //第五步:刷新容器
            this.refreshContext(context);
            //第六步:Spring容器后置处理
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
            //第七步:遍历容器中的监听器,执行监听器逻辑
            listeners.started(context);
            //第八步:执行Runners
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }
        try {
        // 第九步:遍历容器中的监听器,执行监听器逻辑
            listeners.running(context);
            //返回容器
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }


代码看的不直观看下启动的流程图:

ab331e7d83c96e1149fff2eb6f3c3602_72e0c2f1549f4a19aa402abdb1a8b78d.png


    其中第8的个步骤是执行Runners,具体到代码:callRunners(context, applicationArguments)就是调用ApplicationRunner或是CommandLineRunner,具体调用逻辑如下:

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    // 组装runner
        List<Object> runners = new ArrayList();
  // 组装ApplicationRunner类型的runner       
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        // 组装CommandLineRunner类型的runner  
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        // 组装的runner集合进行排序
        AnnotationAwareOrderComparator.sort(runners);
        Iterator var4 = (new LinkedHashSet(runners)).iterator();
        while(var4.hasNext()) {
            Object runner = var4.next();
            // 执行ApplicationRunner实现类逻辑
            if (runner instanceof ApplicationRunner) {
                this.callRunner((ApplicationRunner)runner, args);
            }
    // 执行CommandLineRunner实现类逻辑
            if (runner instanceof CommandLineRunner) {
                this.callRunner((CommandLineRunner)runner, args);
            }
        }
    }

    callRunner方法用来执行CommandLineRunner或是ApplicationRunner实现类的run逻辑内容,具体实现如下:

private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
  try {
    (runner).run(args);
  }
  catch (Exception ex) {
    throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
  }
  }


看到这里会发现有ApplicationRunner、CommandLineRunner,下面分别说下两个的区别:


ApplicationRunner 中run方法


public interface ApplicationRunner {
    void run(ApplicationArguments var1) throws Exception;
}


看下官方对的说明:


Interface used to indicate that a bean should run when it is contained within a {@link SpringApplication}.

Multiple {@link ApplicationRunner} beans can be defined within the same application context and can be ordered using the {@link Ordered} interface or {@link Order @Order} annotation.


翻译一下就是:


ApplicationRunner这个接口主要用于执行bean,当bean在SpringApplication中.

SpringApplication应该不陌生,springboot项目启动类中main方法就是执行的SpringApplication.run().所以说ApplicationRunner就是为了springboot项目启动过程中执行指定业务逻辑准备的接口.

如果存在多个ApplicationRunner接口的实现类,可以通过@Ordered注解指定执行顺序.


看下CommandLineRunner官方说明:


Interface used to indicate that a bean should run when it is

contained within a {@link SpringApplication}. Multiple {@link

CommandLineRunner} beans can be defined within the same application context and can be ordered using the {@link Ordered} interface or {@link Order @Order} annotation. If you need access to {@link ApplicationArguments} instead of the raw String array consider using {@link ApplicationRunner}.


    看完会发现关于CommandLineRunner的说明和ApplicationRunner基本一致,也是用于springboot项目启动用于执行指定业务逻辑准备的接口.唯一的区别就是传递参数不同,官方文档中还明确说明,如果在想获取ApplicationArguments(项目参数信息),推荐使用ApplicationRunner.

CommandLineRunner中run方法

public interface CommandLineRunner {
    void run(String... var1) throws Exception;
}


2.ApplicationListener监听ContextRefreshedEvent事件使用说明

    上文中讲到SpringApplication中run方法,其中第五步是刷新容器,具体刷新的业务处理是在AbstractApplicationContext中refresh方法中完成,其中最后一个操作就是执行发布容器中的事件. AbstractApplicationContext中refresh方法

public void refresh() throws BeansException, IllegalStateException {
  synchronized (this.startupShutdownMonitor) {
    // 省略部分逻辑
    // 发布容器中定义的各种事件
    finishRefresh();
    }

AbstractApplicationContext中finishRefresh具体的执行逻辑如下:


protected void finishRefresh() {
  // 省略部分逻辑
  // 发布ContextRefreshedEvent事件
  publishEvent(new ContextRefreshedEvent(this));
  // 省略部分逻辑
  }


继续往下看,最终调用的监听事件逻辑:

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
  try {
    // 执行监听器实现类中onApplicationEvent具体实现逻辑
    listener.onApplicationEvent(event);
  }
  catch (ClassCastException ex) {
    // 省略部分逻辑
  }
  }


    总结一下:项目启动过程中需要进行容器刷新操作,在容器刷新结束之后会发布ContextRefreshedEvent事件,自定义的监听器监听到该事件就会执行自定义的业务逻辑处理.也就想做数据初始化自定义监听器,要在实现ApplicationListener的时候必须指定监听事件为ContextRefreshedEvent的原因.


3.InitializingBean使用说明

    关于InitializingBean看下官方介绍:

Interface to be implemented by beans that need to react once all their properties have been set by a {@link BeanFactory}: e.g. to perform custom initialization, or merely to check that all mandatory

properties have been set.


用自己的话翻译一下:

    InitializingBean接口用于执行一次当bean的各种属性设置完成之后.每个bean对象会经历实例化、初始化的过程,初始化过程可以简单理解为设置属性过程,适用场景:执行自定义初始化设置或是检查属性是否被设置.当然目前使用到最多的是初始化设置.

首先看下bean创建的核心逻辑:AbstractAutowireCapableBeanFactory中doCreateBean方法:


protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
    throws BeanCreationException {
  // 省略部分逻辑
  // 处理bean的依赖注入
  populateBean(beanName, mbd, instanceWrapper);
  // 实例化bean处理    
  exposedObject = initializeBean(beanName, exposedObject, mbd);
  // 省略部分逻辑
  return exposedObject;
  }


继续看initializeBean中的执行逻辑,最终执行的是初始化方法invokeInitMethods,AbstractAutowireCapableBeanFactory中initializeBean方法:

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
  // 省略部分逻辑
  // 执行初始化方法
  invokeInitMethods(beanName, wrappedBean, mbd);
  // 省略部分逻辑
  return wrappedBean;
  }


看下invokeInitMethods方法执行的具体逻辑就会发现此逻辑实际上执行的就是InitializingBean中的afterPropertiesSet,AbstractAutowireCapableBeanFactory中invokeInitMethods方法:

protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)
    throws Throwable {
    // 省略部分逻辑
    // SecurityManager是JAVA安全管理器当运行未知的Java程序的时候,该程序可能有恶意代码(删除系统文件、重启系统等),为了防止运行恶意代码对系统产生影响,需要对运行的代码的权限进行控制,这时候就要启用Java安全管理器。
    if (System.getSecurityManager() != null) {
    AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
      ((InitializingBean) bean).afterPropertiesSet();
      return null;
      }, getAccessControlContext());
    }
    else {
    ((InitializingBean) bean).afterPropertiesSet();
    }
  }
  // 省略部分逻辑
  }


也就是说InitializingBean中的afterPropertiesSet会在对象实例化设置完成属性之后才会执行.


4.@PostConstruct使用说明

bean创建的核心逻辑:

4114796456ffb2e87241cd6992d986b6_7c0a8050fcc740febae0d0e9e9954af4.png

AbstractAutowireCapableBeanFactory中initializeBean方法:

protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
    // ...
    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
        // 初始化前置处理
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }
    try {
        // 调用初始化方法
        invokeInitMethods(beanName, wrappedBean, mbd);
    } catch (Throwable ex) {
        // ...
    }
    if (mbd == null || !mbd.isSynthetic()) {
        // 初始化后置处理
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }
    return wrappedBean;
}


调试可知@PostConstruct是在applyBeanPostProcessorsBeforeInitialization初始化前置中进行处理的,看下具体实现:

@Override
  public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
    throws BeansException {
  Object result = existingBean;
  for (BeanPostProcessor processor : getBeanPostProcessors()) {
    Object current = processor.postProcessBeforeInitialization(result, beanName);
    if (current == null) {
    return result;
    }
    result = current;
  }
  return result;
  }


将各种容器中各种BeanPostProcessors后置处理器进行遍历,依次执行前置初始化逻辑,@PostConstruct是由InitDestroyAnnotationBeanPostProcessor中进行的初始化处理,看这个后置处理器的名称就可以看出是用于处理初始化以及销毁操作的后置处理器.看下具体的实现:

@Override
  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
     // 解析元数据
  LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
  // 反射执行@PostConstruct或是@PreDestroy所在的方法
  metadata.invokeInitMethods(bean, beanName);
  return bean;
  }


具体解析的逻辑在buildLifecycleMetadata中


private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) {
  List<LifecycleElement> initMethods = new ArrayList<>();
  List<LifecycleElement> destroyMethods = new ArrayList<>();
  Class<?> targetClass = clazz;
  do {
    final List<LifecycleElement> currInitMethods = new ArrayList<>();
    final List<LifecycleElement> currDestroyMethods = new ArrayList<>();
    ReflectionUtils.doWithLocalMethods(targetClass, method -> {
    if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) {
      LifecycleElement element = new LifecycleElement(method);
      currInitMethods.add(element);
    }
    if (this.destroyAnnotationType != null && method.isAnnotationPresent(this.destroyAnnotationType)) {
      currDestroyMethods.add(new LifecycleElement(method));
    }
    });
    // 省略部分逻辑
  }
  }


可以看出来这里主要是用initAnnotationType或是destroyAnnotationType进行组装参数,至于这两个具体是什么可以继续往下看怎么设置的,以initAnnotationType为例,initAnnotationType进行设置的逻辑是

public void setDestroyAnnotationType(Class<? extends Annotation> destroyAnnotationType) {
  this.destroyAnnotationType = destroyAnnotationType;
  }


具体调用的逻辑发现会在

CommonAnnotationBeanPostProcessor创建过程中会初始化initAnnotationType

public CommonAnnotationBeanPostProcessor() {
  setOrder(Ordered.LOWEST_PRECEDENCE - 3);
  setInitAnnotationType(PostConstruct.class);
  setDestroyAnnotationType(PreDestroy.class);
  ignoreResourceType("javax.xml.ws.WebServiceContext");
}


    关于@PostConstruct执行原理总结一下:spring项目启动加载bean过程中会将带有@PostConstruct、@PreDestroy的bean信息或是方法信息注册到InitDestroyAnnotationBeanPostProcessor中的initAnnotationType或是destroyAnnotationType,然后InitDestroyAnnotationBeanPostProcessor中postProcessBeforeInitialization会查找出带有@PostConstruct、@PreDestroy注解的方法信息,然后用反射的方式执行方法中的逻辑。

    以上是关于springboot中实现初始化操作的常用方式以及各种方式的使用原理说明,小伙伴们如果有其他的实现方式欢迎评论区留言,看完感觉有收获的别忘了点赞或是收藏一下.


相关文章
|
7月前
|
Java 关系型数据库 MySQL
Spring Boot实现第一次启动时自动初始化数据库
在现在的后端开发中,只要是使用关系型数据库,相信SSM架构(Spring Boot + MyBatis)已经成为首选。 不过在我们第一次运行或者部署项目的时候,通常要先手动连接数据库,执行一个SQL文件以创建数据库以及数据库表格完成数据库的初始化工作,这样我们的SSM应用程序才能够正常工作。 这样也对实际部署或者是容器化造成了一些麻烦,必须先手动初始化数据库再启动应用程序。 那能不能让我们的SSM应用程序第一次启动时,自动地帮我们执行SQL文件以完成数据库初始化工作呢? 这样事实上是没问题的,今天就以Spring Boot + MyBatis为例,使用MySQL作为数据库,完成上述的数
|
14天前
|
缓存 前端开发 Java
SpringBoot启动后加载初始化数据
SpringBoot启动后加载初始化数据
|
22天前
|
Java 应用服务中间件
Springboot启动的时候初始化的线程池默认配置tomcat
Springboot启动的时候初始化的线程池默认配置tomcat
13 1
|
1月前
|
Java 容器 Spring
SpringBoot:Bean生命周期自定义初始化和销毁
SpringBoot:Bean生命周期自定义初始化和销毁
|
4月前
|
SQL 前端开发 Java
Hasor【环境搭建 01】SpringBoot集成Dataway接口配置服务(依赖+配置+数据库数据源初始化+注解添加+demo验证测试)
Hasor【环境搭建 01】SpringBoot集成Dataway接口配置服务(依赖+配置+数据库数据源初始化+注解添加+demo验证测试)
73 0
|
6月前
|
Oracle Java 关系型数据库
Spring Boot 3系列之一(初始化项目)
近期,JDK 21正式发布,而Spring Boot 3也推出已有一段时间。作为这两大技术领域的新一代标杆,它们带来了许多令人振奋的新功能和改进。尽管已有不少博客和文章对此进行了介绍,但对于我们这些身处一线的开发人员来说,有些文章和文档可能一看就会,一写就废。因此,为了更深入地理解JDK 21和Spring Boot 3的新特性,以及加深对Java和Spring Boot生态的理解,我们决定通过编写并分享代码,来展示一个简单的项目。我们计划在之后逐步完善这个项目。本文将向您展示如何使用最新版本的Spring Boot和JDK来初始化一个简单的Spring Boot 3程序。
181 1
Spring Boot 3系列之一(初始化项目)
|
10月前
|
XML 监控 NoSQL
【SpringBoot学习笔记 一】SpringBoot基本概念和项目初始化
【SpringBoot学习笔记 一】SpringBoot基本概念和项目初始化
179 0
|
Java API 调度
[Java Framework] SpringBoot几种启动后自动初始化的几种方式
业务需求需要在项目启动之后自动把执行一次方法 (数据初始化或者创建一些调度任务),但是有时候可能不太明确他们的执行顺序,本文就带你梳理一下它们的执行顺序
297 0
[Java Framework] SpringBoot几种启动后自动初始化的几种方式
|
10月前
|
缓存 Java 数据库连接
Spring Boot 系统初始化器详解
Spring Boot 系统初始化器详解
143 0
|
11月前
|
Java 数据库连接 Spring
spring boot 初始化bean的时候自定义逻辑
spring boot 初始化bean的时候自定义逻辑