Code Ease Code Ease
  • 个人博客网站 (opens new window)
  • 好用的工具网站 (opens new window)
  • Java核心基础
  • 框架的艺术
  • 分布式与微服务
  • 开发经验大全
  • 设计模式
  • 版本新特性
数据库系列
大数据+AI
  • xxl-job
运维与Linux
  • 基于SpringBoot和BootStrap的论坛网址
  • 基于VuePress的个人博客网站
  • 基于SpringBoot开发的小功能
  • 做一个自己的IDEA插件
程序人生
关于我
  • 分类
  • 标签
  • 归档

神秘的鱼仔

你会累是因为你在走上坡路
  • 个人博客网站 (opens new window)
  • 好用的工具网站 (opens new window)
  • Java核心基础
  • 框架的艺术
  • 分布式与微服务
  • 开发经验大全
  • 设计模式
  • 版本新特性
数据库系列
大数据+AI
  • xxl-job
运维与Linux
  • 基于SpringBoot和BootStrap的论坛网址
  • 基于VuePress的个人博客网站
  • 基于SpringBoot开发的小功能
  • 做一个自己的IDEA插件
程序人生
关于我
  • 分类
  • 标签
  • 归档
服务器
  • Java核心基础

  • 框架的艺术

    • Spring

    • Mybatis

    • SpringBoot

      • 如何用SpringBoot(2.3.3版本)快速搭建一个项目
      • 一步步带你看SpringBoot(2.3.3版本)自动装配原理
      • SpringBoot配置文件及自动配置原理详解,这应该是SpringBoot最大的优势了吧
      • SpringBoot整合jdbc、durid、mybatis详解,数据库的连接就是这么简单
      • SpringBoot整合SpringSecurity详解,认证授权从未如此简单
      • SpringBoot整合Shiro详解,还在自己写登陆注册早落伍了
      • SpringBoot如何实现异步、定时任务?
      • 如何在SpringBoot启动时执行初始化操作,两个简单接口就可以实现
        • (一)概述
        • (二)实现方案
          • 2.1 CommandLineRunner
          • 2.2 ApplicationRunner
        • (三)源码分析
        • (四)总结
      • 如何使用SpringBoot写一个属于自己的Starter
      • SpringBoot请求日志,如何优雅地打印
      • 主线程的用户信息,到子线程怎么丢了
    • MQ

    • Zookeeper

    • netty

  • 分布式与微服务

  • 开发经验大全

  • 版本新特性

  • Java
  • 框架的艺术
  • SpringBoot
CodeEase
2023-09-28
目录

如何在SpringBoot启动时执行初始化操作,两个简单接口就可以实现

作者:鱼仔
博客首页: codeease.top (opens new window)
公众号:Java鱼仔

# (一)概述

最近遇到一个功能点,数据库中一张很简单的表有一千多条数据,这里的数据主要做到了值域映射的作用,简单来讲就是我可以通过中文名拿到数据库中对应的code值。原本的实现方式是每次用到之后去查一次sql,虽然不会有什么问题,但是只要是走了网络io,都会消耗时间。所以这个方案需要想办法优化。

优化的方式其实很简单,数据量不多,一千多条数据放在内存里也占不了多少空间。因此完全可以把一次性把数据加载到内存中,后面只需要每次去内存里调用就可以了。

# (二)实现方案

方案想好了就要想实现方式了,想个最直接的方案,在Spring容器初始化时就把这些数据从数据库拿到内存中,后面就直接调用。

SpringBoot中有两个接口能实现该功能:CommandLineRunner和ApplicationRunner。

# 2.1 CommandLineRunner

首先了解一下CommandLineRunner的基本用法,CommandLineRunner可以在系统启动后执行里面的run方法

@Component
public class DataPrepare implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("CommandLineRunner执行数据初始化");
    }
}
1
2
3
4
5
6
7

如果有多个类的话也可以通过@Order注解指定每个类的执行顺序。

接着就可以写代码的实现了,首先定义一个类用来将Mysql的数据存到内存里,通过静态的Map存储

public class DataMap {
    public static Map<String, String> map = new HashMap<>();
    public static void putData(String key, String value) {
        map.put(key, value);
    }
    public static String getDataByKey(String key) {
        return map.get(key);
    }
}
1
2
3
4
5
6
7
8
9

接着在DataPrepare类中将数据都存入到静态到Map中。

@Component
public class DataPrepare implements CommandLineRunner {
    @Autowired
    private DataMapper dataMapper;
    @Override
    public void run(String... args) throws Exception {
        //从数据库中取数据
        List<DataDO> dataDOS = dataMapper.selectList(Wrappers.emptyWrapper());
        //写入到DataMap中
        dataDOS.forEach(item -> DataMap.putData(item.getName(), item.getCode()));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

要使用到时候,只需要调用DataMap.getDataByKey()方法就可以直接使用了。

# 2.2 ApplicationRunner

ApplicationRunner和CommandLineRunner的功能十分相似,实现方式也基本相同。同样继承接口,并实现接口的run方法。

@Component
public class ApplicationDataPrepare implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("ApplicationRunner执行数据初始化");
    }
}
1
2
3
4
5
6
7

在不指定@Order注解的情况下,ApplicationRunner会优先于CommandLineRunner执行。

两者的区别:

CommandLineRunner和ApplicationRunner的功能几乎是相同的,最大的区别在于两者run方法中的入参有所不同,CommandLineRunner通过String数组 来接收启动参数,而ApplicationRunner通过一个ApplicationArguments对象来接收。

在使用时,不管是String数组还是ApplicationArguments都可以拿到JVM的启动参数。

# (三)源码分析

为什么通过实现一个接口,重写run方法就能达到启动程序后就自动执行代码的功能呢?我们可以通过SpringBoot的源码去看:

点进SpringApplication.run()方法,一直进入到public ConfigurableApplicationContext run(String... args)方法中,在执行完一系列初始化方法之后,执行了this.callRunners(context, applicationArguments)方法

8-1.png

callRunners的方法比较简单,首先定义了一个runners集合,并将需要执行的Bean放进去。可以看到ApplicationRunner和CommandLineRunner在这里被放入了runners中,接着对Order注解进行排序,最后遍历执行。

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList();
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    Iterator var4 = (new LinkedHashSet(runners)).iterator();

    while(var4.hasNext()) {
        Object runner = var4.next();
        if (runner instanceof ApplicationRunner) {
            this.callRunner((ApplicationRunner)runner, args);
        }

        if (runner instanceof CommandLineRunner) {
            this.callRunner((CommandLineRunner)runner, args);
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# (四)总结

一个小小的细节可以节约多次的Sql调用。本章主要通过一个简单的例子引出ApplicationRunner和CommandLineRunner,实际在使用时也可以通过懒加载,在第一次使用时将数据塞到静态的Map里,也能实现类似缓存的效果。

上次更新: 2025/04/29, 17:22:06
SpringBoot如何实现异步、定时任务?
如何使用SpringBoot写一个属于自己的Starter

← SpringBoot如何实现异步、定时任务? 如何使用SpringBoot写一个属于自己的Starter→

最近更新
01
AI大模型部署指南
02-18
02
半个月了,DeepSeek为什么还是服务不可用
02-13
03
Python3.9及3.10安装文档
01-23
更多文章>
Theme by Vdoing | Copyright © 2023-2025 备案图标 浙公网安备33021202002405 | 浙ICP备2023040452号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式