首页 > 其他分享 >一文详解 springboot 项目启动时异步执行初始化逻辑

一文详解 springboot 项目启动时异步执行初始化逻辑

时间:2023-10-31 11:03:33浏览次数:39  
标签:异步 java springboot 初始化 poolName static import executorService public


你知道的越多,你不知道的越多
点赞再看,养成习惯

文章目录

  • 前言
  • 代码实现
  • 定义异步处理工具类
  • 实现 java 线程池
  • 新建 AppInit 实现 ApplicationRunner 接口完成启动项目时异步数据初始化


前言

前面的工作中,为了提高地区数据的响应时间,需要加载全国区划数据到 redis 中缓存起来,这个过程希望在项目时启动。
由于初始化全国区划到 redis 中这个过程是比较耗时的,所以我们可以考虑使用异步执行的方式去实现。

代码实现

定义异步处理工具类
import com.xymy.common.config.ThreadPoolExecutorUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;

/**
 * <h2>异步处理工具类<h2>
 *
 * @author xymy
 * @create 2023-02-07 16:45
 */
@Slf4j
public enum RunMerger {
    SM;

    public void asyncHandle(String threadName, Runnable ...runs) {
        this.asyncHandle(null, threadName,runs);
    }

    public void asyncHandle(ExecutorService seedPool, String threadName, Runnable ... runs) {
        if (seedPool == null) {
            seedPool = ThreadPoolExecutorUtil.getSeedPool(threadName, runs.length);
        }
        try {
            for (Runnable run : runs) {
                CompletableFuture.runAsync(() -> {
                        run.run();
                    },seedPool).exceptionally(ex -> {
                        log.error("处理异常", ex);
                    return null;
                });
            }
        } finally {
            ThreadPoolExecutorUtil.releaseSeedPool(seedPool);
        }
    }

    public void syncHandle(Runnable ...runs) {
        Arrays.stream(runs).forEach(Runnable::run);
    }

    public void asyncHandle(Runnable ...runs) {
        this.asyncHandle(null, runs);
    }

}
实现 java 线程池
  • 新建 ThreadPoolExecutorUtil 类
import cn.hutool.core.thread.ThreadFactoryBuilder;
import com.xymy.common.utils.DistrKeyGenerator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.*;

/**
 * <h2>线程池<h2>
 *
 * @author xymy
 * @create 2021-11-22 14:24
 */
@Slf4j
public class ThreadPoolExecutorUtil {
    private static Map<String, ExecutorService> executors = new ConcurrentHashMap<>();

    public static ExecutorService getSeedPool(int poolSize) {
        DistrKeyGenerator keyGenerator = new DistrKeyGenerator();
        return getSeedPool(keyGenerator.generateKey() +"-", poolSize);
    }

    public static ExecutorService getSeedPool(String poolName, int poolSize) { // 需手动释放
        if (StringUtils.isBlank(poolName)) {
            DistrKeyGenerator keyGenerator = new DistrKeyGenerator();
            poolName = keyGenerator.generateKey() +"-";
        }
        ExecutorService executorService = executors.get(poolName);
        if (null == executorService) {
            synchronized (ThreadPoolExecutorUtil.class) {
                executorService = executors.get(poolName);
                if (null == executorService) {
                    executorService = init(poolName,poolSize);
                    executors.put(poolName, executorService);
                }
            }
        }
        return executorService;
    }

    public static void releaseSeedPool(ExecutorService executorService) {
        Set<Map.Entry<String, ExecutorService>> entries = executors.entrySet();
        for (Map.Entry<String, ExecutorService> entry : entries) {
            ExecutorService value = entry.getValue();
            if (value == executorService) {
                executors.remove(entry.getKey());
            }
        }
        if (executorService != null) {
            executorService.shutdown();
        }
    }

    public static void releaseSeedPool(String poolName) { // 释放
        ExecutorService executorService = executors.remove(poolName);
        if (executorService != null) {
            executorService.shutdown();
        }
    }

    private ThreadPoolExecutorUtil(){}


    private static ExecutorService init(String poolName, int poolSize) {
        ThreadFactoryBuilder threadFactoryBuilder = new ThreadFactoryBuilder();
        threadFactoryBuilder.setUncaughtExceptionHandler((t, e) -> log.error( "线程[{}]异常:", t.getName(), e));
        threadFactoryBuilder.setNamePrefix("apply-pool-" + poolName);
        threadFactoryBuilder.setDaemon(false);
        return new ThreadPoolExecutor(poolSize,
                poolSize,0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(poolSize*10),
                threadFactoryBuilder.build(),
                new ThreadPoolExecutor.CallerRunsPolicy());
    }

}
  • 新建 DistrKeyGenerator 类,用来生成主键
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.1.1-jre</version>
</dependency>
import com.google.common.base.Preconditions;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

/**
 * 主键生成
 * sharding-jdbc核心源码
 * @author xymy
 * @create 2019-07-24 09:37
 */
@Slf4j
@Component
public class DistrKeyGenerator {

    public static final long EPOCH;
    // 自增序列长度
    private static final long SEQUENCE_BITS = 12L;
    // workId的长度(单位是位时长度)
    private static final long WORKER_ID_BITS = 10L;

    private static final long SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1;

    private static final long WORKER_ID_LEFT_SHIFT_BITS = SEQUENCE_BITS;

    private static final long TIMESTAMP_LEFT_SHIFT_BITS = WORKER_ID_LEFT_SHIFT_BITS + WORKER_ID_BITS;
    // 位运算计算workerId的最大值(workerId占10位,那么1向左移10位就是workerId的最大值)
    private static final long WORKER_ID_MAX_VALUE = 1L << WORKER_ID_BITS;


    private long workerId = 0;
    // EPOCH就是起始时间,从2016-11-01 00:00:00开始的毫秒数
    static {
        Calendar calendar = Calendar.getInstance();
        calendar.set(2016, Calendar.NOVEMBER, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        EPOCH = calendar.getTimeInMillis();
    }

    private long sequence;

    private long lastTime;

    public DistrKeyGenerator() {

    }

    public DistrKeyGenerator(long workerId) {
        Preconditions.checkArgument(workerId >= 0L && workerId < WORKER_ID_MAX_VALUE);
        this.workerId = workerId;
    }

    /**
     * Generate key.
     * 生成id
     * @return key type is @{@link Long}.
     */
    public synchronized long generateKey() {
        long currentMillis = getCurrentMillis();
        // 每次取分布式唯一ID的时间不能少于上一次取时的时间
        Preconditions.checkState(lastTime <= currentMillis, "Clock is moving backwards, last time is %d milliseconds, current time is %d milliseconds", lastTime, currentMillis);
        // 如果同一毫秒范围内,那么自增,否则从0开始
        if (lastTime == currentMillis) {
            // 如果自增后的sequence值超过4096,那么等待直到下一个毫秒
            if (0L == (sequence = ++sequence & SEQUENCE_MASK)) {
                currentMillis = waitUntilNextTime(currentMillis);
            }
        } else {
            sequence = 0;
        }
        // 更新lastTime的值,即最后一次获取分布式唯一ID的时间
        lastTime = currentMillis;
        if (log.isDebugEnabled()) {
            log.debug("{}-{}-{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(lastTime)), workerId, sequence);
        }
        // 从这里可知分布式唯一ID的组成部分;
        return ((currentMillis - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (workerId << WORKER_ID_LEFT_SHIFT_BITS) | sequence;
    }
    // 获取下一毫秒的方法:死循环获取当前毫秒与lastTime比较,直到大于lastTime的值;
    private long waitUntilNextTime(final long lastTime) {
        long time = getCurrentMillis();
        while (time <= lastTime) {
            time = getCurrentMillis();
        }
        return time;
    }

    /**
     * Get current millis.
     *
     * @return current millis
     */
    private long getCurrentMillis() {
        return System.currentTimeMillis();
    }

}
新建 AppInit 实现 ApplicationRunner 接口完成启动项目时异步数据初始化
@Component
@Slf4j
public class AppInit implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) {

        RunMerger.SM.asyncHandle(() -> {
            log.info("项目启动完成,开始初始化全国区划数据...");
            //省略数据初始化的代码,此处为耗时的操作
            String countryCode = "xxxxx";
            xxService.initAreaInfoList(countryCode);
            log.info("项目启动完成,完成初始化全国区划数据...");
        });
    }
}

至此,就可以实现 springboot 项目启动时异步执行初始化逻辑。


标签:异步,java,springboot,初始化,poolName,static,import,executorService,public
From: https://blog.51cto.com/u_9735550/8102514

相关文章

  • kingbase初始化报错
    [zjh@hs-10-20-30-193Server]$rm-rfdata[zjh@hs-10-20-30-193Server]$./bin/initdb-DdataThefilesbelongingtothisdatabasesystemwillbeownedbyuser"zjh".Thisusermustalsoowntheserverprocess.Thedatabaseclusterwillbeinitializ......
  • Spring Boot 3系列之一(初始化项目)
    近期,JDK21正式发布,而SpringBoot3也推出已有一段时间。作为这两大技术领域的新一代标杆,它们带来了许多令人振奋的新功能和改进。尽管已有不少博客和文章对此进行了介绍,但对于我们这些身处一线的开发人员来说,有些文章和文档可能一看就会,一写就废。因此,为了更深入地理解JDK21和Spr......
  • SpringBoot事件驱动开发
    应用启动过程生命周期事件感知(9大事件)、应用运行中事件感知(无数种)事件发布:ApplicationEventPublisherAware或注入:ApplicationEventMulticaster事件监听:组件+@EventListener场景:当用户登录后,我们需要为用户增加一个积分、随机获取一张优惠券、增加日志等,传统的开发模式......
  • SpringBoot事件和监听器
    事件和监听器生命周期监听场景:监听应用的生命周期监听器-SpringApplicationRunListener自定义SpringApplicationRunListener来监听事件;1.1.编写SpringApplicationRunListener实现类1.2.在META-INF/spring.factories中配置org.springframework.boot.SpringApplication......
  • [Springboot整合thymeleaf]处理js中的路径问题。
    使用了thymeleaf模板引擎之后,html中的标签,都可以直接替换成th:srcth:href但是处理js的中的资源路径并不是像jsp那么简单了。可以通过以下方式解决。<!--处理路径问题--><scriptth:inline="javascript">varpath=[[${#request.contextPath}]]</script><scriptth:inl......
  • 极速指南:在 SpringBoot 中快速集成腾讯云短信功能
    前言今天分享一个SpringBoot集成腾讯云短信的功能,平常除了工作,很多xdm做自己的小项目都可能用到短信,但自己去看文档挺费劲的,我这边就帮你节省时间,直接把步骤给你列出来,照做就行。实战1、申请密钥及签名模板首先,要使用腾讯云短信,你得先在腾讯云有个账号,申请密钥及签名模板。1)......
  • SpringBoot——SSM简单整合v0.1
    学习SpringBoot初次整合SSM,后续需要不断优化参考SpringBoot3教程[1]导入依赖pom.xml<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"......
  • 浅析SpringBoot加载配置的6种方式
    从配置文件中获取属性应该是SpringBoot开发中最为常用的功能之一,但就是这么常用的功能,仍然有很多开发者抓狂~今天带大家简单回顾一下这六种的使用方式:说明Environment对象Environment是springboot核心的环境配置接口,它提供了简单的方法来访问应用程序属性,包括系统属......
  • SpringBoot3特性——错误信息Problemdetails
    SpringFramework6实现了HTTPAPI规范RFC7807的问题详细信息。在本文中,我们将学习如何在SpringBoot3RESTAPI(使用SpringFramework6)中处理异常,并使用ProblemDetailsAPI提供错误响应。详见https://www.sivalabs.in/spring-boot-3-error-reporting-using-proble......
  • springboot Filter @Resource 为空 、@Value 无法读取yml配置的问题
    问题1:在过滤器中使用@Resource为nullSpring中,web应用启动的顺序是:listener->filter->servlet,先初始化listener,然后再来就filter的初始化,再接着才到我们的dispathServlet的初始化,因此,当我们需要在filter里注入一个注解的bean时,就会注入失败,因为filter初始化时,注解的bean还没初......