关注微信公众号:CodingTechWork,一起学习进步。

介绍

  在项目开发中,经常遇到定时任务,今天通过自定义多线程池总结一下SpringBoot默认实现的定时任务机制。

定时任务模板

pom依赖

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>//线程池用到<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>22.0</version></dependency>//@Slf4j注解用到<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional><version>1.18.4</version></dependency>
</dependencies>

自定义线程池模板

package com.example.andya.demo.conf;import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.concurrent.*;/*** @author Andya* @create 2020-05-29 14:08*/
@Configuration
public class ThreadPoolConfig {public static String THREAD_NAME = "first-thread-pool-%d";public static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2 + 1;public static int MAX_POOL_SIZE = 10;public static int QUEUE_SIZE = 10;/*** 自定义消费队列线程池** @return*/@Bean(value = "firstThreadPool")public ExecutorService buildFirstThreadPool() {ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat(THREAD_NAME).build();/*** 1. CallerRunsPolicy :    这个策略重试添加当前的任务,他会自动重复调用 execute() 方法,直到成功。2. AbortPolicy :         对拒绝任务抛弃处理,并且抛出异常。3. DiscardPolicy :       对拒绝任务直接无声抛弃,没有异常信息。4. DiscardOldestPolicy : 对拒绝任务不抛弃,而是抛弃队列里面等待最久的一个线程,然后把拒绝任务加到队列。*/ExecutorService threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,0L,TimeUnit.SECONDS,new LinkedBlockingQueue<>(QUEUE_SIZE),threadFactory,new ThreadPoolExecutor.AbortPolicy());return threadPool;}
}

定时任务模板

package com.example.andya.demo.service;import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;/*** @author Andya* @create 2020-05-29 14:26*/
@Service
@EnableScheduling
@Slf4j
public class TestThreadPool {@Resource(name = "firstThreadPool")private ExecutorService firstThreadPool;@Scheduled(cron = "0 * * * * *")public void test1SchedulerThreadPool() {final CountDownLatch countDownLatch = new CountDownLatch(5);log.info("Begin schedule-【1】 startTime: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));for (int i = 0; i < 5; i++) {firstThreadPool.execute(() -> {countDownLatch.countDown();log.info("schedule-【1】 , threadPool info: {}, countDownLatch info: {}", firstThreadPool.toString(), countDownLatch.toString());});}try {countDownLatch.await(5, TimeUnit.MINUTES);} catch (InterruptedException e) {log.error("schedule-【1】timeout, {}", e.getMessage());} finally {log.info("schedule-【1】 multi-threading countDownLatch count: {}",  countDownLatch.getCount());}log.info("End schedule-【1】 startTime: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));}@Scheduled(cron = "0 * * * * *")public void test2SchedulerThreadPool() {final CountDownLatch countDownLatch = new CountDownLatch(5);log.info("Begin schedule-【2】 startTime: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));for (int i = 0; i < 5; i++) {firstThreadPool.execute(() -> {countDownLatch.countDown();log.info("schedule-【2】, threadPool info {}, countDownLatch info: {}", firstThreadPool.toString(), countDownLatch.toString());});}try {countDownLatch.await(5, TimeUnit.MINUTES);} catch (InterruptedException e) {log.error("schedule-【2】timeout, {}", e.getMessage());} finally {log.info("schedule-【2】 multi-threading countDownLatch count: {}",  countDownLatch.getCount());}log.info("End schedule-【2】 startTime: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));}
}

运行结果

2020-05-29 15:45:00.011  INFO 6900 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool    : Begin schedule-【2】 startTime: 2020-05-29 15:45:00
2020-05-29 15:45:00.018  INFO 6900 --- [t-thread-pool-0] c.e.andya.demo.service.TestThreadPool    : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 5, active threads = 5, queued tasks = 0, completed tasks = 0], countDownLatch info: java.util.concurrent.CountDownLatch@522af59c[Count = 4]
2020-05-29 15:45:00.019  INFO 6900 --- [t-thread-pool-1] c.e.andya.demo.service.TestThreadPool    : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 5, active threads = 4, queued tasks = 0, completed tasks = 1], countDownLatch info: java.util.concurrent.CountDownLatch@522af59c[Count = 3]
2020-05-29 15:45:00.019  INFO 6900 --- [t-thread-pool-2] c.e.andya.demo.service.TestThreadPool    : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 5, active threads = 3, queued tasks = 0, completed tasks = 2], countDownLatch info: java.util.concurrent.CountDownLatch@522af59c[Count = 2]
2020-05-29 15:45:00.019  INFO 6900 --- [t-thread-pool-3] c.e.andya.demo.service.TestThreadPool    : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 5, active threads = 2, queued tasks = 0, completed tasks = 3], countDownLatch info: java.util.concurrent.CountDownLatch@522af59c[Count = 1]
2020-05-29 15:45:00.019  INFO 6900 --- [t-thread-pool-4] c.e.andya.demo.service.TestThreadPool    : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 5, active threads = 1, queued tasks = 0, completed tasks = 4], countDownLatch info: java.util.concurrent.CountDownLatch@522af59c[Count = 0]
2020-05-29 15:45:00.019  INFO 6900 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool    : schedule-【2】 multi-threading countDownLatch count: 0
2020-05-29 15:45:00.019  INFO 6900 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool    : End schedule-【2】 startTime: 2020-05-29 15:45:00
2020-05-29 15:45:00.020  INFO 6900 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool    : Begin schedule-【1】 startTime: 2020-05-29 15:45:00
2020-05-29 15:45:00.021  INFO 6900 --- [t-thread-pool-0] c.e.andya.demo.service.TestThreadPool    : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 9, active threads = 5, queued tasks = 0, completed tasks = 5], countDownLatch info: java.util.concurrent.CountDownLatch@5ced01a9[Count = 4]
2020-05-29 15:45:00.023  INFO 6900 --- [t-thread-pool-5] c.e.andya.demo.service.TestThreadPool    : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 9, active threads = 4, queued tasks = 0, completed tasks = 6], countDownLatch info: java.util.concurrent.CountDownLatch@5ced01a9[Count = 3]
2020-05-29 15:45:00.024  INFO 6900 --- [t-thread-pool-6] c.e.andya.demo.service.TestThreadPool    : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 9, active threads = 3, queued tasks = 0, completed tasks = 7], countDownLatch info: java.util.concurrent.CountDownLatch@5ced01a9[Count = 2]
2020-05-29 15:45:00.024  INFO 6900 --- [t-thread-pool-7] c.e.andya.demo.service.TestThreadPool    : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 9, active threads = 3, queued tasks = 0, completed tasks = 7], countDownLatch info: java.util.concurrent.CountDownLatch@5ced01a9[Count = 1]
2020-05-29 15:45:00.025  INFO 6900 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool    : schedule-【1】 multi-threading countDownLatch count: 0
2020-05-29 15:45:00.025  INFO 6900 --- [t-thread-pool-8] c.e.andya.demo.service.TestThreadPool    : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 9, active threads = 3, queued tasks = 0, completed tasks = 7], countDownLatch info: java.util.concurrent.CountDownLatch@5ced01a9[Count = 0]
2020-05-29 15:45:00.025  INFO 6900 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool    : End schedule-【1】 startTime: 2020-05-29 15:45:00

从上述结果中可以看出,虽然是test1SchedulerThreadPool()test2SchedulerThreadPool()都是每分钟执行定时任务,但是明显两个方法没有并发执行,而是串行执行的。

并发定时器模板

通过增加一个配置类来并发执行定时任务。

package com.example.andya.demo.conf;import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;import java.util.concurrent.Executors;/*** @author Andya* @create 2020-05-29 15:52*/
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {@Overridepublic void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {scheduledTaskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));}
}

运行结果

2020-05-29 15:53:00.005  INFO 3028 --- [pool-2-thread-2] c.e.andya.demo.service.TestThreadPool    : Begin schedule-【2】 startTime: 2020-05-29 15:53:00
2020-05-29 15:53:00.015  INFO 3028 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool    : Begin schedule-【1】 startTime: 2020-05-29 15:53:00
2020-05-29 15:53:00.019  INFO 3028 --- [t-thread-pool-0] c.e.andya.demo.service.TestThreadPool    : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 9, queued tasks = 1, completed tasks = 0], countDownLatch info: java.util.concurrent.CountDownLatch@630f649f[Count = 3]
2020-05-29 15:53:00.020  INFO 3028 --- [t-thread-pool-3] c.e.andya.demo.service.TestThreadPool    : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 9, queued tasks = 1, completed tasks = 0], countDownLatch info: java.util.concurrent.CountDownLatch@630f649f[Count = 1]
2020-05-29 15:53:00.019  INFO 3028 --- [t-thread-pool-1] c.e.andya.demo.service.TestThreadPool    : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 9, queued tasks = 1, completed tasks = 0], countDownLatch info: java.util.concurrent.CountDownLatch@630f649f[Count = 3]
2020-05-29 15:53:00.019  INFO 3028 --- [t-thread-pool-2] c.e.andya.demo.service.TestThreadPool    : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 9, queued tasks = 1, completed tasks = 0], countDownLatch info: java.util.concurrent.CountDownLatch@630f649f[Count = 2]
2020-05-29 15:53:00.021  INFO 3028 --- [t-thread-pool-0] c.e.andya.demo.service.TestThreadPool    : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 6, queued tasks = 0, completed tasks = 4], countDownLatch info: java.util.concurrent.CountDownLatch@5ebb120e[Count = 4]
2020-05-29 15:53:00.021  INFO 3028 --- [t-thread-pool-4] c.e.andya.demo.service.TestThreadPool    : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 5, queued tasks = 0, completed tasks = 5], countDownLatch info: java.util.concurrent.CountDownLatch@630f649f[Count = 0]
2020-05-29 15:53:00.021  INFO 3028 --- [pool-2-thread-2] c.e.andya.demo.service.TestThreadPool    : schedule-【2】 multi-threading countDownLatch count: 0
2020-05-29 15:53:00.022  INFO 3028 --- [pool-2-thread-2] c.e.andya.demo.service.TestThreadPool    : End schedule-【2】 startTime: 2020-05-29 15:53:00
2020-05-29 15:53:00.022  INFO 3028 --- [t-thread-pool-5] c.e.andya.demo.service.TestThreadPool    : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 4, queued tasks = 0, completed tasks = 6], countDownLatch info: java.util.concurrent.CountDownLatch@5ebb120e[Count = 3]
2020-05-29 15:53:00.022  INFO 3028 --- [t-thread-pool-6] c.e.andya.demo.service.TestThreadPool    : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 3, queued tasks = 0, completed tasks = 7], countDownLatch info: java.util.concurrent.CountDownLatch@5ebb120e[Count = 2]
2020-05-29 15:53:00.024  INFO 3028 --- [t-thread-pool-7] c.e.andya.demo.service.TestThreadPool    : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 2, queued tasks = 0, completed tasks = 8], countDownLatch info: java.util.concurrent.CountDownLatch@5ebb120e[Count = 1]
2020-05-29 15:53:00.024  INFO 3028 --- [t-thread-pool-8] c.e.andya.demo.service.TestThreadPool    : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 2, queued tasks = 0, completed tasks = 8], countDownLatch info: java.util.concurrent.CountDownLatch@5ebb120e[Count = 0]
2020-05-29 15:53:00.024  INFO 3028 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool    : schedule-【1】 multi-threading countDownLatch count: 0
2020-05-29 15:53:00.024  INFO 3028 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool    : End schedule-【1】 startTime: 2020-05-29 15:53:00

可以从运行结果中看到,test1SchedulerThreadPool()test2SchedulerThreadPool()两个方法同时打印了Begin schedule...信息,是并发定时。

@Async实现定时并发

除了上述这种实现SchedulingConfigurer类来实现定时任务的并发,还可以通过@EnableAsync@Async注解实现定时任务的并发。

package com.example.andya.demo.util;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;import java.time.LocalDateTime;/*** @author Andya* @create 2020-04-20 22:50*/
@Configuration
@EnableScheduling
@EnableAsync
public class StaticScheduleTask {private Logger LOG = LoggerFactory.getLogger(StaticScheduleTask.class);@Async@Scheduled(cron = "${schedule.cron}")public void firstTask() throws InterruptedException {for (int i = 0; i < 5; i++) {LOG.info("first task time: " + LocalDateTime.now() + "\r\nthread:" + Thread.currentThread().getName());}Thread.sleep(1000 * 10);}@Async
//    @Scheduled(fixedDelay = 5000)@Scheduled(cron = "0 * * * * *")public void secondTask() throws InterruptedException {for (int i = 0; i < 5; i++) {LOG.info("second task time: " + LocalDateTime.now() + "\r\nthread:" + Thread.currentThread().getName());}Thread.sleep(1000 * 10);}
}

运行结果:


2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-1] c.e.andya.demo.util.StaticScheduleTask   : second task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-1
2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-2] c.e.andya.demo.util.StaticScheduleTask   : first task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-2
2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-1] c.e.andya.demo.util.StaticScheduleTask   : second task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-1
2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-2] c.e.andya.demo.util.StaticScheduleTask   : first task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-2
2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-1] c.e.andya.demo.util.StaticScheduleTask   : second task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-1
2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-2] c.e.andya.demo.util.StaticScheduleTask   : first task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-2
2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-1] c.e.andya.demo.util.StaticScheduleTask   : second task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-1
2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-2] c.e.andya.demo.util.StaticScheduleTask   : first task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-2
2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-1] c.e.andya.demo.util.StaticScheduleTask   : second task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-1
2020-05-29 16:07:00.035  INFO 31428 --- [cTaskExecutor-2] c.e.andya.demo.util.StaticScheduleTask   : first task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-2

SpringBoot—自定义线程池及并发定时任务模板相关推荐

  1. SpringBoot 自定义线程池

    文章目录 一.自定义线程池 1. yml配置 2. 线程池配置属性类 3. 开启异步线程支持 4. 创建自定义线程池配置类 5. service逻辑层 6. controller控制层 7. 效果图 ...

  2. @Async 异步任务自定义线程池的配置方法和 @Scheduled 定时任务自定义线程池的配置方式

    文章目录 一.定时和异步业务场景描述 二.定时调度任务的实现方式 三.定时调度任务的问题描述 四.定时调度多线程解决方案(方案一) 五.异步多线程程序实现方式 六.定时调度多线程解决方案(方案二) 一 ...

  3. 【Java 并发编程】线程池机制 ( 线程池阻塞队列 | 线程池拒绝策略 | 使用 ThreadPoolExecutor 自定义线程池参数 )

    文章目录 一.线程池阻塞队列 二.拒绝策略 三.使用 ThreadPoolExecutor 自定义线程池参数 一.线程池阻塞队列 线程池阻塞队列是线程池创建的第 555 个参数 : BlockingQ ...

  4. 多线程与高并发(七):详解线程池 - 自定义线程池,JDK自带线程池,ForkJoin,源码解析等

    Executor 接口关系 Callable:类似于Runnable,但是可以有返回值 Future:存储将来执行的结果.Callable被执行完之后的结果,被封装到Future里面. Future ...

  5. Java高并发编程详解系列-线程池原理自定义线程池

    之前博客的所有内容是对单个线程的操作,例如有Thread和Runnable的使用以及ThreadGroup等的使用,但是对于在有些场景下我们需要管理很多的线程,而对于这些线程的管理有一个统一的管理工具 ...

  6. Java 并发编程之自定义线程池 ThreadPoolExecutor

    1)定义一个任务线程 public class Task implements Runnable {private String name;Task(String name) {this.name = ...

  7. Java队列、线程池及ThreadPoolExecutor自定义线程池实现

    目录 1.阻塞队列 2.队列分类 3.API使用 4.线程池 4.1.线程池参数 4.2.线程池实现 4.3.任务执行流程 4.4.拒绝策略 4.5.参数合理值设置 5.自定义线程池流程 6.自定义线 ...

  8. android自定义线程池工具类,妈妈再也不用担心你不会使用线程池了(ThreadUtils)...

    为什么要用线程池 使用线程池管理线程有如下优点:降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗. 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行. 提高线程的可 ...

  9. Spring Boot使用@Async实现异步调用:自定义线程池

    在之前的Spring Boot基础教程系列中,已经通过<Spring Boot中使用@Async实现异步调用>一文介绍过如何使用 @Async注解来实现异步调用了.但是,对于这些异步执行的 ...

最新文章

  1. 浅析如何从吸引蜘蛛爬取的角度进行网站内容优化建设
  2. Python3 JSON处理
  3. 运行维护:UPS电源并列运行分析及维护应用
  4. 工作中常用的正则表达式
  5. 【Python】安利 3 个 pandas 数据探索分析神器!
  6. 深入探究ASP.NET Core Startup的初始化
  7. weblogic概览下的上下文根配置_weblogic创建域
  8. python解析器原理_Python程序运行原理图文解析
  9. TeaVM编译JAVA感想:看着简单,做起来真难
  10. The tough time set
  11. 挑战程序设计竞赛: Fence Repair
  12. 123f是什么c语言,123RF博客
  13. Xcode12.5 iPhone 模拟器无法直接安装Charles证书
  14. c语言第七章函数实验总结,C语言学习与总结---第七章:函数 [01]
  15. ViewBinding使用时出现Missing required view with ID: xxx 错误
  16. Proteus仿真——常用元件
  17. ShaderJoy —— 心形爆炸烟花效果【GLSL】
  18. 什么是面向对象编程(通俗易懂)
  19. Siemens CXV65+Photoshop CS2
  20. 看看美国人怎么做SEO

热门文章

  1. OpenCV辅助对象(help objects)(2)_Range
  2. 浅谈Opencl之Image和Buffer 区别
  3. oracle9i解密rewrap,oracle 9i 的加密解密用法之dbms_obfuscation_toolkit(一)
  4. 后端代码之服务端 - 项目工程化创建目录启动服务 -讲解篇
  5. phpcmsV9-本地项目上线 - 踩坑篇
  6. vue.js项目中,关于element-ui完整引入、按需引入的介绍
  7. 比亚迪汉鸿蒙系统测评_国产强强联合,比亚迪与华为联手打造的“汉”定制款华为P40亮相...
  8. ripro子主题eeesucai-child集成后台美化包(适用于设计素材站+资源下载站等)
  9. 途观l怎么使用_官宣!中型SUV质量最新排名出炉:汉兰达失前三,大众途观L上榜!...
  10. 关于微型计算机的原理 叙述正确的是,微型计算机原理练习附答案概念.doc