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


相关文章
|
6月前
|
Java Maven Android开发
微服务——SpringBoot使用归纳——Spring Boot开发环境搭建和项目启动
本文介绍了Spring Boot开发环境的搭建和项目启动流程。主要内容包括:jdk的配置(IDEA、STS/eclipse设置方法)、Spring Boot工程的构建方式(IDEA快速构建、官方构建工具start.spring.io使用)、maven配置(本地maven路径与阿里云镜像设置)以及编码配置(IDEA和eclipse中的编码设置)。通过这些步骤,帮助开发者顺利完成Spring Boot项目的初始化和运行准备。
566 0
微服务——SpringBoot使用归纳——Spring Boot开发环境搭建和项目启动
|
5月前
|
前端开发 安全 Java
Spring Boot 便利店销售系统项目分包设计解析
本文深入解析了基于Spring Boot的便利店销售系统分包设计,通过清晰的分层架构(表现层、业务逻辑层、数据访问层等)和模块化设计,提升了代码的可维护性、复用性和扩展性。具体分包结构包括`controller`、`service`、`repository`、`entity`、`dto`、`config`和`util`等模块,职责分明,便于团队协作与功能迭代。该设计为复杂企业级应用开发提供了实践参考。
224 0
|
6月前
|
Java 测试技术 微服务
微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——少量配置信息的情形
本课主要讲解Spring Boot项目中的属性配置方法。在实际开发中,测试与生产环境的配置往往不同,因此不应将配置信息硬编码在代码中,而应使用配置文件管理,如`application.yml`。例如,在微服务架构下,可通过配置文件设置调用其他服务的地址(如订单服务端口8002),并利用`@Value`注解在代码中读取这些配置值。这种方式使项目更灵活,便于后续修改和维护。
96 0
|
6月前
|
Java 微服务 Spring
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录——使用Logger在项目中打印日志
本文介绍了如何在项目中使用Logger打印日志。通过SLF4J和Logback,可设置不同日志级别(如DEBUG、INFO、WARN、ERROR)并支持占位符输出动态信息。示例代码展示了日志在控制器中的应用,说明了日志配置对问题排查的重要性。附课程源码下载链接供实践参考。
747 0
|
2月前
|
Java 关系型数据库 MySQL
springboot项目集成dolphinscheduler调度器 实现datax数据同步任务
springboot项目集成dolphinscheduler调度器 实现datax数据同步任务
346 2
|
2月前
|
分布式计算 Java 大数据
springboot项目集成dolphinscheduler调度器 可拖拽spark任务管理
springboot项目集成dolphinscheduler调度器 可拖拽spark任务管理
156 2
|
2月前
|
Java 测试技术 Spring
简单学Spring Boot | 博客项目的测试
本内容介绍了基于Spring Boot的博客项目测试实践,重点在于通过测试驱动开发(TDD)优化服务层代码,提升代码质量和功能可靠性。案例详细展示了如何为PostService类编写测试用例、运行测试并根据反馈优化功能代码,包括两次优化过程。通过TDD流程,确保每项功能经过严格验证,增强代码可维护性与系统稳定性。
154 0
|
2月前
|
存储 Java 数据库连接
简单学Spring Boot | 博客项目的三层架构重构
本案例通过采用三层架构(数据访问层、业务逻辑层、表现层)重构项目,解决了集中式开发导致的代码臃肿问题。各层职责清晰,结合依赖注入实现解耦,提升了系统的可维护性、可测试性和可扩展性,为后续接入真实数据库奠定基础。
262 0
|
3月前
|
网络协议 Java
在SpringBoot项目中使用Netty实现远程调用
本文介绍了使用Netty解决网络连接性能问题的方法,重点讲解了Netty的NIO特性及其在SpringBoot中的应用。Netty作为高效的NIO框架,支持非阻塞IO,能通过单线程管理多个客户端连接,简化TCP/UDP套接字服务器开发。文章详细展示了Netty在SpringBoot中实现远程调用的过程,包括服务端与客户端代码实现、依赖配置及测试验证。通过示例代码,如`NettyServer`、`NettyClientUtil`等,清晰说明了Netty的工作原理和实际应用,解决了半包等问题,并提供了完整的测试结果。
509 3
|
8月前
|
XML Java 应用服务中间件
SpringBoot项目打war包流程
本文介绍了将Spring Boot项目改造为WAR包并部署到外部Tomcat服务器的步骤。主要内容包括:1) 修改pom.xml中的打包方式为WAR;2) 排除Spring Boot内置的Tomcat依赖;3) 添加Servlet API依赖;4) 改造启动类以支持WAR部署;5) 打包和部署。通过这些步骤,可以轻松地将Spring Boot应用转换为适合外部Tomcat服务器的WAR包。
474 64
SpringBoot项目打war包流程