当前位置: 首页 > news >正文

Spring Boot 自定义定时任务组件深度解析:Quartz 集成与设计模式实战

一、组件设计目标

解决痛点:

  • 简化 Quartz 原生 API 的复杂性
  • 统一任务调度管理(增删改查、日志、重试)
  • 与 Spring Boot 生态无缝整合

二、实现步骤详解

1. 组件初始化配置

1.1 初始化 Quartz 表结构

下载 SQL 脚本

🔗 官方表结构下载地址:Quartz
根据 Quartz 版本选择对应 SQL 文件

1.2 引入pom依赖

<!-- pom.xml -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

1.3 引入yaml配置

# application.yml
# Quartz 配置项,对应 QuartzProperties 配置类
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/your_db?useSSL=falseusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driverautoconfigure:exclude:#排除Quartz自动配置即关闭定时任务,注释掉该配置即打开定时任务- org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 不开启 Quartz 的自动配置quartz:auto-startup: true # 本地开发环境,尽量不要开启 Jobscheduler-name: schedulerName # Scheduler 名字。默认为 schedulerNamejob-store-type: jdbc # Job 存储器类型。默认为 memory 表示内存,可选 jdbc 使用数据库。wait-for-jobs-to-complete-on-shutdown: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 trueproperties: # 添加 Quartz Scheduler 附加属性,更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 文档org:quartz:# Scheduler 相关配置scheduler:instanceName: schedulerNameinstanceId: AUTO # 自动生成 instance ID# JobStore 相关配置jobStore:# JobStore 实现类。可见博客:https://blog.csdn.net/weixin_42458219/article/details/122247162class: org.springframework.scheduling.quartz.LocalDataSourceJobStoreisClustered: true # 是集群模式clusterCheckinInterval: 15000 # 集群检查频率,单位:毫秒。默认为 15000,即 15 秒misfireThreshold: 60000 # misfire 阀值,单位:毫秒。# 线程池相关配置threadPool:threadCount: 25 # 线程池大小。默认为 10 。threadPriority: 5 # 线程优先级class: org.quartz.simpl.SimpleThreadPool # 线程池类型jdbc: # 使用 JDBC 的 JobStore 的时候,JDBC 的配置initialize-schema: NEVER # 是否自动使用 SQL 初始化 Quartz 表结构。这里设置成 never ,我们手动创建表结构。

1.4 Spring Boot 2.7 与 3.0 自动配置声明对比

1. 文件路径与作用
文件类型路径用途说明
AutoConfiguration.importsMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports声明自动配置类(Spring Boot 2.7+ 新增)
spring.factoriesMETA-INF/spring.factories旧版声明方式(Spring Boot 2.7+ 兼容,直到3.0 废弃spring.factories 中自动配置类声明。)
2. 版本差异详解
Spring Boot 2.7
  • 兼容模式: 同时支持两种声明方式,优先级:
    AutoConfiguration.imports > spring.factories
  • 推荐做法: 使用新的 AutoConfiguration.imports 文件
Spring Boot 3.0
  • 强制要求: 自动配置类 必须 通过 AutoConfiguration.imports 声明
  • 废弃内容: spring.factories 中 org.springframework.boot.autoconfigure.EnableAutoConfiguration 条目失效
  • 保留内容: 其他非自动配置条目(如监听器、模板引擎)仍可保留在 spring.factories
3. 文件内容对比
Spring Boot 2.7+ 配置

在 resources/META-INF 目录下创建文件:
文件路径:
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件内容:

# 声明自定义的自动配置类
cn.iocoder.dyh.framework.quartz.config.DyhQuartzAutoConfiguration
cn.iocoder.dyh.framework.quartz.config.DyhAsyncAutoConfiguration
Spring Boot 2.6 及以下配置

在 resources/META-INF 目录下创建文件:
文件路径:
src/main/resources/META-INF/spring.factories
文件内容:

# 声明自定义的自动配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\cn.iocoder.dyh.framework.quartz.config.DyhQuartzAutoConfiguration,\cn.iocoder.dyh.framework.quartz.config.DyhAsyncAutoConfiguration

1.5 自定义自动配置类

DyhAsyncAutoConfiguration
/*** 异步任务 Configuration*/
@AutoConfiguration// 1️⃣ Spring Boot自动配置注解,标识这是一个自动配置类
@EnableAsync// 2️⃣ 启用Spring的异步方法执行功能
public class DyhAsyncAutoConfiguration {@Bean// 3️⃣ 声明该方法返回的对象会被注册为Spring Beanpublic BeanPostProcessor threadPoolTaskExecutorBeanPostProcessor() {return new BeanPostProcessor() { // 4️⃣ 创建BeanPostProcessor实现(Bean生命周期处理器)@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if (!(bean instanceof ThreadPoolTaskExecutor)) {  // 5️⃣ 仅处理ThreadPoolTaskExecutor类型的Beanreturn bean;}// 修改提交的任务,接入 TransmittableThreadLocalThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) bean;  // 6️⃣ 类型转换executor.setTaskDecorator(TtlRunnable::get);  // 7️⃣ 设置任务装饰器(用于TTL线程池上下文传递)return executor;}};}
}
DyhQuartzAutoConfiguration
@AutoConfiguration // 1️⃣ Spring Boot自动配置注解,标识这是一个自动配置类
@EnableScheduling // 2️⃣ 启用Spring自带的定时任务功能(@Scheduled注解生效)
@Slf4j // 3️⃣ Lombok注解,自动生成日志对象 log
public class DyhQuartzAutoConfiguration {@Bean  // 4️⃣ 声明该方法返回的对象会被注册为Spring Beanpublic SchedulerManager schedulerManager(Optional<Scheduler> scheduler) {  // 5️⃣ 使用Optional包装的Quartz调度器if (!scheduler.isPresent()) {  // 6️⃣ 检测是否没有Scheduler实例log.info("[定时任务 - 已禁用]");return new SchedulerManager(null);  // 7️⃣ 创建禁用状态的调度管理器}return new SchedulerManager(scheduler.get());  // 8️⃣ 创建正常工作的调度管理器}
}

1.6 相关类

JobDataKeyEnum
/*** Quartz Job Data 的 key 枚举*/
public enum JobDataKeyEnum {JOB_ID,JOB_HANDLER_NAME,JOB_HANDLER_PARAM,JOB_RETRY_COUNT, // 最大重试次数JOB_RETRY_INTERVAL, // 每次重试间隔
}
JobLogFrameworkService

/*** Job 日志 Framework Service 接口** @author dyh*/
public interface JobLogFrameworkService {/*** 创建 Job 日志** @param jobId           任务编号* @param beginTime       开始时间* @param jobHandlerName  Job 处理器的名字* @param jobHandlerParam Job 处理器的参数* @param executeIndex    第几次执行* @return Job 日志的编号*/Long createJobLog(@NotNull(message = "任务编号不能为空") Long jobId,@NotNull(message = "开始时间") LocalDateTime beginTime,@NotEmpty(message = "Job 处理器的名字不能为空") String jobHandlerName,String jobHandlerParam,@NotNull(message = "第几次执行不能为空") Integer executeIndex);/*** 更新 Job 日志的执行结果** @param logId    日志编号* @param endTime  结束时间。因为是异步,避免记录时间不准去* @param duration 运行时长,单位:毫秒* @param success  是否成功* @param result   成功数据*/void updateJobLogResultAsync(@NotNull(message = "日志编号不能为空") Long logId,@NotNull(message = "结束时间不能为空") LocalDateTime endTime,@NotNull(message = "运行时长不能为空") Integer duration,boolean success, String result);
}
CronUtils

/*** Quartz Cron 表达式的工具类** @author dyh*/
public class CronUtils {/*** 校验 CRON 表达式是否有效** @param cronExpression CRON 表达式* @return 是否有效*/public static boolean isValid(String cronExpression) {return CronExpression.isValidExpression(cronExpression);}/*** 基于 CRON 表达式,获得下 n 个满足执行的时间** @param cronExpression CRON 表达式* @param n 数量* @return 满足条件的执行时间*/public static List<LocalDateTime> getNextTimes(String cronExpression, int n) {// 1. 获得 CronExpression 对象CronExpression cron;try {cron = new CronExpression(cronExpression);} catch (ParseException e) {throw new IllegalArgumentException(e.getMessage());}// 2. 从当前开始计算,n 个满足条件的Date now = new Date();List<LocalDateTime> nextTimes = new ArrayList<>(n);for (int i = 0; i < n; i++) {Date nextTime = cron.getNextValidTimeAfter(now);// 2.1 如果 nextTime 为 null,说明没有更多的有效时间,退出循环if (nextTime == null) {break;}nextTimes.add(LocalDateTimeUtil.of(nextTime));// 2.2 切换现在,为下一个触发时间;now = nextTime;}return nextTimes;}}

2. 核心模块实现

2.1 任务调度管理(SchedulerManager)


/*** {@link Scheduler} 的管理器,负责创建任务** 考虑到实现的简洁性,我们使用 jobHandlerName 作为唯一标识,即:* 1. Job 的 {@link JobDetail#getKey()}* 2. Trigger 的 {@link Trigger#getKey()}** 另外,jobHandlerName 对应到 Spring Bean 的名字,直接调用** @author dyh*/
public class SchedulerManager {// Quartz调度器核心对象private final Scheduler scheduler;// 通过依赖注入获取Quartz调度器实例public SchedulerManager(Scheduler scheduler) {this.scheduler = scheduler;}/*** 添加 Job 到 Quartz 中** @param jobId 任务编号* @param jobHandlerName 任务处理器的名字* @param jobHandlerParam 任务处理器的参数* @param cronExpression CRON 表达式* @param retryCount 重试次数* @param retryInterval 重试间隔* @throws SchedulerException 添加异常*/public void addJob(Long jobId, String jobHandlerName, String jobHandlerParam, String cronExpression,Integer retryCount, Integer retryInterval)throws SchedulerException {validateScheduler();// 创建 JobDetail 对象JobDetail jobDetail = JobBuilder.newJob(JobHandlerInvoker.class).usingJobData(JobDataKeyEnum.JOB_ID.name(), jobId).usingJobData(JobDataKeyEnum.JOB_HANDLER_NAME.name(), jobHandlerName).withIdentity(jobHandlerName).build();// 创建 Trigger 对象Trigger trigger = this.buildTrigger(jobHandlerName, jobHandlerParam, cronExpression, retryCount, retryInterval);// 新增 Job 调度scheduler.scheduleJob(jobDetail, trigger);}/*** 更新 Job 到 Quartz** @param jobHandlerName 任务处理器的名字* @param jobHandlerParam 任务处理器的参数* @param cronExpression CRON 表达式* @param retryCount 重试次数* @param retryInterval 重试间隔* @throws SchedulerException 更新异常*/public void updateJob(String jobHandlerName, String jobHandlerParam, String cronExpression,Integer retryCount, Integer retryInterval)throws SchedulerException {validateScheduler();// 创建新 Trigger 对象Trigger newTrigger = this.buildTrigger(jobHandlerName, jobHandlerParam, cronExpression, retryCount, retryInterval);// 修改调度scheduler.rescheduleJob(new TriggerKey(jobHandlerName), newTrigger);}/*** 删除 Quartz 中的 Job** @param jobHandlerName 任务处理器的名字* @throws SchedulerException 删除异常*/public void deleteJob(String jobHandlerName) throws SchedulerException {validateScheduler();// 暂停 Trigger 对象scheduler.pauseTrigger(new TriggerKey(jobHandlerName));// 取消并删除 Job 调度scheduler.unscheduleJob(new TriggerKey(jobHandlerName));scheduler.deleteJob(new JobKey(jobHandlerName));}/*** 暂停 Quartz 中的 Job** @param jobHandlerName 任务处理器的名字* @throws SchedulerException 暂停异常*/public void pauseJob(String jobHandlerName) throws SchedulerException {validateScheduler();scheduler.pauseJob(new JobKey(jobHandlerName));}/*** 启动 Quartz 中的 Job** @param jobHandlerName 任务处理器的名字* @throws SchedulerException 启动异常*/public void resumeJob(String jobHandlerName) throws SchedulerException {validateScheduler();scheduler.resumeJob(new JobKey(jobHandlerName));scheduler.resumeTrigger(new TriggerKey(jobHandlerName));}/*** 立即触发一次 Quartz 中的 Job** @param jobId 任务编号* @param jobHandlerName 任务处理器的名字* @param jobHandlerParam 任务处理器的参数* @throws SchedulerException 触发异常*/public void triggerJob(Long jobId, String jobHandlerName, String jobHandlerParam)throws SchedulerException {validateScheduler();// 触发任务JobDataMap data = new JobDataMap(); // 无需重试,所以不设置 retryCount 和 retryIntervaldata.put(JobDataKeyEnum.JOB_ID.name(), jobId);data.put(JobDataKeyEnum.JOB_HANDLER_NAME.name(), jobHandlerName);data.put(JobDataKeyEnum.JOB_HANDLER_PARAM.name(), jobHandlerParam);scheduler.triggerJob(new JobKey(jobHandlerName), data);}private Trigger buildTrigger(String jobHandlerName, String jobHandlerParam, String cronExpression,Integer retryCount, Integer retryInterval) {return TriggerBuilder.newTrigger().withIdentity(jobHandlerName).withSchedule(CronScheduleBuilder.cronSchedule(cronExpression)).usingJobData(JobDataKeyEnum.JOB_HANDLER_PARAM.name(), jobHandlerParam).usingJobData(JobDataKeyEnum.JOB_RETRY_COUNT.name(), retryCount).usingJobData(JobDataKeyEnum.JOB_RETRY_INTERVAL.name(), retryInterval).build();}private void validateScheduler() {if (scheduler == null) {throw exception0(NOT_IMPLEMENTED.getCode(),"[定时任务 - 已禁用]");}}}

2.2 任务执行器(JobHandlerInvoker)


/*** 基础 Job 调用者,负责调用 {@link JobHandler#execute(String)} 执行任务** @author dyh*/
@DisallowConcurrentExecution        // 禁止同一JobDetail并发执行
@PersistJobDataAfterExecution       // 执行后持久化JobDataMap数据
@Slf4j
public class JobHandlerInvoker extends QuartzJobBean {@Resourceprivate ApplicationContext applicationContext;@Resourceprivate JobLogFrameworkService jobLogFrameworkService;@Overrideprotected void executeInternal(JobExecutionContext executionContext) throws JobExecutionException {// 第一步,获得 Job 数据Long jobId = executionContext.getMergedJobDataMap().getLong(JobDataKeyEnum.JOB_ID.name());String jobHandlerName = executionContext.getMergedJobDataMap().getString(JobDataKeyEnum.JOB_HANDLER_NAME.name());String jobHandlerParam = executionContext.getMergedJobDataMap().getString(JobDataKeyEnum.JOB_HANDLER_PARAM.name());int refireCount  = executionContext.getRefireCount();int retryCount = (Integer) executionContext.getMergedJobDataMap().getOrDefault(JobDataKeyEnum.JOB_RETRY_COUNT.name(), 0);int retryInterval = (Integer) executionContext.getMergedJobDataMap().getOrDefault(JobDataKeyEnum.JOB_RETRY_INTERVAL.name(), 0);// 第二步,执行任务Long jobLogId = null;LocalDateTime startTime = LocalDateTime.now();String data = null;Throwable exception = null;try {// 记录 Job 日志(初始)jobLogId = jobLogFrameworkService.createJobLog(jobId, startTime, jobHandlerName, jobHandlerParam, refireCount + 1);// 执行任务data = this.executeInternal(jobHandlerName, jobHandlerParam);} catch (Throwable ex) {exception = ex;}// 第三步,记录执行日志this.updateJobLogResultAsync(jobLogId, startTime, data, exception, executionContext);// 第四步,处理有异常的情况handleException(exception, refireCount, retryCount, retryInterval);}private String executeInternal(String jobHandlerName, String jobHandlerParam) throws Exception {// 获得 JobHandler 对象JobHandler jobHandler = applicationContext.getBean(jobHandlerName, JobHandler.class);Assert.notNull(jobHandler, "JobHandler 不会为空");// 执行任务return jobHandler.execute(jobHandlerParam);}private void updateJobLogResultAsync(Long jobLogId, LocalDateTime startTime, String data, Throwable exception,JobExecutionContext executionContext) {LocalDateTime endTime = LocalDateTime.now();// 处理是否成功boolean success = exception == null;if (!success) {data = getRootCauseMessage(exception);}// 更新日志try {jobLogFrameworkService.updateJobLogResultAsync(jobLogId, endTime, (int) LocalDateTimeUtil.between(startTime, endTime).toMillis(), success, data);} catch (Exception ex) {log.error("[executeInternal][Job({}) logId({}) 记录执行日志失败({}/{})]",executionContext.getJobDetail().getKey(), jobLogId, success, data);}}private void handleException(Throwable exception,int refireCount, int retryCount, int retryInterval) throws JobExecutionException {// 如果有异常,则进行重试if (exception == null) {return;}// 情况一:如果到达重试上限,则直接抛出异常即可if (refireCount >= retryCount) {throw new JobExecutionException(exception);}// 情况二:如果未到达重试上限,则 sleep 一定间隔时间,然后重试// 这里使用 sleep 来实现,主要还是希望实现比较简单。因为,同一时间,不会存在大量失败的 Job。if (retryInterval > 0) {ThreadUtil.sleep(retryInterval);}// 第二个参数,refireImmediately = true,表示立即重试throw new JobExecutionException(exception, true);}}

2.3 任务处理器(JobHandler)

/*** 任务处理器** @author dyh*/
public interface JobHandler {/*** 执行任务** @param param 参数* @return 结果* @throws Exception 异常*/String execute(String param) throws Exception;}

三、核心设计模式解析

1. 策略模式(Strategy Pattern)

应用场景:不同业务任务的具体实现

public interface JobHandler {String execute(String param) throws Exception;
}@Component
public class DemoJob implements JobHandler {@Overridepublic String execute(String param) {// 具体业务逻辑}
}

2. 模板方法模式(Template Method)

应用场景:任务执行流程标准化

此代码为org.springframework.scheduling.quartz包下源码

public abstract class QuartzJobBean implements Job {public QuartzJobBean() {}//不可变的流程用final修饰生成流程骨架public final void execute(JobExecutionContext context) throws JobExecutionException {try {BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);MutablePropertyValues pvs = new MutablePropertyValues();pvs.addPropertyValues(context.getScheduler().getContext());pvs.addPropertyValues(context.getMergedJobDataMap());bw.setPropertyValues(pvs, true);} catch (SchedulerException var4) {SchedulerException ex = var4;throw new JobExecutionException(ex);}this.executeInternal(context);}//可变部分抽象出来由具体子类实现功能protected abstract void executeInternal(JobExecutionContext context) throws JobExecutionException;
}

3. 工厂方法模式(Factory Method)

间接应用:通过 Spring 容器获取 JobHandler 实例

// 获得 JobHandler 对象
JobHandler jobHandler = applicationContext.getBean(jobHandlerName, JobHandler.class);

4. 装饰器模式(Decorator)

应用场景:TTL 异步任务上下文传递

executor.setTaskDecorator(TtlRunnable::get); // 增强 Runnable

四、组件运行流程

1. 组件运行流程图

┌───────────────────────┐          ┌───────────────────────┐
│      Spring Boot      │          │      Quartz Scheduler │
│       Application     │          │ (Cluster Mode)        │
└───────────┬───────────┘          └───────────┬───────────┘│                                  ││ 1. 自动配置触发                  │├─────────────────────────────────▶│   - @AutoConfiguration           ││   - AutoConfiguration.imports    ││                                  ││ 2. 初始化 SchedulerManager       ││    (依赖注入 Scheduler)          ││                                  ││ 3. 业务系统调用                  ││    SchedulerManager.addJob()     ││                                  ││ 4. 创建 JobDetail & Trigger      ││    (存储到 JDBC JobStore)        ││                                  │
┌───────────▼───────────┐          ┌───────────▼───────────┐
│   Quartz JobStore     │          │   ThreadPoolTaskExecutor
│   (Database)          │          │   (异步日志记录)      │
└───────────┬───────────┘          └───────────▲───────────┘│                                  ││ 5. 调度触发                      ││    (Cron 表达式)                 ││                                  ││ 6. 执行 JobHandlerInvoker        ││    (extends QuartzJobBean)       ││                                  │
┌───────────▼───────────┐                     │
│   JobHandler 策略模式  │                     │
│  (具体业务实现类)       │                     │
│  - DemoJob.execute()  │                     │
│  - OrderTimeoutJob    │                     │
└───────────┬───────────┘                     ││                                  ││ 7. 执行结果/异常                 │├─────────────────────────────────▶│   JobLogFrameworkService         ││   .updateJobLogResultAsync()     ││                                  ││ 8. 异常重试机制                  ││   (handleException 方法)         ││                                  ││                                  │
└───────────────────────┘          └───────────────────────┘

2. 流程关键步骤说明

  1. 自动配置阶段
    • Spring Boot 通过 AutoConfiguration.imports 加载 DyhQuartzAutoConfiguration
    • 初始化 SchedulerManager 并注入 Quartz Scheduler
  2. 任务注册阶段
    • 业务代码调用 SchedulerManager.addJob() 方法
    • 创建 JobDetail(绑定 JobHandlerInvoker)和 Trigger
    • 任务配置持久化到数据库(JDBC JobStore)
  3. 调度触发阶段
    • Quartz Scheduler 根据 Cron 表达式触发任务
    • 集群模式下通过数据库锁实现任务分片
  4. 任务执行阶段
    • JobHandlerInvoker 执行模板方法:
      • a. 获取任务参数(JobDataMap)
      • b. 记录初始日志(JobLogFrameworkService.createJobLog())
      • c. 策略模式 调用具体 JobHandler.execute()
      • d. 异步更新日志结果(updateJobLogResultAsync())
      • e. 异常重试机制(handleException)
  5. 异步日志处理
    • 使用 ThreadPoolTaskExecutor 异步记录日志
    • 通过 TtlRunnable 实现线程池上下文传递(TransmittableThreadLocal)

3. 设计模式标注

  • 🎯 策略模式:JobHandler 接口统一任务执行入口
  • 📜 模板方法:QuartzJobBean 定义任务执行骨架
  • 🏭 工厂模式:Spring 容器管理 JobHandler 实例
  • 🎨 装饰器模式:TtlRunnable 增强线程池任务

五、完整使用示例

1. 定义具体策略的任务处理器

@Component
@Slf4j
public class OrderTimeoutJob implements JobHandler {@Overridepublic String execute(String orderId) {log.info("[订单超时检查] 订单ID: {}", orderId);// 业务逻辑:关闭超时订单return "已关闭订单: " + orderId;}
}

2. 注册定时任务

说明:注册定时任务可以自建业务类对接该自定义定时任务组件持久化到数据库表,这样就可以像xxl-job一样界面上操作定时任务了,在这里写就篇幅太长了,有空我写了把链接贴过来。

@Autowired
private SchedulerManager schedulerManager;// 添加任务
public void addOrderTimeoutJob(Long jobId, String orderId) throws SchedulerException {schedulerManager.addJob(jobId, "orderTimeoutJob", // Bean 名称orderId,           // 参数"0 0 0 * * ?",     // CRON3,                 // 最大重试次数5000               // 重试间隔(毫秒));
}

六、组件优势总结

  1. 开箱即用:通过 Starter 自动配置,快速集成
  2. 灵活扩展:基于策略模式轻松添加新任务类型
  3. 企业级特性:
    • 分布式调度(基于 Quartz JDBC JobStore)
    • 任务日志追踪
    • 失败重试机制
  4. 生产就绪:完善的异常处理与防御性编程

相关文章:

Spring Boot 自定义定时任务组件深度解析:Quartz 集成与设计模式实战

一、组件设计目标 解决痛点&#xff1a; 简化 Quartz 原生 API 的复杂性统一任务调度管理&#xff08;增删改查、日志、重试&#xff09;与 Spring Boot 生态无缝整合 二、实现步骤详解 1. 组件初始化配置 1.1 初始化 Quartz 表结构 下载 SQL 脚本 &#x1f517; 官方表…...

Java Bean演进历程:从POJO到Spring Boot配置绑定

一、早期阶段&#xff1a;手动编写Java Bean 基本结构 私有属性&#xff1a;所有字段均为private&#xff0c;保证封装性。 公共构造方法&#xff1a;提供无参构造&#xff08;JavaBean规范&#xff09;或有参构造&#xff08;POJO常见&#xff09;。 Setter/Getter方法&…...

信息科技伦理与道德0:课程安排

1 课程安排 分组讨论的议题如下&#xff1a; 1.1 生成对抗网络&#xff08;GAN&#xff09; &#xff08;1&#xff09;GAN生成伪造人脸与身份冒用风险 算法原理&#xff1a; GAN通过生成器&#xff08;Generator&#xff09;和判别器&#xff08;Discriminator&#xff09;…...

STM32F103C8T6-基于FreeRTOS系统实现步进电机控制

引言 上一篇文章讲述了如何使用蓝牙连接stm32进行数据收发控制步进电机&#xff0c;这篇在之前的基础上通过移植操作系统&#xff08;FreeRTOS或者其他的也可以&#xff0c;原理操作都类似&#xff09;实现步进电机控制。 上篇博客指路&#xff1a;STM32蓝牙连接Android实现云…...

数字资产和交易解决方案

数字资产和交易解决方案 一、背景 &#xff08;一&#xff09;数字经济的蓬勃发展 随着信息技术的飞速发展&#xff0c;数字经济已成为全球经济增长的新引擎。数字资产作为数字经济的重要组成部分&#xff0c;其价值逐渐被人们所认识和重视。数字资产包括但不限于数字货币、…...

计算机网络 实验四 静态路由的配置与应用

一、实验目的 熟悉路由器的工作原理&#xff1b;熟悉静态路由的原理&#xff1b;熟悉华为网络模拟器的使用方法&#xff1b;掌握网络拓扑图的绘制&#xff1b;掌握路由器的配置。 二、实验设备 PC、华为模拟器ENSP。 三、实验步骤 知识准备&#xff1a;路由器和静态路由的…...

二进制求和 - 简单

************* C topic: 67. 二进制求和 - 力扣&#xff08;LeetCode&#xff09; ************* Give the topic an inspection. Too many works these days. And no spare time for code learning. However here I am gagin. This topic is an easy one and I want to pra…...

【C++】 —— 笔试刷题day_18

一、压缩字符串(一) 题目解析 题目给定一个字符str&#xff0c;让我们将这个字符串进行压缩&#xff1b; **压缩规则&#xff1a;**出现多次的字符压缩成字符数字&#xff1b;例如aaa压缩成a3。如果字符值出现一次&#xff0c;1不用写。 算法思路 这道题总的来说就非常简单了…...

LeetCode 热题 100_最长递增子序列(87_300_中等_C++)(动态规划)

LeetCode 热题 100_最长递增子序列&#xff08;87_300&#xff09; 题目描述&#xff1a;输入输出样例&#xff1a;题解&#xff1a;解题思路&#xff1a;思路一&#xff08;动态规划&#xff09;&#xff1a; 代码实现代码实现&#xff08;思路一&#xff08;动态规划&#xf…...

asp-for等常用的HTML辅助标记?

在ASP.NET Core Razor Pages 和 MVC 中&#xff0c;除了asp-for之外&#xff0c;还有许多常用的 HTML 辅助标记&#xff0c;下面为你详细介绍&#xff1a; 表单与路由相关 asp-action 和 asp-controller 用途&#xff1a;这两个标记用于生成表单或链接的 URL&#xff0c;指定…...

map用法介绍

在 C 里&#xff0c;map是标准库提供的一种关联容器&#xff0c;它以键 - 值对的形式存储元素&#xff0c;并且按键的升序排列。下面为你展示如何在 C 用map。 如果没有用万能头的时候&#xff0c;需要加入#include 用法介绍&#xff1a; 映射[需要注意map的映射是1对1的不能出…...

AIGC-十款知识付费类智能体完整指令直接用(DeepSeek,豆包,千问,Kimi,GPT)

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列AIGC(GPT、DeepSeek、豆包、千问、Kimi)👉关于作者 专注于Android/Unity和各种游戏开发技巧,以及各种资…...

一页概览:桌面虚拟化方案

2010年左右手绘的&#xff0c;用的是公司的信纸&#xff0c;马克笔。当时在买VMware和Citrix的桌面虚拟化方案&#xff0c;以及Wyse的瘦客户端。...

通过导入 Excel 的方式复制文件或文件夹

在进行文件或文件夹的批量整理时&#xff0c;许多人都会遇到需要将大量文件或文件夹复制到另一个文件夹中的问题。传统的手动复制粘贴方法不仅繁琐&#xff0c;而且效率低下。今天给大家介绍一种方法&#xff0c;可以实现将多个不同文件夹中的文件复制到一个或者多个文件夹&…...

Python单例设计模式深度解析

目录 一、什么是单例设计模式 核心特点 二、为什么需要单例模式 典型应用场景 优势对比 三、Python实现单例的三种方式 1. 使用__new__方法&#xff08;经典实现&#xff09; 2. 使用装饰器实现 3. 使用模块实现&#xff08;Python特有&#xff09; 四、深入理解__new…...

WPF 图标原地旋转

如何使元素原地旋转 - WPF .NET Framework | Microsoft Learn <ButtonRenderTransformOrigin"0.5,0.5"HorizontalAlignment"Left">Hello,World<Button.RenderTransform><RotateTransform x:Name"MyAnimatedTransform" Angle"…...

深入解析Java日志框架Logback:从原理到最佳实践

Logback作为Java领域最主流的日志框架之一&#xff0c;由Log4j创始人Ceki Glc设计开发&#xff0c;凭借其卓越的性能、灵活的配置以及与SLF4J的无缝集成&#xff0c;成为企业级应用开发的首选日志组件。本文将从架构设计、核心机制、配置优化等维度全面剖析Logback的技术细节。…...

【设计模式——装饰器模式】

在 Unity 游戏开发中&#xff0c;装饰模式是一种非常灵活的设计模式&#xff0c;用于在运行时动态地为对象添加功能。以下是装饰模式的设计思路和实现步骤&#xff0c;以角色的装备系统为例进行说明。 设计思路 装饰模式的核心思想是通过创建一个装饰器类来包装原有的对象&am…...

在 macOS 上切换默认 Java 版本

下载javasdk 打开android studio -> setting -> build.execution,dep -> build tools -> gradle -> Gradle JDK -> download JDK… 点击下载&#xff0c;就下载到了 ~/Library/Java/JavaVirtualMachines/ 安装 jenv brew install jenv将 jenv 集成到 Shell …...

【Linux网络与网络编程】11.数据链路层mac帧协议ARP协议

前面在介绍网络层时我们提出来过一个问题&#xff1a;主机是怎么把数据交给路由器的&#xff1f;那里我们说这是由数据链路层来做的。 网络上的报文在物理结构上是以mac帧的形式流动的&#xff0c;但在逻辑上是以IP流动的&#xff0c;IP的流动是需要mac帧支持的。 数据链路层解…...

158页PPT | 某大型研发制造集团信息化IT规划整体方案

该文档是某大型研发制造集团信息化IT规划整体方案&#xff0c;涵盖项目过程回顾、信息平台分析、现状评估、规划及治理建议和下阶段工作计划。项目旨在理解集团战略目标&#xff0c;评估信息化应用现状&#xff0c;制定可扩展的蓝图&#xff0c;明确未来3年管理与IT建设子项目&…...

ON DUPLICATE KEY UPDATE 更底层解释它的优势

从更底层来看&#xff0c;ON DUPLICATE KEY UPDATE 的优势主要源于以下几个方面&#xff1a; 1. 减少网络往返次数 先查询再更新&#xff1a;这种方式需要客户端和数据库服务器之间进行多次网络通信。首先&#xff0c;客户端发送一个 SELECT 查询请求&#xff0c;然后等待服务…...

Python 赋能区块链金融——从零构建智能交易系统

Python 赋能区块链金融——从零构建智能交易系统 引言:区块链金融系统的崛起 区块链技术正在颠覆传统金融体系,带来去中心化、透明化和高效的交易模式。从 DeFi(去中心化金融)到 NFT 市场,区块链金融系统已成为 Web 3.0 生态的重要支柱。如何用 Python 构建一个区块链金…...

基础(测试用例设计方法:流程图法,等价类划分法,边界值分析法,判定表法,正交分析法,错误推测法,其他方法,案例)

目录 流程图法&#xff08;场景法&#xff09; 业务流程 流程图 流程图法设计测试用例 案例-退款泳道图 案例-刷视频流程 等价类划分法 等价类 等价类设计测试用例 案例1-验证电话号码 案例2-验证邮箱格式 边界值分析法 测试数据的选取 边界值法设计测试用例 案例…...

QT —— 信号和槽(槽函数)

QT —— 信号和槽 信号和槽信号(Signal)槽(Slot)声明方式工作原理连接方式1. 传统连接方式(Qt4风格)2. 新式连接方式(Qt5风格) 区分槽函数和信号通过QtCreator生成信号槽代码自动生成槽函数显式连接的优势命名约定自动连接的局限性最佳实践建议结论 我们之前对QT&#xff0c;有…...

ROS2模块库概览

一、核心通信与基础库&#xff08;最常用&#xff09; 客户端库 rclcpp (ROS Client Library for C) 核心API&#xff1a;create_node(), create_publisher(), create_subscription()高级特性&#xff1a; 生命周期节点&#xff1a;通过rclcpp_lifecycle实现configure/activate…...

HADOOP——序列化

1.创建一个data目录在主目录下&#xff0c;并且在data目录下新建log.txt文件 2.新建flow软件包&#xff0c;在example软件包下 FlowBean package com.example.flow;import org.apache.hadoop.io.Writable;import java.io.DataInput; import java.io.DataOutput; import java.i…...

第五章 5.2ESP32物联网应用:HTTP与Web服务器详细教学

本文将详细讲解如何在ESP32上搭建Web服务器&#xff0c;通过HTTP协议实现远程控制LED灯。每行代码均有详细注释&#xff0c;适合零基础学习。 一、HTTP协议基础 HTTP是客户端&#xff08;浏览器&#xff09;和服务器之间的通信协议&#xff0c;常用请求方法&#xff1a; GET&a…...

c++11 绑定器bind

文章目录 std::bind 使用总结&#xff08;C11&#xff09;1. 绑定普通函数2. 使用占位符 _1, _2&#xff0c;调用时传参数3. 绑定类的成员函数&#xff08;类外&#xff09;4. 绑定类的成员函数&#xff08;类内&#xff09;5. 占位符结合成员函数小结 std::bind 使用总结&…...

实现时间最优轨迹生成/轨迹规划方法(TOTG),不使用moveit,可用于ROS驱动机械臂FollowJointTrajectoryGoal()

前言 在我的这篇文章&#xff1a;https://blog.csdn.net/weixin_45702459/article/details/139293391?spm1011.2415.3001.5331中&#xff0c;写了不使用moveit来ros驱动机械臂的方法&#xff0c;也就是用FollowJointTrajectoryGoal()来进行一系列点的关节运动&#xff0c;其实…...

2025年推荐使用的开源大语言模型top20:核心特性、选择指标和开源优势

李升伟 编译 随着人工智能技术的持续发展&#xff0c;开源大型语言模型&#xff08;LLMs&#xff09;正变得愈发强大&#xff0c;使最先进的AI能力得以普及。到2025年&#xff0c;开源生态系统中涌现出多个关键模型&#xff0c;它们在各类应用场景中展现出独特优势。 大型语言…...

高并发多级缓存架构实现思路

目录 1.整体架构 3.安装环境 1.1 使用docket安装redis 1.2 配置redis缓存链接&#xff1a; 1.3 使用redisTemplate实现 1.4 缓存注解优化 1.4.1 常用缓存注解简绍 1.4.2 EnableCaching注解的使用 1.4.3使用Cacheable 1.4.4CachePut注解的使用 1.4.5 优化 2.安装Ngin…...

Qt 的 事件队列

Qt 的 事件队列 是其核心事件处理机制之一&#xff0c;用于管理和分发系统与用户生成的事件&#xff08;如鼠标点击、键盘输入、定时器、信号槽中的队列连接等&#xff09;。理解 Qt 的事件队列对多线程、界面响应以及异步处理尤为关键。 一、Qt 的事件处理模型概览 Qt 是基于…...

html-css样式

1. 所有类型为文本的 元素的样式 指定所有类型为文本的 元素的样式 /* 文本框的样式 */ input[type"text"] { font-size: 25px;width: 80px; /* 文本框的宽度 */ padding: 25px; } font-size&#xff1a;字体大小 width&#xff1a;文本框宽度 padding&#…...

Qemu-STM32(十五):STM32F103加入Flash控制器

概述 本文主要描述了在Qemu平台中&#xff0c;如何添加STM32F103的Flash控制器模拟代码。 参考资料 STM32F1XX TRM手册&#xff0c;手册编号&#xff1a;RM0008 添加步骤 1、在hw/arm/Kconfig文件中添加STM32F1XX_FLASH&#xff0c;如下所示: 号部分为新增加内容 diff -…...

设计模式(责任链模式)

责任链模式 模板模式、策略模式和责任链模式&#xff0c;这三种模式具有相同的作用&#xff1a;复用和扩展&#xff0c;在实际的项目开发中比较常用&#xff0c;特别是框架开发中&#xff0c;我们可以利用它们来提供框架的扩展点&#xff0c;能够让框架的使用者在不修改框架源…...

【Mac-ML-DL】深度学习使用MPS出现内存泄露(leaked semaphore)以及张量转换错误

MPS加速修改总结 先说设备&#xff1a;MacBook Pro M4 24GB 事情的起因是我在进行深度学习的时候想尝试用苹果自带的MPS进行训练加速&#xff0c;修改设备后准备开始训练&#xff0c;但是出现如下报错&#xff1a; UserWarning: resource_tracker: There appear to be 1 leak…...

Hadoop集群部署教程-P5

Hadoop集群部署教程-P5 Hadoop集群部署教程&#xff08;续&#xff09; 第十七章&#xff1a;安全增强配置 17.1 认证与授权 Kerberos认证集成&#xff1a; # 生成keytab文件 kadmin -q "addprinc -randkey hdfs/masterEXAMPLE.COM" kadmin -q "xst -k hdfs.…...

Github 2FA(Two-Factor Authentication/两因素认证)

Github 2FA认证 多因素用户认证(Multi-Factor Authentication)&#xff0c;基本上各个大互联网平台&#xff0c;尤其是云平台厂商&#xff08;如&#xff1a;阿里云的MFA、华为云、腾讯云/QQ安全中心等&#xff09;都有启用了&#xff0c;Github算是搞得比较晚些了。 双因素身…...

Spark大数据分析与实战笔记(第四章 Spark SQL结构化数据文件处理-05)

文章目录 每日一句正能量第4章 Spark SQL结构化数据文件处理章节概要4.5 Spark SQL操作数据源4.5.1 Spark SQL操作MySQL4.5.2 操作Hive数据集 每日一句正能量 努力学习&#xff0c;勤奋工作&#xff0c;让青春更加光彩。 第4章 Spark SQL结构化数据文件处理 章节概要 在很多情…...

使用 Azure AKS 保护 Kubernetes 部署的综合指南

企业不断寻求增强其软件开发和部署流程的方法。DevOps 一直是这一转型的基石&#xff0c;弥合了开发与运营之间的差距。然而&#xff0c;随着安全威胁日益复杂&#xff0c;将安全性集成到 DevOps 流水线&#xff08;通常称为 DevSecOps&#xff09;已变得势在必行。本指南深入探…...

遵守 Vue3 的单向数据流原则:父组件传递对象 + 子组件修改对象属性,安全地实现父子组件之间复杂对象的双向绑定示例代码及讲解

以下是针对 父组件传递对象 子组件修改对象属性 的完整示例代码&#xff0c;同时遵守 Vue3 的单向数据流原则&#xff1a; 1. 父组件代码 (ParentComponent.vue) vue <template><!-- 通过 v-model 传递整个对象 --><ChildComponent v-model"formData&qu…...

Unchained 内容全面上链,携手 Walrus 迈入去中心化媒体新时代

加密新闻媒体 Unchained — — 业内最受信赖的声音之一 — — 现已选择 Walrus 作为其去中心化存储解决方案&#xff0c;正式将其所有媒体内容&#xff08;文章、播客和视频&#xff09;上链存储。Walrus 将替代 Unchained 现有的中心化存储架构&#xff0c;接管其全部历史内容…...

20.3 使用技巧2

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的 20.3.3 修改表头单元格 设置列表头单元格的内容&#xff1a; 一是可以通过 DataGridView.Columns[列号].HeaderCell来获得对应列的单…...

【Axure绘制原型】小图标使用技巧

获取小图标的网站&#xff1a;https://www.iconfont.cn/ 搜索相关图标 点击下载-复制SVG代码 回到Axure软件中粘贴&#xff0c;此时会显示出图片 在Axure软件中右键-变换图片-转换为形状 即可...

音视频之H.265/HEVC预测编码

H.265/HEVC系列文章&#xff1a; 1、音视频之H.265/HEVC编码框架及编码视频格式 2、音视频之H.265码流分析及解析 3、音视频之H.265/HEVC预测编码 预测编码是视频编码中的核心技术之一。对于视频信号来说&#xff0c;一幅图像内邻近像素之间有着较强的空间相关性,相邻图像之…...

无人机遥感与传统卫星遥感:谁更适合你的需求?

在对地观测领域&#xff0c;无人机遥感和卫星遥感是两种重要的技术手段&#xff0c;各自具有独特的技术原理、性能特点和应用优势。本文将从技术原理、性能特点和应用场景三个方面&#xff0c;对无人机遥感和卫星遥感进行系统对比&#xff0c;帮助读者全面了解两种技术的差异与…...

学习笔记—C++—模板初阶

目录 模板初阶 泛型编程 函数模板 模版概念 函数模版格式 模版的原理 函数模板的实例化 模版参数的匹配规则 类模板 模板初阶 泛型编程 使用函数重载虽然可以实现&#xff0c;但是有一下几个不好的地方&#xff1a; 1. 重载的函数仅仅是类型不同&#xff0c;代码复…...

【Python进阶】字典:高效键值存储的十大核心应用

目录 前言&#xff1a;技术背景与价值当前技术痛点解决方案概述目标读者说明 一、技术原理剖析核心概念图解核心作用讲解关键技术模块技术选型对比 二、实战演示环境配置要求核心代码实现&#xff08;10个案例&#xff09;案例1&#xff1a;基础操作案例2&#xff1a;字典推导式…...

充电宝项目中集成地图地址解析功能梳理

文章目录 MongoDB数据库引入pom依赖配置yaml配置文件参考POJOXLocationRepositoryservice服务方法 腾讯地图接口申请api key配置api key启动类配置RestTemplate控制层服务层 MongoDB数据库 MongoDB对应经纬度的查询具体很好的支持. 引入pom依赖 <dependency><group…...