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


相关文章
|
2月前
|
Java 数据库连接 Maven
springBoot:项目建立&配置修改&yaml的使用&resource 文件夹(二)
本文档介绍了如何创建一个基于Maven的项目,并配置阿里云仓库、数据库连接、端口号、自定义启动横幅及多环境配置等。同时,详细说明了如何使用YAML格式进行配置,以及如何处理静态资源和模板文件。文档还涵盖了Spring Boot项目的`application.properties`和`application.yaml`文件的配置方法,包括设置数据库驱动、URL、用户名、密码等关键信息,以及如何通过配置文件管理不同环境下的应用设置。
154 1
|
2月前
|
NoSQL Java MongoDB
Springboot WebFlux项目结合mongodb进行crud
这篇文章介绍了如何使用Spring Boot WebFlux框架结合MongoDB进行基本的CRUD(创建、读取、更新、删除)操作,包括项目设置、实体类和Repository的创建、控制器的实现以及配置文件的编写。
50 0
Springboot WebFlux项目结合mongodb进行crud
|
27天前
|
Java 应用服务中间件
SpringBoot获取项目文件的绝对路径和相对路径
SpringBoot获取项目文件的绝对路径和相对路径
64 1
SpringBoot获取项目文件的绝对路径和相对路径
|
18天前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
31 2
|
22天前
|
分布式计算 关系型数据库 MySQL
SpringBoot项目中mysql字段映射使用JSONObject和JSONArray类型
SpringBoot项目中mysql字段映射使用JSONObject和JSONArray类型 图像处理 光通信 分布式计算 算法语言 信息技术 计算机应用
38 8
|
2月前
|
JavaScript 前端开发 Java
解决跨域问题大集合:vue-cli项目 和 java/springboot(6种方式) 两端解决(完美解决)
这篇文章详细介绍了如何在前端Vue项目和后端Spring Boot项目中通过多种方式解决跨域问题。
367 1
解决跨域问题大集合:vue-cli项目 和 java/springboot(6种方式) 两端解决(完美解决)
|
29天前
|
JavaScript 前端开发 Java
SpringBoot项目的html页面使用axios进行get post请求
SpringBoot项目的html页面使用axios进行get post请求
45 2
|
29天前
|
前端开发 Java Spring
SpringBoot项目thymeleaf页面支持词条国际化切换
SpringBoot项目thymeleaf页面支持词条国际化切换
60 2
|
29天前
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
38 1
|
1月前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。